package com.pixelized.server.lwa.services.campaign

import com.pixelized.server.lwa.server.exception.BusinessException
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.campaign.CampaignJson
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

class CampaignService(
    private val store: CampaignStore,
    private val factory: CampaignJsonFactory,
) {
    private val scope = CoroutineScope(Dispatchers.IO + Job())

    private val campaign: Campaign get() = campaignFlow.value

    private val campaignFlow = store.campaignFlow()

    private val campaignJsonFlow: StateFlow<CampaignJson> = campaignFlow
        .map { factory.convertToJson(it) }
        .stateIn(
            scope = scope,
            started = SharingStarted.Eagerly,
            initialValue = factory.convertToJson(campaignFlow.value),
        )

    fun campaign(): Campaign {
        return campaignFlow.value
    }

    fun campaignJson(): CampaignJson {
        return campaignJsonFlow.value
    }

    @Throws
    suspend fun addCharacter(
        characterSheetId: String,
    ) {
        // Check if the character is already in the campaign.
        if (campaign.instances.contains(characterSheetId)) {
            throw BusinessException(message = "Character with id:$characterSheetId is already in the campaign.")
        }
        // Update the corresponding instance
        val characters = campaign.characters.toMutableSet().also {
            it.add(characterSheetId)
        }
        // Save the campaign to the disk (update the flow).
        store.save(
            campaign = campaign.copy(characters = characters)
        )
    }

    @Throws
    suspend fun addNpc(
        characterSheetId: String,
    ) {
        // Check if the character is already in the campaign.
        if (campaign.instances.contains(characterSheetId)) {
            throw BusinessException(message = "Character with id:$characterSheetId is already in the campaign.")
        }
        // Update the corresponding instance
        val characters = campaign.npcs.toMutableSet().also {
            it.add(characterSheetId)
        }
        // Save the campaign to the disk (update the flow).
        store.save(
            campaign = campaign.copy(npcs = characters)
        )
    }

    @Throws
    suspend fun removeCharacter(
        characterSheetId: String,
    ) {
        // Check if the character is in the campaign.
        if (campaign.characters.contains(characterSheetId).not()) {
            throw BusinessException(message = "Character with id:$characterSheetId is not in the party.")
        }
        // Update the corresponding instance
        val characters = campaign.characters.toMutableSet().also {
            it.remove(characterSheetId)
        }
        // Save the campaign to the disk + update the flow.
        store.save(
            campaign = campaign.copy(characters = characters)
        )
    }

    @Throws
    suspend fun removeNpc(
        characterSheetId: String,
    ) {
        // Check if the character is in the campaign.
        if (campaign.npcs.contains(characterSheetId).not()) {
            throw BusinessException(message = "Character with id:$characterSheetId is not in the npcs.")
        }
        // Update the corresponding instance
        val characters = campaign.npcs.toMutableSet().also {
            it.remove(characterSheetId)
        }
        // Save the campaign to the disk + update the flow.
        store.save(
            campaign = campaign.copy(npcs = characters)
        )
    }

    @Throws
    suspend fun setScene(
        scene: Campaign.Scene,
    ) {
        // save the campaign to the disk + update the flow.
        store.save(
            campaign = campaign.copy(scene = scene)
        )
    }

    @Throws
    suspend fun setMap(
        map: Campaign.MiniMap?,
    ) {
        // save the campaign to the disk + update the flow.
        store.save(
            campaign = campaign.copy(map = map)
        )
    }

    // Data manipulation through WebSocket.

    suspend fun updateToggleParty() {
        store.save(
            campaign = campaign.copy(
                options = campaign.options.copy(
                    showParty = campaign.options.showParty.not()
                )
            )
        )
    }

    suspend fun updateToggleNpc() {
        store.save(
            campaign = campaign.copy(
                options = campaign.options.copy(
                    showNpcs = campaign.options.showNpcs.not()
                )
            )
        )
    }
}