// Dominant-color extraction from album cover art (#396 item 1).
// The full-screen Now Playing screen uses this to paint a top-to-bottom
// gradient backdrop that grounds each track in its album's palette.
//
// Implementation: reuses AlbumCoverCache to get a local file path for
// the cover, then runs PaletteGenerator over it. Results are cached
// in-memory keyed by album_id so back-to-back plays of the same album
// don't repeat the work. Cache is process-lifetime; album-art changes
// are rare enough that LRU eviction isn't worth the complexity.
//
// Returns null on any failure (no cover, empty album_id, palette
// extraction returned nothing). Callers fall back to the FabledSword
// obsidian color for the gradient when null.

import 'dart:io';

import 'package:flutter/painting.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:palette_generator/palette_generator.dart';

import 'player_provider.dart';

/// Caches dominant colors keyed by album_id. Process-lifetime; refilled
/// on app restart.
class AlbumColorCache {
  AlbumColorCache(this._ref);

  final Ref _ref;
  final Map<String, Color?> _cache = {};

  /// Returns the dominant color for the album's cover, or null if no
  /// cover is available or extraction fails. Same call for the same
  /// album_id is cached after the first successful resolution.
  Future<Color?> getOrExtract(String albumId) async {
    if (albumId.isEmpty) return null;
    if (_cache.containsKey(albumId)) return _cache[albumId];
    final color = await _extract(albumId);
    _cache[albumId] = color;
    return color;
  }

  /// Synchronous peek. Returns the previously-extracted color if this
  /// album has been resolved this process; otherwise null. Used by
  /// the now-playing fast-path swap so a warm cache transitions the
  /// gradient in lockstep with the audio, instead of awaiting the
  /// async [getOrExtract] future. A null return means "either no
  /// extraction yet or extraction returned null" — caller falls back
  /// to the async path.
  Color? peekColor(String albumId) => _cache[albumId];

  Future<Color?> _extract(String albumId) async {
    try {
      final coverCache = _ref.read(albumCoverCacheProvider);
      final path = await coverCache.getOrFetch(albumId);
      if (path == null) return null;
      final file = File(path);
      if (!await file.exists()) return null;
      final palette = await PaletteGenerator.fromImageProvider(
        FileImage(file),
        // Small target size: palette extraction is CPU-bound and the
        // gradient only needs a single dominant color, so we don't
        // need full-resolution sampling.
        size: const Size(80, 80),
        maximumColorCount: 8,
      );
      // Prefer the explicit dominant color; fall back to the strongest
      // muted swatch (which tends to read better as a background than
      // a hot vibrant pick), then any populated swatch.
      final swatch = palette.dominantColor ??
          palette.darkMutedColor ??
          palette.darkVibrantColor ??
          palette.mutedColor;
      return swatch?.color;
    } catch (_) {
      return null;
    }
  }
}

final albumColorCacheProvider = Provider<AlbumColorCache>(
  (ref) => AlbumColorCache(ref),
);

/// Family provider: dominant color for the given album_id, or null if
/// no cover / extraction failed. The Now Playing screen watches this
/// keyed by the current MediaItem's album_id.
final albumColorProvider = FutureProvider.family<Color?, String>(
  (ref, albumId) async {
    final cache = ref.watch(albumColorCacheProvider);
    return cache.getOrExtract(albumId);
  },
);
