// ignore_for_file: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
part of 'track_metadata_screen.dart';

// Embedded-cover preview: disk cache keyed by file validation token,
// plus the FFmpeg-backed refresh that extracts the preview image.
extension _TrackMetadataCover on _TrackMetadataScreenState {
  bool _isCacheTrackedPath(String? path) {
    if (!_hasPath(path)) return false;
    return _TrackMetadataScreenState._embeddedCoverPreviewCache.values.any(
      (entry) => entry.previewPath == path,
    );
  }

  bool _isVolatileSafTempPath(String path) {
    if (path.isEmpty) return false;
    return path.contains(
      '${Platform.pathSeparator}cache${Platform.pathSeparator}saf_',
    );
  }

  Future<String?> _readLocalFileValidationToken(String path) async {
    if (path.isEmpty || isContentUri(path) || _isVolatileSafTempPath(path)) {
      return null;
    }
    try {
      final stat = await fileStat(path);
      if (stat == null) return null;
      return '${stat.modified?.millisecondsSinceEpoch ?? 0}:${stat.size ?? 0}';
    } catch (_) {
      return null;
    }
  }

  Future<void> _cacheEmbeddedCoverPreview(
    String cacheKey,
    String sourcePath,
    String previewPath,
    ({int width, int height})? dimensions,
  ) async {
    final sourceValidationToken = await _readLocalFileValidationToken(
      sourcePath,
    );
    final existing =
        _TrackMetadataScreenState._embeddedCoverPreviewCache[cacheKey];
    _TrackMetadataScreenState._embeddedCoverPreviewCache[cacheKey] =
        _EmbeddedCoverPreviewCacheEntry(
          previewPath: previewPath,
          sourceValidationToken: sourceValidationToken,
          dimensions: dimensions,
        );
    if (existing != null && existing.previewPath != previewPath) {
      await _cleanupTempFileAndParentIfNotCached(existing.previewPath);
    }

    while (_TrackMetadataScreenState._embeddedCoverPreviewCache.length >
        _TrackMetadataScreenState._maxCoverPreviewCacheEntries) {
      final oldestKey =
          _TrackMetadataScreenState._embeddedCoverPreviewCache.keys.first;
      final removed = _TrackMetadataScreenState._embeddedCoverPreviewCache
          .remove(oldestKey);
      if (removed != null) {
        await _cleanupTempFileAndParentIfNotCached(removed.previewPath);
      }
    }
  }

  Future<void> _invalidateEmbeddedCoverPreviewCacheForPath(
    String cacheKey,
  ) async {
    if (cacheKey.isEmpty) return;
    final removed = _TrackMetadataScreenState._embeddedCoverPreviewCache.remove(
      cacheKey,
    );
    if (removed != null) {
      await _cleanupTempFileAndParentIfNotCached(removed.previewPath);
    }
  }

  Future<_EmbeddedCoverPreviewCacheEntry?>
  _getCachedEmbeddedCoverPreviewIfValid(
    String cacheKey,
    String sourcePath,
  ) async {
    if (cacheKey.isEmpty) return null;
    final cached =
        _TrackMetadataScreenState._embeddedCoverPreviewCache[cacheKey];
    if (cached == null) return null;

    if (!await fileExists(cached.previewPath)) {
      _TrackMetadataScreenState._embeddedCoverPreviewCache.remove(cacheKey);
      return null;
    }

    if (!isContentUri(sourcePath) && !_isVolatileSafTempPath(sourcePath)) {
      final currentToken = await _readLocalFileValidationToken(sourcePath);
      if (currentToken != null &&
          cached.sourceValidationToken != null &&
          currentToken != cached.sourceValidationToken) {
        _TrackMetadataScreenState._embeddedCoverPreviewCache.remove(cacheKey);
        await _cleanupTempFileAndParentIfNotCached(cached.previewPath);
        return null;
      }
    }

    return cached;
  }

