package com.fabledsword.minstrel.home.data

import com.fabledsword.minstrel.api.endpoints.HomeApi
import com.fabledsword.minstrel.api.endpoints.MeApi
import com.fabledsword.minstrel.cache.db.dao.CachedHomeIndexDao
import com.fabledsword.minstrel.cache.db.entities.CachedHomeIndexEntity
import com.fabledsword.minstrel.metadata.HomeArtistPrewarmer
import com.fabledsword.minstrel.metadata.MetadataProvider
import com.fabledsword.minstrel.models.AlbumRef
import com.fabledsword.minstrel.models.ArtistRef
import com.fabledsword.minstrel.models.HomeTile
import com.fabledsword.minstrel.models.SystemPlaylistsStatus
import com.fabledsword.minstrel.models.TrackRef
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import retrofit2.Retrofit
import retrofit2.create
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Cache-first reads for the Home screen. Per-section ID lists live in
 * `cached_home_index`; entity rows live in `cached_albums` /
 * `cached_artists` / `cached_tracks`.
 *
 * Each section is a `Flow<List<HomeTile<Ref>>>`: the index Flow drives
 * the ordered id list, and each id maps to a per-entity
 * [MetadataProvider] Flow that emits the cached row (or null while it
 * hydrates) and fires a dedup'd, concurrency-gated on-miss fetch. A
 * null [HomeTile.value] renders as a skeleton in position; the tile
 * reveals when the fetch lands and Room re-emits. Mirrors Flutter's
 * per-item tile providers.
 *
 * `refreshIndex()` pulls `GET /api/home/index`, replaces each section
 * in-place (delete-then-insert, so the section Flows re-fire), and
 * pre-warms the top artists via [HomeArtistPrewarmer].
 */
