package com.fabledsword.minstrel.library.ui

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.toRoute
import com.fabledsword.minstrel.api.ErrorCopy
import com.fabledsword.minstrel.connectivity.NetworkStatusController
import com.fabledsword.minstrel.connectivity.recoveries
import com.fabledsword.minstrel.library.data.LibraryRepository
import com.fabledsword.minstrel.likes.data.LikesRepository
import com.fabledsword.minstrel.models.AlbumDetailRef
import com.fabledsword.minstrel.models.AlbumRef
import com.fabledsword.minstrel.nav.AlbumDetail
import com.fabledsword.minstrel.nav.DetailSeedCache
import com.fabledsword.minstrel.player.PlayerController
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject

private const val SHARE_STOP_TIMEOUT_MS = 5_000L

sealed interface AlbumDetailUiState {
    data class Loading(val seed: AlbumRef? = null) : AlbumDetailUiState
    data class Success(val detail: AlbumDetailRef) : AlbumDetailUiState
    data class Error(val message: String) : AlbumDetailUiState
}

@HiltViewModel
class AlbumDetailViewModel @Inject constructor(
    private val repository: LibraryRepository,
    private val likes: LikesRepository,
    private val player: PlayerController,
    private val seedCache: DetailSeedCache,
    networkStatus: NetworkStatusController,
    savedStateHandle: SavedStateHandle,
) : ViewModel() {

    private val route: AlbumDetail = savedStateHandle.toRoute()
    val albumId: String = route.id

    private val internal =
        MutableStateFlow<AlbumDetailUiState>(AlbumDetailUiState.Loading())
    val uiState: StateFlow<AlbumDetailUiState> = internal.asStateFlow()

    val albumLiked: StateFlow<Boolean> =
        likes.observeIsLiked(LikesRepository.ENTITY_ALBUM, albumId)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(SHARE_STOP_TIMEOUT_MS),
                initialValue = false,
            )

    val likedTrackIds: StateFlow<Set<String>> =
        likes.observeLikedTrackIds()
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(SHARE_STOP_TIMEOUT_MS),
                initialValue = emptySet(),
            )

    init {
        refresh()
        // Screen-level auto-recovery (issue #1245): a detail that failed to
        // load while the server was unreachable re-fetches when health
        // returns, so coming back to the screen shows content, not an error.
        viewModelScope.launch {
            networkStatus.recoveries().collect {
                if (internal.value is AlbumDetailUiState.Error) refresh()
            }
        }
    }

    fun refresh(): Job = viewModelScope.launch {
        val carried = when (val cur = internal.value) {
            is AlbumDetailUiState.Loading -> cur.seed
            is AlbumDetailUiState.Success -> cur.detail.album
            is AlbumDetailUiState.Error -> null
        }
        internal.value = AlbumDetailUiState.Loading(carried ?: seedCache.peekAlbum(albumId))
        try {
            val detail = repository.refreshAlbumDetail(albumId)
            internal.value = AlbumDetailUiState.Success(detail)
        } catch (
            @Suppress("TooGenericExceptionCaught") e: Throwable,
        ) {
            internal.value = AlbumDetailUiState.Error(ErrorCopy.fromThrowable(e))
        }
    }

    /** Play the album starting at [startTrackId] (or the first track when null). */
    fun play(startTrackId: String?) {
        val detail = (internal.value as? AlbumDetailUiState.Success)?.detail ?: return
        if (detail.tracks.isEmpty()) return
        val startIndex = detail.tracks
            .indexOfFirst { it.id == startTrackId }
            .coerceAtLeast(0)
        player.setQueue(
            tracks = detail.tracks,
            initialIndex = startIndex,
            source = "album:${detail.album.id}",
        )
    }

    /**
     * Play the album in shuffled order. Pre-shuffles the list and hands
     * the result to the player — matches the PlaylistDetail shuffle
     * pattern. A future refinement could use Media3's
     * `setShuffleModeEnabled` for play-then-shuffle without disturbing
     * the original queue ordering.
     */
    fun shuffle() {
        val detail = (internal.value as? AlbumDetailUiState.Success)?.detail ?: return
        if (detail.tracks.isEmpty()) return
        player.setQueue(
            tracks = detail.tracks.shuffled(),
            initialIndex = 0,
            source = "album:${detail.album.id}",
        )
    }

    fun toggleLikeAlbum() {
        val desired = !albumLiked.value
        viewModelScope.launch {
            likes.toggleLike(LikesRepository.ENTITY_ALBUM, albumId, desired)
        }
    }

    fun toggleLikeTrack(trackId: String) {
        val desired = trackId !in likedTrackIds.value
        viewModelScope.launch {
            likes.toggleLike(LikesRepository.ENTITY_TRACK, trackId, desired)
        }
    }
}
