package com.pixelized.biblib.ui.screen.home

import android.app.Application
import android.util.Log
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.*
import com.pixelized.biblib.R
import com.pixelized.biblib.model.book.Book
import com.pixelized.biblib.model.search.FilterType
import com.pixelized.biblib.model.search.SortType
import com.pixelized.biblib.model.search.SortValue
import com.pixelized.biblib.repository.book.IBookRepository
import com.pixelized.biblib.repository.search.ISearchRepository
import com.pixelized.biblib.ui.screen.home.common.item.BookThumbnailUio
import com.pixelized.biblib.ui.screen.home.filter.FilterChipUio
import com.pixelized.biblib.ui.screen.home.options.OptionsUio
import com.pixelized.biblib.utils.extention.stringResource
import com.pixelized.biblib.utils.extention.toThumbnailUio
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.plus
import javax.inject.Inject

@HiltViewModel
class BookSearchViewModel @Inject constructor(
    application: Application,
    private val searchRepository: ISearchRepository,
    private val bookRepository: IBookRepository,
) : AndroidViewModel(application) {

    private val _error = MutableSharedFlow<HomeScreenErrorUio.LibraryUpdate>()
    val error: Flow<HomeScreenErrorUio.LibraryUpdate> = _error

    private val _updating = mutableStateOf(false)
    val updating: State<Boolean> = _updating

    private val _display = mutableStateOf(HomeDisplay.MICRO)
    val display: State<HomeDisplay> = _display

    val options = derivedStateOf {
        when(display.value) {
            HomeDisplay.MICRO -> OptionsUio(micro = true, small = false, large = false)
            HomeDisplay.SMALL -> OptionsUio(micro = false, small = true, large = false)
            HomeDisplay.GRID -> OptionsUio(micro = false, small = false, large = true)
        }
    }

    private var searchSource: BookSearchSource? = null
    val paging: Flow<PagingData<BookThumbnailUio>>

    private val _search = mutableStateOf<String?>(null)
    val search: State<String?> = _search

    var sortBy: Map<SortType, SortValue?> = mapOf(SortType.New to SortValue.ASC)
        private set

    private val newFilter = mutableNewFilterState()
    private var filterByNew: Boolean? = true
    private val authorFilter = mutableAuthorFilterState()
    private var filterByAuthorId: Int? = null
    private val seriesFilter = mutableSeriesFilterState()
    private var filterBySeriesId: Int? = null
    private val genreFilter = mutableGenreFilterState()
    private var filterByGenreId: Int? = null
    private val languageFilter = mutableLanguageFilterState()
    private var filterByLanguageId: Int? = null
    val filters: List<State<FilterChipUio>> = listOf(
        newFilter, authorFilter, seriesFilter, genreFilter, languageFilter
    )

    init {
        val searchFlow = Pager(
            config = PagingConfig(
                pageSize = SEARCH_PAGE_SIZE,
                enablePlaceholders = true,
            ),
            pagingSourceFactory = ::buildBookSource,
        ).flow
        // keep transaction updated with the pager.
        paging = searchFlow
            .map { pagingData -> pagingData.map { it.toThumbnailUio() } }
            .cachedIn(viewModelScope + Dispatchers.IO)
    }

    suspend fun updateLibrary() {
        try {
            _updating.value = true
            bookRepository.update()
            searchSource?.invalidate()
        } catch (exception: Exception) {
            Log.e("BookSearchViewModel", exception.message, exception)
            _error.emit(
                HomeScreenErrorUio.LibraryUpdate(
                    message = R.string.error_book_update_message,
                    action = R.string.error_book_update_action,
                )
            )
        } finally {
            _updating.value = false
        }
    }

    fun onDisplay(type: HomeDisplay) {
        _display.value = type
    }

    fun source(clean: Boolean = false, block: SortAndFilterScope.() -> Unit) {
        val scope = if (clean) {
            SortAndFilterScope(
                sortBy = mapOf(),
                isNew = null,
                title = null,
                author = null,
                authorId = null,
                series = null,
                seriesId = null,
                genre = null,
                genreId = null,
                language = null,
                languageId = null,
            )
        } else {
            SortAndFilterScope(
                sortBy = sortBy,
                isNew = filterByNew,
                title = search.value,
                author = authorFilter.value.label,
                authorId = filterByAuthorId,
                series = seriesFilter.value.label,
                seriesId = filterBySeriesId,
                genre = genreFilter.value.label,
                genreId = filterByGenreId,
                language = languageFilter.value.label,
                languageId = filterByLanguageId,
            )
        }
        scope.block()
        // filter the search
        if (_search.value != scope.title) {
            _search.value = scope.title
        }
        // clean the new filter
        if (filterByNew != scope.isNew) {
            filterByNew = scope.isNew
            newFilter.value = newFilter.value.copy(
                selected = scope.isNew == true,
                closable = scope.isNew == true,
            )
        }
        // clean the author filter
        if (filterByAuthorId != scope.authorId) {
            filterByAuthorId = scope.authorId
            authorFilter.value = authorFilter.value.copy(
                selected = scope.authorId != null,
                label = scope.author,
                closable = scope.authorId != null,
                openable = scope.authorId == null,
            )
        }
        // clean the author filter
        if (filterBySeriesId != scope.seriesId) {
            filterBySeriesId = scope.seriesId
            seriesFilter.value = seriesFilter.value.copy(
                selected = scope.seriesId != null,
                label = scope.series,
                closable = scope.seriesId != null,
                openable = scope.seriesId == null,
            )
        }
        // clean the author filter
        if (filterByGenreId != scope.genreId) {
            filterByGenreId = scope.genreId
            genreFilter.value = genreFilter.value.copy(
                selected = scope.genreId != null,
                label = scope.genre,
                closable = scope.genreId != null,
                openable = scope.genreId == null,
            )
        }
        // clean the author filter
        if (filterByLanguageId != scope.languageId) {
            filterByLanguageId = scope.languageId
            languageFilter.value = languageFilter.value.copy(
                selected = scope.languageId != null,
                label = scope.language,
                closable = scope.languageId != null,
                openable = scope.languageId == null,
            )
        }
        // apply the sorting
        if (sortBy != scope.sortBy) {
            sortBy = scope.sortBy
        }
        // invalidate the source
        searchSource?.invalidate()
    }

    fun filterByTitle(criteria: String?) {
        _search.value = criteria
        searchSource?.invalidate()
    }

    private fun buildBookSource(): PagingSource<Int, Book> {
        return BookSearchSource(
            searchRepository = searchRepository,
            search = search.value,
            isNew = filterByNew,
            authorId = filterByAuthorId,
            seriesId = filterBySeriesId,
            genreId = filterByGenreId,
            languageId = filterByLanguageId,
            sortBy = sortBy,
            limit = SEARCH_PAGE_SIZE,
        ).also {
            searchSource = it
        }
    }

    private fun mutableNewFilterState() = mutableStateOf(
        FilterChipUio(
            id = FilterType.New,
            title = stringResource(R.string.search_filter_new),
            label = null,
            selected = true,
            closable = false,
            openable = false,
        )
    )

    private fun mutableAuthorFilterState() = mutableStateOf(
        FilterChipUio(
            id = FilterType.Author,
            title = stringResource(R.string.search_filter_author),
            label = null,
            selected = false,
            closable = false,
            openable = true,
        )
    )

    private fun mutableSeriesFilterState() = mutableStateOf(
        FilterChipUio(
            id = FilterType.Series,
            title = stringResource(R.string.search_filter_series),
            label = null,
            selected = false,
            closable = false,
            openable = true,
        )
    )

    private fun mutableGenreFilterState() = mutableStateOf(
        FilterChipUio(
            id = FilterType.Genre,
            title = stringResource(R.string.search_filter_genre),
            label = null,
            selected = false,
            closable = false,
            openable = true,
        )
    )

    private fun mutableLanguageFilterState() = mutableStateOf(
        FilterChipUio(
            id = FilterType.Language,
            title = stringResource(R.string.search_filter_language),
            label = null,
            selected = false,
            closable = false,
            openable = true,
        )
    )

    class SortAndFilterScope(
        sortBy: Map<SortType, SortValue?>,
        isNew: Boolean?,
        title: String?,
        author: String?,
        authorId: Int?,
        series: String?,
        seriesId: Int?,
        genre: String?,
        genreId: Int?,
        language: String?,
        languageId: Int?,
    ) {
        var sortBy: Map<SortType, SortValue?> = sortBy
            private set
        var isNew: Boolean? = isNew
            private set
        var title: String? = title
            private set
        var author: String? = author
            private set
        var authorId: Int? = authorId
            private set
        var series: String? = series
            private set
        var seriesId: Int? = seriesId
            private set
        var genre: String? = genre
            private set
        var genreId: Int? = genreId
            private set
        var language: String? = language
            private set
        var languageId: Int? = languageId
            private set

        fun sortBy(type: SortType, value: SortValue?) {
            this.sortBy = mapOf(type to value)
        }

        fun sortBy(sortBy: Map<SortType, SortValue?>) {
            this.sortBy = sortBy
        }

        fun filterByNew(isNew: Boolean) {
            this.isNew = if (isNew) true else null
        }

        fun filterByTitle(title: String?) {
            this.title = title
        }

        fun filterByAuthor(author: String?, authorId: Int?) {
            this.author = author
            this.authorId = authorId
        }

        fun filterBySeries(series: String?, seriesId: Int?) {
            this.series = series
            this.seriesId = seriesId
        }

        fun filterByGenre(genre: String?, genreId: Int?) {
            this.genre = genre
            this.genreId = genreId
        }

        fun filterByLanguage(language: String?, languageId: Int?) {
            this.language = language
            this.languageId = languageId
        }
    }

    companion object {
        private const val SEARCH_PAGE_SIZE = 20
    }
}