package com.ponzischeme89.memby.ui.genre

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.ponzischeme89.memby.data.EmbyRepository
import com.ponzischeme89.memby.data.GENRE_PAGE_SIZE
import com.ponzischeme89.memby.data.friendlyEmbyError
import com.ponzischeme89.memby.data.hasMoreGenreItems
import com.ponzischeme89.memby.data.model.BaseItem
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

data class GenreBrowseUiState(
    val categories: List<GenreCategory> = emptyList(),
    val selectedCategoryId: String? = null,
    val items: List<BaseItem> = emptyList(),
    val isLoading: Boolean = false,
    val isLoadingMore: Boolean = false,
    val canLoadMore: Boolean = false,
    val errorMessage: String? = null,
)

/** A paged, media-type-specific genre shelf used by the Movies and TV Series pages. */
class GenreBrowseViewModel(
    private val repository: EmbyRepository,
    private val itemType: String,
) : ViewModel() {
    private val categories = genreCategoryTabs(itemType)
    private val _state = MutableStateFlow(GenreBrowseUiState(categories = categories))
    val state: StateFlow<GenreBrowseUiState> = _state.asStateFlow()
    private var pageJob: Job? = null
    private val categoryPages = mutableMapOf<String, CachedCategoryPage>()

    fun selectCategory(categoryId: String) {
        val selected = categories.firstOrNull { it.id == categoryId } ?: categories.first()
        val current = state.value
        if (current.selectedCategoryId == selected.id && (current.items.isNotEmpty() || current.isLoading)) return
        val cached = categoryPages[selected.id]
        pageJob?.cancel()
        _state.update {
            it.copy(
                selectedCategoryId = selected.id,
                items = cached?.items.orEmpty(),
                isLoading = cached == null,
                isLoadingMore = false,
                canLoadMore = cached?.canLoadMore ?: false,
                errorMessage = null,
            )
        }
        if (cached == null) pageJob = viewModelScope.launch { loadPage(selected, 0) }
    }

    fun loadMore() {
        val current = state.value
        val category = categories.firstOrNull { it.id == current.selectedCategoryId } ?: return
        if (!current.canLoadMore || current.isLoading || current.isLoadingMore) return
        _state.update { it.copy(isLoadingMore = true) }
        pageJob = viewModelScope.launch { loadPage(category, current.items.size) }
    }

    fun retry() {
        val current = state.value
        val category = categories.firstOrNull { it.id == current.selectedCategoryId } ?: return
        val offset = current.items.size
        _state.update {
            it.copy(
                isLoading = offset == 0,
                isLoadingMore = offset > 0,
                errorMessage = null,
            )
        }
        pageJob?.cancel()
        pageJob = viewModelScope.launch { loadPage(category, offset) }
    }

    /** Applies optimistic favourite changes made on a detail page to this screen's copies. */
    fun applyFavouriteStates(changes: Map<String, Boolean>) {
        if (changes.isEmpty()) return
        categoryPages.entries.forEach { entry ->
            entry.setValue(
                entry.value.copy(
                    items = entry.value.items.map { item ->
                        changes[item.id]?.let { item.withFavourite(it) } ?: item
                    },
                ),
            )
        }
        _state.update { current ->
            current.copy(
                items = current.items.map { item ->
                    val favourite = changes[item.id] ?: return@map item
                    item.withFavourite(favourite)
                },
            )
        }
    }

    /** Applies optimistic watched changes to the paged copies without losing position. */
    fun applyPlayedStates(changes: Map<String, Boolean>) {
        if (changes.isEmpty()) return
        categoryPages.entries.forEach { entry ->
            entry.setValue(
                entry.value.copy(
                    items = entry.value.items.map { item ->
                        changes[item.id]?.let { item.withPlayed(it) } ?: item
                    },
                ),
            )
        }
        _state.update { current ->
            current.copy(
                items = current.items.map { item ->
                    val played = changes[item.id] ?: return@map item
                    item.withPlayed(played)
                },
            )
        }
    }

    private suspend fun loadPage(category: GenreCategory, offset: Int) {
        runCatching {
            if (category.genres.isEmpty()) {
                repository.browseLibrary(offset = offset, limit = GENRE_PAGE_SIZE, itemType = itemType)
            } else {
                repository.browseGenre(
                    genre = category.filter,
                    offset = offset,
                    limit = GENRE_PAGE_SIZE,
                    itemType = itemType,
                )
            }
        }.onSuccess { page ->
            _state.update { current ->
                if (current.selectedCategoryId != category.id || current.items.size != page.offset) {
                    return@update current
                }
                val items = (current.items + page.items).distinctBy(BaseItem::id)
                val canLoadMore = hasMoreGenreItems(
                    loaded = items.size,
                    total = page.total,
                    lastPageSize = page.items.size,
                    pageSize = GENRE_PAGE_SIZE,
                )
                categoryPages[category.id] = CachedCategoryPage(items, canLoadMore)
                current.copy(
                    items = items,
                    isLoading = false,
                    isLoadingMore = false,
                    canLoadMore = canLoadMore,
                    errorMessage = null,
                )
            }
        }.onFailure { error ->
            _state.update { current ->
                if (current.selectedCategoryId != category.id) current else current.copy(
                    isLoading = false,
                    isLoadingMore = false,
                    canLoadMore = false,
                    errorMessage = friendlyEmbyError(error),
                )
            }
        }
    }
}

private data class CachedCategoryPage(
    val items: List<BaseItem>,
    val canLoadMore: Boolean,
)

class GenreBrowseViewModelFactory(
    private val repository: EmbyRepository,
    private val itemType: String,
) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T =
        GenreBrowseViewModel(repository, itemType) as T
}
