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

import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.desktop.lwa.ui.theme.lwa
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.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
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 rolls = hashMapOf<String, MutableState<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

    @Composable
    @Stable
    fun roll(
        characterSheetId: String,
    ): State<CharacterRibbonRollUio?> {
        val colorScheme = MaterialTheme.lwa.colorScheme
        val state = remember(characterSheetId) {
            rolls.getOrPut(characterSheetId) { mutableStateOf(null) }
        }

        LaunchedEffect(characterSheetId) {
            combine(
                settingsRepository.settingsFlow(),
                rollHistoryRepository.rolls(),
            ) { settings, roll ->
                if (settings.portrait.dynamicDice && characterSheetId == roll.characterSheetId) {
                    state.value = CharacterRibbonRollUio(
                        rollId = roll.uuid,
                        hideDelay = settings.portrait.dynamicDiceDelay,
                        characterSheetId = characterSheetId,
                        value = roll.rollValue,
                        tint = when (roll.critical) {
                            Critical.CRITICAL_SUCCESS -> colorScheme.portrait.criticalSuccess
                            Critical.SPECIAL_SUCCESS -> colorScheme.portrait.spacialSuccess
                            Critical.SUCCESS -> colorScheme.portrait.success
                            Critical.FAILURE -> colorScheme.portrait.failure
                            Critical.CRITICAL_FAILURE -> colorScheme.portrait.criticalFailure
                            null -> colorScheme.portrait.default
                        },
                    )
                }
            }.launchIn(this)
        }

        return state
    }
}