package com.pixelized.desktop.lwa.ui.composable.character.item

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository
import com.pixelized.desktop.lwa.repository.item.ItemRepository
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
import com.pixelized.shared.lwa.model.inventory.Inventory
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update

class ItemDetailDialogViewModel(
    private val inventoryRepository: InventoryRepository,
    itemRepository: ItemRepository,
    private val factory: ItemDetailDialogFactory,
) : ViewModel() {

    private val _error = MutableSharedFlow<ErrorSnackUio>()
    val error: SharedFlow<ErrorSnackUio> = _error

    private val selectedItemId = MutableStateFlow<InventoryItemId?>(null)

    @OptIn(ExperimentalCoroutinesApi::class)
    private val selectedInventoryItem: Flow<Inventory.Item?> = selectedItemId
        .flatMapLatest { selectedIds ->
            when (selectedIds?.inventoryId) {
                null -> flowOf(null)
                else -> inventoryRepository
                    .inventoryFlow(characterSheetId = selectedIds.characterSheetId)
                    .mapNotNull { inventory ->
                        inventory.items.firstOrNull { it.inventoryId == selectedIds.inventoryId }
                    }
            }
        }

    val itemDialog = combine(
        itemRepository.itemFlow(),
        selectedInventoryItem,
        selectedItemId,
        transform = { items, selectedInventoryItem, ids ->
            factory.convertToDialogUio(
                characterSheetId = ids?.characterSheetId,
                items = items,
                count = selectedInventoryItem?.count ?: 1f,
                equipped = selectedInventoryItem?.equipped == true,
                inventoryId = ids?.inventoryId,
                itemId = ids?.itemId,
            )
        }
    ).stateIn(
        scope = viewModelScope,
        started = SharingStarted.Lazily,
        initialValue = null
    )

    fun showItemDialog(
        characterSheetId: String,
        inventoryId: String?,
        itemId: String?,
    ) {
        selectedItemId.update {
            InventoryItemId(
                characterSheetId = characterSheetId,
                inventoryId = inventoryId,
                itemId = itemId,
            )
        }
    }

    fun hideItemDialog() {
        selectedItemId.update { null }
    }

    suspend fun onAddInventoryItem(
        dialog: ItemDetailDialogUio,
    ): Boolean {
        try {
            if (dialog.countable?.errorFlow?.value == true) return false
            val quantity = dialog.countable?.valueFlow?.value
            val count = factory.parse(quantity = quantity ?: "")
                ?: quantity?.toFloatOrNull()
                ?: 1f
            // create the inventory item on the server, get the newly create id from that.
            inventoryRepository.createInventoryItem(
                characterSheetId = dialog.characterSheetId,
                itemId = dialog.itemId,
                count = count,
            )
            return true
        } catch (exception: Exception) {
            val message = ErrorSnackUio.from(exception)
            _error.emit(message)
            return false
        }
    }

    suspend fun throwInventoryItem(
        characterSheetId: String,
        inventoryId: String?,
    ): Boolean {
        if (inventoryId == null) return false
        try {
            inventoryRepository.deleteInventoryItem(
                characterSheetId = characterSheetId,
                inventoryId = inventoryId,
            )
            return true
        } catch (exception: Exception) {
            val message = ErrorSnackUio.from(exception)
            _error.emit(message)
            return false
        }
    }

    suspend fun consumeInventoryItem(
        characterSheetId: String,
        inventoryId: String?,
    ): Boolean {
        if (inventoryId == null) return false
        try {
            inventoryRepository.consumeInventoryItem(
                characterSheetId = characterSheetId,
                inventoryId = inventoryId,
            )
            return true
        } catch (exception: Exception) {
            val message = ErrorSnackUio.from(exception)
            _error.emit(message)
            return false
        }
    }

    suspend fun equipInventoryItem(
        characterSheetId: String,
        inventoryId: String?,
    ): Boolean {
        if (inventoryId == null) return false
        try {
            inventoryRepository.equipInventoryItem(
                characterSheetId = characterSheetId,
                inventoryId = inventoryId,
            )
            return true
        } catch (exception: Exception) {
            val message = ErrorSnackUio.from(exception)
            _error.emit(message)
            return false
        }
    }

    suspend fun changeInventoryItemQuantity(
        dialog: ItemDetailDialogUio,
    ): Boolean {
        if (dialog.countable?.errorFlow?.value == true) return false

        val characterSheetId = dialog.characterSheetId
        val inventoryId = dialog.inventoryId ?: return false
        val quantity = dialog.countable?.valueFlow?.value ?: return false

        val count = factory.parse(quantity = quantity)
            ?: quantity.toFloatOrNull()
            ?: return false

        try {
            inventoryRepository.changeInventoryItemCount(
                characterSheetId = characterSheetId,
                inventoryId = inventoryId,
                count = count
            )
            return true
        } catch (exception: Exception) {
            val message = ErrorSnackUio.from(exception)
            _error.emit(message)
            return false
        }
    }

    private data class InventoryItemId(
        val characterSheetId: String,
        val inventoryId: String?,
        val itemId: String?,
    )
}