package org.mosad.teapod.ui.activity.main.viewmodel

import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.mosad.teapod.parser.crunchyroll.Crunchyroll
import org.mosad.teapod.util.ItemMedia
import org.mosad.teapod.util.toItemMediaList

class LibraryFragmentViewModel : ViewModel() {

    val PAGESIZE = 50

    private val uiState = MutableStateFlow<UiState>(UiState.Loading)
    private var oldSearchQuery = ""
    private var searchJob: Job? = null
    var isLazyLoading = false
        internal set

    sealed class UiState {
        object Loading : UiState()
        data class Browse(
            val itemList: MutableList<ItemMedia>
        ) : UiState()
        data class Search(
            val itemList: List<ItemMedia>
        ) : UiState()
        data class Error(val message: String?) : UiState()
    }

    init {
        load()
    }

    fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) {
        scope.launch { uiState.collect { collector(it) } }
    }

    /**
     * initially load the first n browsing items
     */
    private fun load() {
        viewModelScope.launch {
            uiState.emit(UiState.Loading)

            try {
                initBrowse()
            } catch (ex: Exception) {
                uiState.emit(UiState.Error(ex.message))
            }
        }
    }

    /**
     * Search for a query string at Crunchyroll and emit the new ui state.
     */
    fun search(query: String) {
        // return if nothing has changed
        if (query == oldSearchQuery) return

        // update the old query since it has changed
        oldSearchQuery = query

        viewModelScope.launch {

            // always cancel a running search job
            if (searchJob?.isActive == true) searchJob?.cancel()

            // handle state change: browse <-> search
            if (query.isEmpty()) {
                // if the query is empty change back to browse state
                initBrowse()
            } else {
                // TODO handle errors

                // if the current ui state is not search, clear the recyclerview
                if (uiState.value !is UiState.Search) {
                    uiState.emit(UiState.Search(emptyList()))
                }

                // create a new search job
                searchJob = viewModelScope.async {
                    // wait for a few ms: if the user is typing the task will get canceled
                    delay(250)

                    val results = Crunchyroll.search(query, 50)
                        .items.firstOrNull()?.items?.toItemMediaList()
                        ?: listOf()
                    uiState.emit(UiState.Search(results))
                }
            }
        }
    }

    fun onLazyLoad() = viewModelScope.launch {
        isLazyLoading = true

        try {
            uiState.update { currentUiState ->
                if (currentUiState is UiState.Browse) {
                    val newBrowseItems = Crunchyroll.browse(start = currentUiState.itemList.size, n = PAGESIZE)
                        .toItemMediaList()
                    currentUiState.itemList.addAll(newBrowseItems)
                }
                currentUiState
            }
        } catch (ex: Exception) {
            uiState.emit(UiState.Error(ex.message))
        }

        isLazyLoading = false
    }

    private suspend fun initBrowse() {
        try {
            val initialBrowseItems = Crunchyroll.browse(n = PAGESIZE)
                .toItemMediaList()
                .toMutableList()
            uiState.emit(UiState.Browse(initialBrowseItems))
        } catch (ex: Exception) {
            uiState.emit(UiState.Error(ex.message))
        }
    }

}