package com.pixelized.desktop.lwa.repository.map

import com.pixelized.desktop.lwa.network.LwaClient
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.shared.lwa.model.map.factory.MapJsonFactory
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import com.pixelized.shared.lwa.model.map.Map as MiniMap

class MapStore(
    private val network: NetworkRepository,
    private val factory: MapJsonFactory,
    private val client: LwaClient,
) {
    private val mapsFlow = MutableStateFlow<Map<String, MiniMap>>(emptyMap())

    init {
        val scope = CoroutineScope(Dispatchers.IO + Job())
        // data update through WebSocket.
        scope.launch {
            network.data.collect(::handleMessage)
        }
    }

    fun mapsFlow(): StateFlow<Map<String, MiniMap>> = mapsFlow

    suspend fun updateMapFlow() {
        val map = try {
            getMaps()
        } catch (exception: Exception) {
            println(exception.message) // TODO proper exception handling
            null
        }
        // guard case if getItem failed
        if (map == null) return
        // update the flow with the new item.
        mapsFlow.update { map }
    }

    @Throws
    private suspend fun getMaps(): Map<String, MiniMap> {
        val request = client.getMaps()
        return when (request.success) {
            true -> request.data
                ?.map { factory.convertFromJson(it) }
                ?.associateBy { it.id }
                ?: emptyMap()

            else -> LwaClient.error(error = request)
        }
    }

    @Throws
    private suspend fun getMap(id: String): MiniMap? {
        val request = client.getMap(mapId = id)
        return when (request.success) {
            true -> request.data?.let { factory.convertFromJson(it) }
            else -> LwaClient.error(error = request)
        }
    }

    @Throws
    suspend fun putMap(
        map: MiniMap,
        create: Boolean,
    ) {
        val request = client.putMap(
            mapJson = factory.convertToJson(item = map),
            create = create,
        )
        if (request.success.not()) {
            LwaClient.error(error = request)
        }
    }

    @Throws
    suspend fun deleteMap(
        mapId: String,
    ) {
        val request = client.deleteMap(
            mapId = mapId,
        )
        if (request.success.not()) {
            LwaClient.error(error = request)
        }
    }

    // region: WebSocket & data update.

    private suspend fun handleMessage(message: SocketMessage) {
        when (message) {
            is ApiSynchronisation.MapApiSynchronisation -> when (message) {
                is ApiSynchronisation.MapUpdate -> mapsFlow.update { maps ->
                    maps.toMutableMap().also {
                        when (val map = getMap(id = message.id)) {
                            null -> it.remove(key = message.id)
                            else -> it[message.id] = map
                        }
                    }
                }

                is ApiSynchronisation.MapDelete -> mapsFlow.update { maps ->
                    maps.toMutableMap().also { it.remove(message.id) }
                }
            }

            else -> Unit
        }
    }

    // endregion
}