  Future<void> _refreshEmbeddedCoverPreview({bool force = false}) async {
    final generation = _metadataLoadGeneration;
    final cacheKey = _coverCacheKey;
    final sourcePath = cleanFilePath;
    if (!force) {
      final cachedCover = await _getCachedEmbeddedCoverPreviewIfValid(
        cacheKey,
        sourcePath,
      );
      if (cachedCover != null) {
        if (mounted &&
            generation == _metadataLoadGeneration &&
            sourcePath == cleanFilePath &&
            (_embeddedCoverPreviewPath != cachedCover.previewPath ||
                _embeddedCoverDimensions != cachedCover.dimensions)) {
          setState(() {
            _embeddedCoverPreviewPath = cachedCover.previewPath;
            _embeddedCoverDimensions = cachedCover.dimensions;
          });
        }
        return;
      }
    }

    String? newPreviewPath;
    ({int width, int height})? newDimensions;
    try {
      if (!_fileExists) {
        await _invalidateEmbeddedCoverPreviewCacheForPath(cacheKey);
        await _cleanupTempFileAndParentIfNotCached(_embeddedCoverPreviewPath);
        if (mounted &&
            generation == _metadataLoadGeneration &&
            sourcePath == cleanFilePath) {
          setState(() {
            _embeddedCoverPreviewPath = null;
            _embeddedCoverDimensions = null;
          });
        }
        return;
      }
      if (force) {
        await _invalidateEmbeddedCoverPreviewCacheForPath(cacheKey);
      }
      final tempDir = await Directory.systemTemp.createTemp(
        'track_cover_preview_',
      );
      final outputPath =
          '${tempDir.path}${Platform.pathSeparator}cover_preview.jpg';
      final result = await PlatformBridge.extractCoverToFile(
        sourcePath,
        outputPath,
      );
      if (result['error'] == null && await File(outputPath).exists()) {
        newPreviewPath = outputPath;
        newDimensions = await FFmpegService.probeImageDimensions(outputPath);
        await _cacheEmbeddedCoverPreview(
          cacheKey,
          sourcePath,
          outputPath,
          newDimensions,
        );
      } else {
        try {
          await tempDir.delete(recursive: true);
        } catch (_) {}
      }
    } catch (_) {}

    final oldPreviewPath = _embeddedCoverPreviewPath;
    if (!mounted ||
        generation != _metadataLoadGeneration ||
        sourcePath != cleanFilePath) {
      if (newPreviewPath != null) {
        await _cleanupTempFileAndParentIfNotCached(newPreviewPath);
      }
      return;
    }

    setState(() {
      _embeddedCoverPreviewPath = newPreviewPath;
      _embeddedCoverDimensions = newDimensions;
    });
    if (oldPreviewPath != null && oldPreviewPath != newPreviewPath) {
      await _cleanupTempFileAndParentIfNotCached(oldPreviewPath);
    }
  }

  bool get _isLocalItem => _currentLocalLibraryItem != null;
  DownloadHistoryItem? get _downloadItem => _currentDownloadItem;
  LocalLibraryItem? get _localLibraryItem => _currentLocalLibraryItem;
  bool get _hasHistoryNavigation =>
      widget.historyNavigationItems != null && widget.navigationIndex != null;
  bool get _hasLocalNavigation =>
      widget.localNavigationItems != null && widget.navigationIndex != null;
  bool get _hasTrackSwipeNavigation =>
      _hasHistoryNavigation || _hasLocalNavigation;
  int? get _navigationIndex => _currentNavigationIndex;
  int get _navigationLength =>
      widget.historyNavigationItems?.length ??
      widget.localNavigationItems?.length ??
      0;

  String get _itemId =>
      _isLocalItem ? _localLibraryItem!.id : _downloadItem!.id;
  String get trackName =>
      _editedMetadata?['title']?.toString() ??
      (_isLocalItem ? _localLibraryItem!.trackName : _downloadItem!.trackName);
  String get artistName =>
      _editedMetadata?['artist']?.toString() ??
      (_isLocalItem
          ? _localLibraryItem!.artistName
          : _downloadItem!.artistName);
  String get albumName =>
      _editedMetadata?['album']?.toString() ??
      (_isLocalItem ? _localLibraryItem!.albumName : _downloadItem!.albumName);
  String? get albumArtist {
    final edited = _editedMetadata?['album_artist']?.toString();
    if (edited != null && edited.isNotEmpty) return edited;
    return normalizeOptionalString(
      _isLocalItem
          ? _localLibraryItem!.albumArtist
          : _downloadItem!.albumArtist,
    );
  }

  int? get trackNumber {
    final edited = _editedMetadata?['track_number'];
    if (edited != null) {
      final v = int.tryParse(edited.toString());
      if (v != null && v > 0) return v;
    }
    return _isLocalItem
        ? _localLibraryItem!.trackNumber
        : _downloadItem!.trackNumber;
  }

  int? get totalTracks =>
      readPositiveInt(_editedMetadata?['total_tracks']) ??
      (_isLocalItem
          ? _localLibraryItem!.totalTracks
          : _downloadItem!.totalTracks);

  int? get discNumber {
    final edited = _editedMetadata?['disc_number'];
    if (edited != null) {
      final v = int.tryParse(edited.toString());
      if (v != null && v > 0) return v;
    }
    return _isLocalItem
        ? _localLibraryItem!.discNumber
        : _downloadItem!.discNumber;
  }

