import 'dart:convert'; import 'package:flutter/services.dart'; /// Bridge to communicate with Go backend via platform channels class PlatformBridge { static const _channel = MethodChannel('com.zarz.spotiflac/backend'); /// Parse and validate Spotify URL static Future> parseSpotifyUrl(String url) async { final result = await _channel.invokeMethod('parseSpotifyUrl', {'url': url}); return jsonDecode(result as String) as Map; } /// Get Spotify metadata from URL static Future> getSpotifyMetadata(String url) async { final result = await _channel.invokeMethod('getSpotifyMetadata', {'url': url}); return jsonDecode(result as String) as Map; } /// Search Spotify static Future> searchSpotify(String query, {int limit = 10}) async { final result = await _channel.invokeMethod('searchSpotify', { 'query': query, 'limit': limit, }); return jsonDecode(result as String) as Map; } /// Search Spotify for tracks and artists static Future> searchSpotifyAll(String query, {int trackLimit = 15, int artistLimit = 3}) async { final result = await _channel.invokeMethod('searchSpotifyAll', { 'query': query, 'track_limit': trackLimit, 'artist_limit': artistLimit, }); return jsonDecode(result as String) as Map; } /// Check track availability on streaming services static Future> checkAvailability(String spotifyId, String isrc) async { final result = await _channel.invokeMethod('checkAvailability', { 'spotify_id': spotifyId, 'isrc': isrc, }); return jsonDecode(result as String) as Map; } /// Download a track from specific service static Future> downloadTrack({ required String isrc, required String service, required String spotifyId, required String trackName, required String artistName, required String albumName, String? albumArtist, String? coverUrl, required String outputDir, required String filenameFormat, String quality = 'LOSSLESS', bool embedLyrics = true, bool embedMaxQualityCover = true, int trackNumber = 1, int discNumber = 1, int totalTracks = 1, String? releaseDate, String? itemId, int durationMs = 0, }) async { final request = jsonEncode({ 'isrc': isrc, 'service': service, 'spotify_id': spotifyId, 'track_name': trackName, 'artist_name': artistName, 'album_name': albumName, 'album_artist': albumArtist ?? artistName, 'cover_url': coverUrl, 'output_dir': outputDir, 'filename_format': filenameFormat, 'quality': quality, 'embed_lyrics': embedLyrics, 'embed_max_quality_cover': embedMaxQualityCover, 'track_number': trackNumber, 'disc_number': discNumber, 'total_tracks': totalTracks, 'release_date': releaseDate ?? '', 'item_id': itemId ?? '', 'duration_ms': durationMs, }); final result = await _channel.invokeMethod('downloadTrack', request); return jsonDecode(result as String) as Map; } /// Download with automatic fallback to other services static Future> downloadWithFallback({ required String isrc, required String spotifyId, required String trackName, required String artistName, required String albumName, String? albumArtist, String? coverUrl, required String outputDir, required String filenameFormat, String quality = 'LOSSLESS', bool embedLyrics = true, bool embedMaxQualityCover = true, int trackNumber = 1, int discNumber = 1, int totalTracks = 1, String? releaseDate, String preferredService = 'tidal', String? itemId, int durationMs = 0, }) async { final request = jsonEncode({ 'isrc': isrc, 'service': preferredService, 'spotify_id': spotifyId, 'track_name': trackName, 'artist_name': artistName, 'album_name': albumName, 'album_artist': albumArtist ?? artistName, 'cover_url': coverUrl, 'output_dir': outputDir, 'filename_format': filenameFormat, 'quality': quality, 'embed_lyrics': embedLyrics, 'embed_max_quality_cover': embedMaxQualityCover, 'track_number': trackNumber, 'disc_number': discNumber, 'total_tracks': totalTracks, 'release_date': releaseDate ?? '', 'item_id': itemId ?? '', 'duration_ms': durationMs, }); final result = await _channel.invokeMethod('downloadWithFallback', request); return jsonDecode(result as String) as Map; } /// Get download progress (legacy single download) static Future> getDownloadProgress() async { final result = await _channel.invokeMethod('getDownloadProgress'); return jsonDecode(result as String) as Map; } /// Get progress for all active downloads (concurrent mode) static Future> getAllDownloadProgress() async { final result = await _channel.invokeMethod('getAllDownloadProgress'); return jsonDecode(result as String) as Map; } /// Initialize progress tracking for a download item static Future initItemProgress(String itemId) async { await _channel.invokeMethod('initItemProgress', {'item_id': itemId}); } /// Finish progress tracking for a download item static Future finishItemProgress(String itemId) async { await _channel.invokeMethod('finishItemProgress', {'item_id': itemId}); } /// Clear progress tracking for a download item static Future clearItemProgress(String itemId) async { await _channel.invokeMethod('clearItemProgress', {'item_id': itemId}); } /// Set download directory static Future setDownloadDirectory(String path) async { await _channel.invokeMethod('setDownloadDirectory', {'path': path}); } /// Check if file with ISRC already exists static Future> checkDuplicate(String outputDir, String isrc) async { final result = await _channel.invokeMethod('checkDuplicate', { 'output_dir': outputDir, 'isrc': isrc, }); return jsonDecode(result as String) as Map; } /// Build filename from template static Future buildFilename(String template, Map metadata) async { final result = await _channel.invokeMethod('buildFilename', { 'template': template, 'metadata': jsonEncode(metadata), }); return result as String; } /// Sanitize filename static Future sanitizeFilename(String filename) async { final result = await _channel.invokeMethod('sanitizeFilename', { 'filename': filename, }); return result as String; } /// Fetch lyrics for a track static Future> fetchLyrics( String spotifyId, String trackName, String artistName, ) async { final result = await _channel.invokeMethod('fetchLyrics', { 'spotify_id': spotifyId, 'track_name': trackName, 'artist_name': artistName, }); return jsonDecode(result as String) as Map; } /// Get lyrics in LRC format /// First tries to extract from embedded file, then falls back to internet static Future getLyricsLRC( String spotifyId, String trackName, String artistName, { String? filePath, }) async { final result = await _channel.invokeMethod('getLyricsLRC', { 'spotify_id': spotifyId, 'track_name': trackName, 'artist_name': artistName, 'file_path': filePath ?? '', }); return result as String; } /// Embed lyrics into an existing FLAC file static Future> embedLyricsToFile( String filePath, String lyrics, ) async { final result = await _channel.invokeMethod('embedLyricsToFile', { 'file_path': filePath, 'lyrics': lyrics, }); return jsonDecode(result as String) as Map; } /// Cleanup idle HTTP connections to prevent TCP exhaustion /// Call this periodically during large batch downloads static Future cleanupConnections() async { await _channel.invokeMethod('cleanupConnections'); } /// Start foreground download service to keep downloads running in background static Future startDownloadService({ String trackName = '', String artistName = '', int queueCount = 0, }) async { await _channel.invokeMethod('startDownloadService', { 'track_name': trackName, 'artist_name': artistName, 'queue_count': queueCount, }); } /// Stop foreground download service static Future stopDownloadService() async { await _channel.invokeMethod('stopDownloadService'); } /// Update download service notification progress static Future updateDownloadServiceProgress({ required String trackName, required String artistName, required int progress, required int total, required int queueCount, }) async { await _channel.invokeMethod('updateDownloadServiceProgress', { 'track_name': trackName, 'artist_name': artistName, 'progress': progress, 'total': total, 'queue_count': queueCount, }); } /// Check if download service is running static Future isDownloadServiceRunning() async { final result = await _channel.invokeMethod('isDownloadServiceRunning'); return result as bool; } /// Set custom Spotify API credentials /// Pass empty strings to use default credentials static Future setSpotifyCredentials(String clientId, String clientSecret) async { await _channel.invokeMethod('setSpotifyCredentials', { 'client_id': clientId, 'client_secret': clientSecret, }); } /// Pre-warm track ID cache for album/playlist tracks /// This runs in background and returns immediately /// Speeds up subsequent downloads by caching ISRC → Track ID mappings static Future preWarmTrackCache(List> tracks) async { final tracksJson = jsonEncode(tracks); await _channel.invokeMethod('preWarmTrackCache', {'tracks': tracksJson}); } /// Get current track cache size static Future getTrackCacheSize() async { final result = await _channel.invokeMethod('getTrackCacheSize'); return result as int; } /// Clear track ID cache static Future clearTrackCache() async { await _channel.invokeMethod('clearTrackCache'); } }