package com.fabledsword.minstrel.cache

import com.fabledsword.minstrel.cache.db.CacheSource
import com.fabledsword.minstrel.cache.db.dao.AudioCacheIndexDao
import com.fabledsword.minstrel.cache.db.entities.AudioCacheIndexEntity
import com.fabledsword.minstrel.di.ApplicationScope
import com.fabledsword.minstrel.models.TrackRef
import com.fabledsword.minstrel.player.PlayerController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Populates `audio_cache_index` from playback so the offline pools
 * ([ShuffleSource]) and the cached indicator have a data source.
 *
 * Mirrors the user-visible behavior of Flutter's `AudioCacheManager`
 * (`registerStreamCache` + `touch`): a track you play gets cached as a
 * side effect of streaming, and playing it bumps its recency. The
 * Android idiom differs intentionally — Media3's `SimpleCache` is the
 * real byte store (span-based, keyed by trackId via the MediaItem's
 * custom cache key), so this index is a lightweight *play-recency*
 * record (source = INCIDENTAL) rather than Flutter's file-per-track
 * table. SimpleCache stays the source of truth for "is it actually on
 * disk"; this index orders "recently played" for the offline pools.
 *
 * State observed against [PlayerController.uiState], deduped by track
 * id so each entry into the player records once. Eager-constructed via
 * the construct-the-singleton trick in `MinstrelApplication`.
 */
@Singleton
class CacheIndexer @Inject constructor(
    playerController: PlayerController,
    private val dao: AudioCacheIndexDao,
    @ApplicationScope private val scope: CoroutineScope,
) {
    init {
        scope.launch {
            playerController.uiState
                .map { it.currentTrack }
                .distinctUntilChanged { a, b -> a?.id == b?.id }
                .collect { track -> if (track != null) record(track) }
        }
    }

    private suspend fun record(track: TrackRef) {
        val now = Clock.System.now()
        if (dao.get(track.id) != null) {
            dao.touchLastPlayed(track.id, now)
        } else {
            dao.upsert(
                AudioCacheIndexEntity(
                    trackId = track.id,
                    path = track.streamUrl,
                    sizeBytes = 0,
                    lastPlayedAt = now,
                    source = CacheSource.INCIDENTAL,
                ),
            )
        }
    }
}