@Singleton
// Per-section observe accessors (one per Home row) inflate the function
// count past detekt's 11 default — accessor density, not complexity.
@Suppress("TooManyFunctions")
class HomeRepository @Inject constructor(
    private val homeIndexDao: CachedHomeIndexDao,
    private val metadataProvider: MetadataProvider,
    private val prewarmer: HomeArtistPrewarmer,
    retrofit: Retrofit,
) {
    private val api: HomeApi = retrofit.create()
    private val meApi: MeApi = retrofit.create()

    /**
     * Caller's system-playlist build state for the Home placeholder
     * cards. One-shot GET; the caller refreshes alongside the index.
     */
    suspend fun getSystemPlaylistsStatus(): SystemPlaylistsStatus =
        meApi.getSystemPlaylistsStatus().let {
            SystemPlaylistsStatus(
                inFlight = it.inFlight,
                lastRunAt = it.lastRunAt,
                lastError = it.lastError,
            )
        }

    fun observeRecentlyAddedAlbums(): Flow<List<HomeTile<AlbumRef>>> =
        observeAlbumSection(SECTION_RECENTLY_ADDED_ALBUMS)

    fun observeRediscoverAlbums(): Flow<List<HomeTile<AlbumRef>>> =
        observeAlbumSection(SECTION_REDISCOVER_ALBUMS)

    fun observeRediscoverArtists(): Flow<List<HomeTile<ArtistRef>>> =
        observeArtistSection(SECTION_REDISCOVER_ARTISTS)

    fun observeMostPlayedTracks(): Flow<List<HomeTile<TrackRef>>> =
        observeTrackSection(SECTION_MOST_PLAYED_TRACKS)

    fun observeLastPlayedArtists(): Flow<List<HomeTile<ArtistRef>>> =
        observeArtistSection(SECTION_LAST_PLAYED_ARTISTS)

    fun observeYouMightLikeAlbums(): Flow<List<HomeTile<AlbumRef>>> =
        observeAlbumSection(SECTION_YOU_MIGHT_LIKE_ALBUMS)

    fun observeYouMightLikeArtists(): Flow<List<HomeTile<ArtistRef>>> =
        observeArtistSection(SECTION_YOU_MIGHT_LIKE_ARTISTS)

    /**
     * Pulls /api/home/index, replaces each cached_home_index section,
     * and pre-warms the top artists. The section Flows re-fire on the
     * index change; missing entity rows hydrate via the on-miss path.
     */
    suspend fun refreshIndex() {
        val wire = api.getHomeIndex()
        replaceSection(SECTION_RECENTLY_ADDED_ALBUMS, "album", wire.recentlyAddedAlbums)
        replaceSection(SECTION_REDISCOVER_ALBUMS, "album", wire.rediscoverAlbums)
        replaceSection(SECTION_REDISCOVER_ARTISTS, "artist", wire.rediscoverArtists)
        replaceSection(SECTION_MOST_PLAYED_TRACKS, "track", wire.mostPlayedTracks)
        replaceSection(SECTION_LAST_PLAYED_ARTISTS, "artist", wire.lastPlayedArtists)
        replaceSection(SECTION_YOU_MIGHT_LIKE_ALBUMS, "album", wire.youMightLikeAlbums)
        replaceSection(SECTION_YOU_MIGHT_LIKE_ARTISTS, "artist", wire.youMightLikeArtists)
        prewarmer.warm(
            wire.rediscoverArtists + wire.lastPlayedArtists + wire.youMightLikeArtists,
        )
    }

    private suspend fun replaceSection(section: String, entityType: String, ids: List<String>) {
        homeIndexDao.deleteBySection(section)
        if (ids.isEmpty()) return
        homeIndexDao.upsertAll(
            ids.mapIndexed { index, id ->
                CachedHomeIndexEntity(
                    section = section,
                    position = index,
                    entityType = entityType,
                    entityId = id,
                )
            },
        )
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    private fun observeAlbumSection(section: String): Flow<List<HomeTile<AlbumRef>>> =
        homeIndexDao.observeBySection(section).flatMapLatest { rows ->
            if (rows.isEmpty()) {
                flowOf(emptyList())
            } else {
                combine(rows.map { metadataProvider.observeAlbum(it.entityId) }) { refs ->
                    rows.mapIndexed { i, r -> HomeTile(r.entityId, refs[i]) }
                }
            }
        }

    @OptIn(ExperimentalCoroutinesApi::class)
    private fun observeArtistSection(section: String): Flow<List<HomeTile<ArtistRef>>> =
        homeIndexDao.observeBySection(section).flatMapLatest { rows ->
            if (rows.isEmpty()) {
                flowOf(emptyList())
            } else {
                combine(rows.map { metadataProvider.observeArtist(it.entityId) }) { refs ->
                    rows.mapIndexed { i, r -> HomeTile(r.entityId, refs[i]) }
                }
            }
        }

    @OptIn(ExperimentalCoroutinesApi::class)
    private fun observeTrackSection(section: String): Flow<List<HomeTile<TrackRef>>> =
        homeIndexDao.observeBySection(section).flatMapLatest { rows ->
            if (rows.isEmpty()) {
                flowOf(emptyList())
            } else {
                combine(rows.map { metadataProvider.observeTrack(it.entityId) }) { refs ->
                    rows.mapIndexed { i, r -> HomeTile(r.entityId, refs[i]) }
                }
            }
        }

    companion object {
        const val SECTION_RECENTLY_ADDED_ALBUMS = "recently_added_albums"
        const val SECTION_REDISCOVER_ALBUMS = "rediscover_albums"
        const val SECTION_REDISCOVER_ARTISTS = "rediscover_artists"
        const val SECTION_MOST_PLAYED_TRACKS = "most_played_tracks"
        const val SECTION_LAST_PLAYED_ARTISTS = "last_played_artists"
        const val SECTION_YOU_MIGHT_LIKE_ALBUMS = "you_might_like_albums"
        const val SECTION_YOU_MIGHT_LIKE_ARTISTS = "you_might_like_artists"
    }
}
