// Offline play sources over the local cache index (#427 S4).
//
// "Shuffle all" is always-present and degrades with reachability:
//   online  → GET /api/library/shuffle (random over the whole library)
//   offline → a client shuffle over the entire local cache index
//
// Offline-only pools surfaced on Home beside the (disabled) system
// playlists: "Recently played" (cache by lastPlayedAt desc) and
// "Liked" (cache ∩ liked set). All of these are UNIONs over the
// cache regardless of storage bucket — liked AND recently-played
// both included. The two-bucket split (S2) is storage/eviction-only
// and never filters playback, which is exactly what this relies on.

import 'dart:math';

import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../library/library_providers.dart' show libraryApiProvider;
import '../likes/likes_provider.dart' show likedIdsProvider;
import '../models/track.dart';
import 'adapters.dart';
import 'audio_cache_manager.dart' show appDbProvider;
import 'offline_provider.dart';

class ShuffleSource {
  ShuffleSource(this._ref);
  final Ref _ref;

  /// Shuffle-all. Online: server-random. Offline: every cached
  /// track, shuffled. Empty offline → nothing cached yet.
  Future<List<TrackRef>> tracks({int limit = 100}) async {
    if (_ref.read(offlineProvider)) {
      final ids = await _cachedIdsByRecency();
      final list = await _refs(ids);
      list.shuffle(Random());
      return list.length > limit ? list.sublist(0, limit) : list;
    }
    final api = await _ref.read(libraryApiProvider.future);
    return api.shuffle(limit: limit);
  }

  /// Cached tracks, most-recently-played first (liked included).
  /// Cache-only — surfaced on Home only when offline.
  Future<List<TrackRef>> recentlyPlayed({int limit = 100}) async {
    final ids = await _cachedIdsByRecency();
    final list = await _refs(ids);
    return list.length > limit ? list.sublist(0, limit) : list;
  }

  /// Cached tracks that are in the user's liked set. Cache-only —
  /// surfaced on Home only when offline.
  Future<List<TrackRef>> liked({int limit = 100}) async {
    final likedSet =
        _ref.read(likedIdsProvider).value?.tracks ?? const <String>{};
    final ids =
        (await _cachedIdsByRecency()).where(likedSet.contains).toList();
    final list = await _refs(ids);
    return list.length > limit ? list.sublist(0, limit) : list;
  }

  /// Cached track ids ordered by lastPlayedAt desc (nulls last so
  /// never-touched downloads sort after real plays).
  Future<List<String>> _cachedIdsByRecency() async {
    final db = _ref.read(appDbProvider);
    final rows = await db.select(db.audioCacheIndex).get();
    rows.sort((a, b) {
      final av = a.lastPlayedAt, bv = b.lastPlayedAt;
      if (av == null && bv == null) return 0;
      if (av == null) return 1;
      if (bv == null) return -1;
      return bv.compareTo(av);
    });
    return rows.map((r) => r.trackId).toList();
  }

  /// Materializes ordered track ids into TrackRefs from the cached
  /// metadata tables, preserving the given order.
  Future<List<TrackRef>> _refs(List<String> orderedIds) async {
    if (orderedIds.isEmpty) return const [];
    final db = _ref.read(appDbProvider);
    final meta = await (db.select(db.cachedTracks)
          ..where((t) => t.id.isIn(orderedIds)))
        .get();
    if (meta.isEmpty) return const [];
    final byId = {for (final t in meta) t.id: t};
    final artistName = {
      for (final a in await db.select(db.cachedArtists).get()) a.id: a.name
    };
    final albumTitle = {
      for (final a in await db.select(db.cachedAlbums).get()) a.id: a.title
    };
    return [
      for (final id in orderedIds)
        if (byId[id] case final t?)
          t.toRef(
            artistName: artistName[t.artistId] ?? '',
            albumTitle: albumTitle[t.albumId] ?? '',
          )
    ];
  }
}

final shuffleSourceProvider =
    Provider<ShuffleSource>((ref) => ShuffleSource(ref));
