package com.pixelized.desktop.lwa.repository.alteration

import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository
import com.pixelized.desktop.lwa.repository.item.ItemRepository
import com.pixelized.shared.lwa.model.alteration.Alteration
import com.pixelized.shared.lwa.model.alteration.FieldAlteration
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

class AlterationRepository(
    private val alterationStore: AlterationStore,
    private val inventoryRepository: InventoryRepository,
    private val itemRepository: ItemRepository,
    private val characterRepository: CharacterSheetRepository,
) {
    private val scope = CoroutineScope(Dispatchers.IO + Job())

    val alterationFlow get() = alterationStore.alterationsFlow

    /**
     * This flow build a Map of CharacterSheetId to a list of AlterationId.
     * Map<CharacterSheetId, List<AlterationId>> from the CharacterSheet Alterations.
     * It is used by an other flow to build the FieldAlteration cache.
     */
    @OptIn(ExperimentalCoroutinesApi::class)
    private val charactersAlterationFlow: Flow<Map<String, List<String>>> =
        characterRepository.characterSheetPreviewFlow()
            .flatMapLatest { preview ->
                val characters = preview.map {
                    characterRepository.characterDetailFlow(characterSheetId = it.characterSheetId)
                }
                combine(characters) { sheets: Array<CharacterSheet?> ->
                    sheets
                        .asSequence()
                        .filterNotNull()
                        .associate { sheet -> sheet.id to sheet.alterations }
                }
            }

    /**
     * This flow build a Map of CharacterSheetId to a list of AlterationId.
     * Map<CharacterSheetId, List<AlterationId>> from the character inventory items Alterations.
     * It is used by an other flow to build the FieldAlteration cache.
     */
    @OptIn(ExperimentalCoroutinesApi::class)
    private val inventoriesAlterationFlow: Flow<Map<String, List<String>>> =
        characterRepository.characterSheetPreviewFlow()
            .flatMapLatest { previews ->
                val equippedItems = previews.map {
                    combine(
                        inventoryRepository.equippedItemsFlow(characterSheetId = it.characterSheetId),
                        itemRepository.itemFlow(),
                    ) { equipments, items ->
                        it.characterSheetId to equipments.flatMap { equipment ->
                            items[equipment.itemId]?.alterations ?: emptyList()
                        }
                    }
                }
                combine(equippedItems) { equipments: Array<Pair<String, List<String>>> ->
                    equipments
                        .asSequence()
                        .associate { it.first to it.second }
                }
            }

    /**
     * This flow build a Map of CharacterSheetId to a list of AlterationId.
     * Map<CharacterSheetId, List<AlterationId>> from the character sheet + inventory items Alterations.
     * It is used by an other flow to build the FieldAlteration cache.
     */
    private val activeAlterationIdsFlow: StateFlow<Map<String, List<String>>> = combine(
        charactersAlterationFlow,
        inventoriesAlterationFlow,
    ) { characters, inventories ->
        val characterSheetIds = characters.keys + inventories.keys
        characterSheetIds.associateWith {
            characters.getOrElse(it) { emptyList() } + inventories.getOrElse(it) { emptyList() }
        }
    }.stateIn(
        scope = scope,
        started = SharingStarted.Eagerly,
        initialValue = emptyMap(),
    )

    /**
     * This flow build a Map of CharacterSheetId to a list of Alteration.
     * Map<CharacterSheetId, List<Alteration>> from the character sheet + inventory items Alterations.
     */
    private val activeAlterationsFlow: StateFlow<Map<String, List<Alteration>>> =
        combine(
            characterRepository.characterSheetPreviewFlow(),
            alterationStore.alterationsFlow,
            activeAlterationIdsFlow,
        ) { preview, alterations, activeAlterationIds ->
            preview.map { it.characterSheetId }.associateWith { characterSheetId ->
                activeAlterationIds
                    .getOrElse(characterSheetId) { emptyList() }
                    .mapNotNull { alterations[it] }
            }
        }.stateIn(
            scope = scope,
            started = SharingStarted.Eagerly,
            initialValue = emptyMap(),
        )

    /**
     * This flow build a Map of CharacterSheetId to a Map of AlterationId and FieldAlteration.
     * Map<CharacterSheetId, Map<AlterationId, Alteration>> from the character sheet + inventory items Alterations.
     */
    private val activeFieldAlterationsFlow: StateFlow<Map<String, Map<String, List<FieldAlteration>>>> =
        combine(
            characterRepository.characterSheetPreviewFlow(),
            alterationStore.alterationsFlow,
            activeAlterationIdsFlow,
        ) { previews, alterations, activeAlterationIds ->
            previews.map { it.characterSheetId }.associateWith {
                transformToAlterationFieldMap(
                    alterations = alterations,
                    actives = activeAlterationIds.getOrElse(it) { emptyList() },
                )
            }
        }.stateIn(
            scope = scope,
            started = SharingStarted.Eagerly,
            initialValue = emptyMap(),
        )

    suspend fun updateAlterationFlow() {
        alterationStore.updateAlterationsFlow()
    }

    fun alteration(
        alterationId: String?,
    ): Alteration? {
        return alterationFlow.value[alterationId]
    }

    fun activeFieldAlterations(
        characterSheetId: String,
    ): Map<String, List<FieldAlteration>> {
        return activeFieldAlterationsFlow.value[characterSheetId] ?: emptyMap()
    }

    fun activeFieldAlterationsFlow(
        characterSheetId: String,
    ): Flow<Map<String, List<FieldAlteration>>> {
        return activeFieldAlterationsFlow.map { it[characterSheetId] ?: emptyMap() }
    }

    fun activeAlterations(
        characterSheetId: String?,
    ): List<Alteration> {
        return activeAlterationsFlow.value[characterSheetId] ?: emptyList()
    }

    fun activeAlterationsFlow(
        characterSheetId: String?,
    ): Flow<List<Alteration>> {
        return activeAlterationsFlow.map { it[characterSheetId] ?: emptyList() }
    }

    @Throws
    suspend fun updateAlteration(
        alteration: Alteration,
        create: Boolean,
    ) {
        alterationStore.putAlteration(
            alteration = alteration,
            create = create,
        )
    }

    @Throws
    suspend fun deleteAlteration(
        alterationId: String,
    ) {
        alterationStore.deleteAlteration(alterationId = alterationId)
    }

    private fun transformToAlterationFieldMap(
        alterations: Map<String, Alteration>,
        actives: List<String>,
    ): Map<String, List<FieldAlteration>> {
        val fieldAlterations = hashMapOf<String, MutableList<FieldAlteration>>()
        actives.forEach { alterationId ->
            alterations[alterationId]?.let { alteration ->
                alteration.fields.forEach { field ->
                    fieldAlterations
                        .getOrPut(field.fieldId) { mutableListOf() }
                        .add(
                            FieldAlteration(
                                alterationId = alteration.id,
                                metadata = alteration.metadata,
                                expression = field.expression,
                            )
                        )
                }
            }
        }
        return fieldAlterations
    }
}