  int? get totalDiscs =>
      readPositiveInt(_editedMetadata?['total_discs']) ??
      (_isLocalItem
          ? _localLibraryItem!.totalDiscs
          : _downloadItem!.totalDiscs);

  String? get releaseDate =>
      _editedMetadata?['date']?.toString() ??
      (_isLocalItem
          ? _localLibraryItem!.releaseDate
          : _downloadItem!.releaseDate);
  String? get isrc {
    final raw =
        _editedMetadata?['isrc']?.toString() ??
        (_isLocalItem ? _localLibraryItem!.isrc : _downloadItem!.isrc);
    if (raw == null || raw.trim().isEmpty) return null;
    final upper = raw.trim().toUpperCase();
    // Only accept valid ISRC codes (CC-XXX-YY-NNNNN, 12 alphanumeric chars).
    // Strip hyphens/spaces that some sources include.
    final stripped = upper.replaceAll(RegExp(r'[-\s]'), '');
    if (_isrcValidationPattern.hasMatch(stripped)) return stripped;
    return null;
  }

  static final RegExp _isrcValidationPattern = RegExp(
    r'^[A-Z]{2}[A-Z0-9]{3}\d{7}$',
  );
  String? get genre =>
      _editedMetadata?['genre']?.toString() ??
      (_isLocalItem ? _localLibraryItem!.genre : _downloadItem!.genre);
  String? get label =>
      _editedMetadata?['label']?.toString() ??
      (_isLocalItem ? _localLibraryItem!.label : _downloadItem!.label);
  String? get copyright =>
      _editedMetadata?['copyright']?.toString() ??
      (_isLocalItem ? _localLibraryItem!.copyright : _downloadItem!.copyright);
  String? get composer =>
      _editedMetadata?['composer']?.toString() ??
      (_isLocalItem ? _localLibraryItem!.composer : null);
  String? get albumType =>
      normalizeOptionalString(_editedMetadata?['album_type']?.toString());
  String? get upc => normalizeOptionalString(
    (_editedMetadata?['upc'] ?? _editedMetadata?['barcode'])?.toString(),
  );
  String? get comment =>
      normalizeOptionalString(_editedMetadata?['comment']?.toString());
  bool get isExplicit =>
      parseExplicitFlag(_editedMetadata?['explicit']) ??
      (_isLocalItem ? _localLibraryItem!.explicit : _downloadItem!.explicit);
  int? get duration =>
      readPositiveInt(_editedMetadata?['duration']) ??
      (_isLocalItem ? _localLibraryItem!.duration : _downloadItem!.duration);
  int? get bitDepth =>
      readPositiveInt(_editedMetadata?['bit_depth']) ??
      (_isLocalItem ? _localLibraryItem!.bitDepth : _downloadItem!.bitDepth);
  int? get sampleRate =>
      readPositiveInt(_editedMetadata?['sample_rate']) ??
      (_isLocalItem
          ? _localLibraryItem!.sampleRate
          : _downloadItem!.sampleRate);
  int? get _audioBitrate =>
      _isLocalItem ? _localLibraryItem!.bitrate : _downloadItem?.bitrate;
  String? get _storedAudioFormat =>
      _resolvedAudioFormat ??
      (_isLocalItem ? _localLibraryItem?.format : _downloadItem?.format);

  String get _filePath =>
      _isLocalItem ? _localLibraryItem!.filePath : _downloadItem!.filePath;
  String get _coverHeroTag =>
      widget.coverHeroTag ??
      (_isLocalItem ? 'cover_lib_$_itemId' : 'cover_$_itemId');
  String? get _coverUrl => _isLocalItem
      ? null
      : highResCoverUrl(normalizeRemoteHttpUrl(_downloadItem!.coverUrl));

  String? get _localCoverPath =>
      _isLocalItem ? _localLibraryItem!.coverPath : null;
  String? get _spotifyId => _isLocalItem ? null : _downloadItem!.spotifyId;
  String get _service =>
      _isLocalItem ? MusicServices.local : _downloadItem!.service;
  DateTime get _addedAt {
    if (_isLocalItem) {
      final modTime = _localLibraryItem!.fileModTime;
      if (modTime != null && modTime > 0) {
        return DateTime.fromMillisecondsSinceEpoch(modTime);
      }
      return _localLibraryItem!.scannedAt;
    }
    return _downloadItem!.downloadedAt;
  }

  String? get _quality => _isLocalItem ? null : _downloadItem!.quality;

  int? _readPlausibleBitrateKbps(dynamic value) {
    final parsed = readPositiveInt(value);
    if (parsed == null) return null;
    final kbps = parsed >= 10000 ? (parsed / 1000).round() : parsed;
    return kbps >= 16 ? kbps : null;
  }
}
