package com.fabledsword.minstrel.cache

import com.fabledsword.minstrel.cache.db.dao.AudioCacheIndexDao
import com.fabledsword.minstrel.cache.db.dao.CachedTrackDao
import com.fabledsword.minstrel.library.data.toDomain
import com.fabledsword.minstrel.likes.data.LikesRepository
import com.fabledsword.minstrel.models.TrackRef
import com.fabledsword.minstrel.player.PlayerFactory
import javax.inject.Inject
import javax.inject.Singleton

private const val POOL_LIMIT = 100

/**
 * Offline play sources over the local audio-cache index. Mirrors
 * `flutter_client/lib/cache/shuffle_source.dart`.
 *
 * Both pools are UNIONs over the cache regardless of storage bucket
 * (liked AND recently-played both included). The two-bucket split is
 * storage/eviction-only and never filters playback — exactly what
 * this relies on.
 *
 * Divergence note: Flutter's `audioCacheIndex` only holds fully-cached
 * tracks, so its pools are inherently resident. Android's index records
 * plays (see `CacheIndexer`), so the recency list is intersected with
 * true Media3 SimpleCache residency here — the pool never offers a
 * played-then-evicted track that would fail to play offline.
 *
 * Surfaced on Home's Playlists row only when offline; tap shuffles +
 * plays the pool from the cache.
 */
@Singleton
class ShuffleSource @Inject constructor(
    private val cacheIndexDao: AudioCacheIndexDao,
    private val trackDao: CachedTrackDao,
    private val likes: LikesRepository,
    private val playerFactory: PlayerFactory,
) {
    /** Cached tracks, most-recently-played first (liked included). */
    suspend fun recentlyPlayed(limit: Int = POOL_LIMIT): List<TrackRef> =
        materialize(residentIdsByRecency()).take(limit)

    /** Cached tracks that are in the user's liked set, recency-ordered. */
    suspend fun liked(limit: Int = POOL_LIMIT): List<TrackRef> {
        val likedSet = likes.likedTrackIds()
        val ids = residentIdsByRecency().filter { it in likedSet }
        return materialize(ids).take(limit)
    }

    /** Recency-ordered ids whose audio is actually resident in the cache. */
    private suspend fun residentIdsByRecency(): List<String> {
        val resident = runCatching { playerFactory.simpleCache.keys.toSet() }
            .getOrDefault(emptySet())
        return cacheIndexDao.trackIdsByRecency().filter { it in resident }
    }

    /** Materialize ordered ids into TrackRefs from cached metadata, preserving order. */
    private suspend fun materialize(orderedIds: List<String>): List<TrackRef> {
        if (orderedIds.isEmpty()) return emptyList()
        val byId = trackDao.getByIds(orderedIds).associateBy { it.id }
        return orderedIds.mapNotNull { byId[it]?.toDomain() }
    }
}
