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

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterCharacterEditPage
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult.BoundedRollResult.Difficulty
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult.BoundedRollResult.Result
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.CharacterDetailInventoryFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.CharacterDetailInventoryUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetUio
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

class CharacterDetailPanelViewModel(
    private val characterSheetRepository: CharacterSheetRepository,
    private val characterHeaderFactory: CharacterDetailHeaderFactory,
    private val characterSheetFactory: CharacterDetailSheetFactory,
    private val characterInventoryFactory: CharacterDetailInventoryFactory,
    private val network: NetworkRepository,
) : ViewModel() {

    private val characterSheetPanelFlow = MutableStateFlow<CharacterSheetPanel?>(null)
    private val addItemAction = characterInventoryFactory
        .addItemActionFlow()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.Lazily,
            initialValue = false,
        )

    val detail: StateFlow<CharacterDetailPanelUio> = characterSheetPanelFlow
        .map { characterSheetPanel ->
            val (characterSheetId, panel) = characterSheetPanel ?: return@map emptyPanel()
            return@map CharacterDetailPanelUio(
                characterSheetId = characterSheetId,
                panel = panel,
                header = characterHeaderFactory.convertToCharacterDetailHeaderUioFlow(
                    characterSheetId = characterSheetId,
                    panel = panel,
                    scope = viewModelScope,
                    initialValue = ::emptyHeader,
                ),
                sheet = characterSheetFactory.convertToCharacterDetailSheetUioFlow(
                    characterSheetId = characterSheetId,
                    scope = viewModelScope,
                    initialValue = ::emptySheet,
                ),
                inventory = characterInventoryFactory.convertToCharacterInventoryUioFlow(
                    characterSheetId = characterSheetId,
                    scope = viewModelScope,
                    addItemAction = addItemAction,
                    initialValue = ::emptyInventory,
                ),
            )
        }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.Eagerly,
            initialValue = emptyPanel(),
        )

    suspend fun showCharacter(
        characterSheetId: String,
        panel: DetailPanelUio,
    ) {
        if (characterSheetRepository.characterDetail(characterSheetId = characterSheetId) == null) {
            characterSheetRepository.updateCharacterSheet(characterSheetId = characterSheetId)
        }
        characterSheetPanelFlow.value = CharacterSheetPanel(
            characterSheetId = characterSheetId,
            panel = panel,
        )
    }

    suspend fun editCharacter(
        characterSheetId: String,
        screens: NavHostController,
    ) {
        if (characterSheetRepository.characterDetail(characterSheetId = characterSheetId) == null) {
            characterSheetRepository.updateCharacterSheet(characterSheetId = characterSheetId)
        }
        screens.navigateToGameMasterCharacterEditPage(
            characterSheetId = characterSheetId,
        )
    }

    fun hideCharacter() {
        characterSheetPanelFlow.value = null
    }

    suspend fun onSkillUse(
        skillId: String,
        result: RollResult,
    ) {
        val (characterSheetId, _) = characterSheetPanelFlow.value ?: return
        // check if the RollResult is a BoundedRollResult. can work with other roll result.
        val roll = result as? RollResult.BoundedRollResult ?: return
        // check if the roll is a success with some challenge.
        val difficulty = roll.difficulty != Difficulty.Easy
        val success = roll.result.let {
            it == Result.Success || it == Result.SpecialSuccess || it == Result.CriticalSuccess
        }
        if (!success || !difficulty) return
        // Get the skill that have been rolled.
        val skill = characterSheetRepository
            .characterDetail(characterSheetId = characterSheetId)
            ?.skill(id = skillId)
        // Check the skill if needed.
        if (skill?.used == false) {
            network.share(
                message = CharacterSheetEvent.UpdateSkillUsageEvent(
                    timestamp = System.currentTimeMillis(),
                    characterSheetId = characterSheetId,
                    skillId = skillId,
                    used = true,
                )
            )
        }
    }

    suspend fun onSkillUse(
        skillId: String,
        used: Boolean,
    ) {
        val (characterSheetId, _) = characterSheetPanelFlow.value ?: return
        network.share(
            message = CharacterSheetEvent.UpdateSkillUsageEvent(
                timestamp = System.currentTimeMillis(),
                characterSheetId = characterSheetId,
                skillId = skillId,
                used = used.not(),
            )
        )
    }

    private fun emptyPanel() = CharacterDetailPanelUio(
        characterSheetId = null,
        panel = DetailPanelUio.Sheet,
        header = MutableStateFlow(value = emptyHeader()),
        sheet = MutableStateFlow(value = emptySheet()),
        inventory = MutableStateFlow(value = emptyInventory()),
    )

    private fun emptyHeader(): CharacterDetailHeaderUio? = null

    private fun emptySheet(): CharacterDetailSheetUio? = null

    private fun emptyInventory(): CharacterDetailInventoryUio? = null

    data class CharacterSheetPanel(
        val characterSheetId: String,
        val panel: DetailPanelUio,
    )
}