package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon

import androidx.compose.runtime.Stable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.repository.settings.model.Settings
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRollUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonUio
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.protocol.websocket.RollEvent.Critical
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import java.text.Collator

abstract class CharacterRibbonViewModel(
    private val rollHistoryRepository: RollHistoryRepository,
    private val settingsRepository: SettingsRepository,
    characterRepository: CharacterSheetRepository,
    alterationRepository: AlterationRepository,
    campaignRepository: CampaignRepository,
    private val ribbonFactory: CharacterRibbonFactory,
) : ViewModel() {

    private val rollFlowCache = hashMapOf<String, SharedFlow<CharacterRibbonRollUio?>>()

    abstract fun fetch(
        campaign: Campaign,
        settings: Settings,
    ): Set<String>

    abstract fun hideOverruled(
        campaign: Campaign,
        settings: Settings,
    ): Boolean

    abstract fun enableCharacterSheet(
        settings: Settings,
    ): Boolean

    abstract fun enableCharacterStats(
        settings: Settings,
    ): Boolean

    abstract fun enableCharacterStatus(
        settings: Settings,
    ): Boolean

    /**
     * This flow is a tad complex so there is an explanation of wtf it's about :
     * On a campaign update it go through every element of the abstract [fetch] list and either:
     * - build and flow of an empty list to handle the case where the map is empty.
     * - build a flow of flow bind to each character & alteration to update portrait details.
     * Then sort the result.
     */
    @OptIn(ExperimentalCoroutinesApi::class)
    val characters: StateFlow<List<CharacterRibbonUio>> = combine(
        settingsRepository.settingsFlow(),
        campaignRepository.campaignFlow(),
    ) { settings, campaign -> campaign to settings }
        .distinctUntilChanged()
        .flatMapLatest { (campaign, settings) ->
            val hideOverruled = hideOverruled(campaign, settings)
            val characterSheetIds = fetch(campaign, settings)
            when (characterSheetIds.isEmpty()) {
                true -> flowOf(emptyList())
                else -> combine<CharacterRibbonUio?, List<CharacterRibbonUio>>(
                    flows = characterSheetIds.map { characterSheetId ->
                        combine(
                            characterRepository.characterDetailFlow(characterSheetId = characterSheetId),
                            alterationRepository.activeAlterationsFlow(characterSheetId = characterSheetId),
                            alterationRepository.activeFieldAlterationsFlow(characterSheetId = characterSheetId),
                        ) { sheet, alterations, fieldAlterations ->
                            ribbonFactory.convertToPlayerPortraitUio(
                                characterSheet = sheet,
                                alterations = alterations,
                                fieldAlterations = fieldAlterations,
                                hideOverruled = hideOverruled,
                                enableCharacterSheet = enableCharacterSheet(settings = settings),
                                enableCharacterStats = enableCharacterStats(settings = settings),
                                enableCharacterStatus = enableCharacterStatus(settings = settings),
                            )
                        }
                    },
                    transform = { headers ->
                        headers.mapNotNull { it }
                            .sortedWith(compareBy(Collator.getInstance()) { it.portrait.name })
                            .toList()
                    }
                )
            }
        }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.Eagerly,
            initialValue = emptyList()
        )

    fun characters(index: Int): String? = characters.value
        .getOrNull(index)
        ?.characterSheetId

    @Stable
    fun roll(
        characterSheetId: String,
    ): SharedFlow<CharacterRibbonRollUio?> {
        return rollFlowCache.getOrPut(key = characterSheetId) {
            combine(
                settingsRepository.settingsFlow(),
                rollHistoryRepository.rolls().filter { it.characterSheetId == characterSheetId },
            ) { settings, roll ->
                if (settings.portrait.dynamicDice.not()) return@combine null

                CharacterRibbonRollUio(
                    rollId = roll.uuid,
                    hideDelay = settings.portrait.dynamicDiceDelay,
                    characterSheetId = characterSheetId,
                    value = roll.rollValue,
                    critical = when (roll.critical) {
                        Critical.CRITICAL_SUCCESS -> CharacterRibbonRollUio.Critical.CRITICAL_SUCCESS
                        Critical.SPECIAL_SUCCESS -> CharacterRibbonRollUio.Critical.SPECIAL_SUCCESS
                        Critical.SUCCESS -> CharacterRibbonRollUio.Critical.SUCCESS
                        Critical.FAILURE -> CharacterRibbonRollUio.Critical.FAILURE
                        Critical.CRITICAL_FAILURE -> CharacterRibbonRollUio.Critical.CRITICAL_FAILURE
                        else -> null
                    },
                )
            }.shareIn(
                scope = viewModelScope,
                started = SharingStarted.Eagerly,
            )
        }
    }
}