package com.pixelized.desktop.lwa.ui.screen.admin.item.edit

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.network.LwaNetworkException
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
import com.pixelized.desktop.lwa.repository.item.ItemRepository
import com.pixelized.desktop.lwa.repository.tag.TagRepository
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldFlow
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMItemEditDestination
import com.pixelized.desktop.lwa.ui.screen.admin.common.tag.GMTagUio
import com.pixelized.shared.lwa.protocol.rest.APIResponse.ErrorCode
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.util.UUID

class GMItemEditViewModel(
    private val itemRepository: ItemRepository,
    private val alterationRepository: AlterationRepository,
    private val tagRepository: TagRepository,
    private val factory: GMItemEditFactory,
    savedStateHandle: SavedStateHandle,
) : ViewModel() {
    private val argument = GMItemEditDestination.Argument(savedStateHandle)

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

    private val _form = MutableStateFlow<GMItemEditForm?>(null)
    val form: StateFlow<GMItemEditPageUio?> = _form.map {
        if (it == null) return@map null
        factory.createPage(
            scope = viewModelScope,
            originId = argument.id,
            form = it,
            alterations = alterationRepository.alterationFlow.value
        )
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.Lazily,
        initialValue = null,
    )

    init {
        viewModelScope.launch {
            _form.value = factory.createForm(
                item = itemRepository.item(itemId = argument.id),
                tags = tagRepository.itemsTags(),
                alterationFlow = alterationRepository.alterationFlow,
            )
        }
    }

    suspend fun save(): Boolean {
        if (!isFormValid()) return false

        val edited = factory.createItem(form = _form.value) ?: return false

        try {
            itemRepository.updateItem(
                item = edited,
                create = argument.id == null,
            )
            return true
        } catch (exception: LwaNetworkException) {
            _form.value?.idFlow?.errorFlow?.value = exception.code == ErrorCode.ItemId
            _form.value?.labelFlow?.errorFlow?.value = exception.code == ErrorCode.ItemName

            val message = ErrorSnackUio.from(exception = exception)
            _error.emit(message)
            return false
        } catch (exception: Exception) {
            val message = ErrorSnackUio.from(exception = exception)
            _error.emit(message)
            return false
        }
    }

    private suspend fun isFormValid(): Boolean {
        var isValid = true

        // check for empty values
        _form.value?.alterationsFlow?.value?.values
            ?.forEach { field ->
                if (field.valueFlow.value.isNullOrBlank()) {
                    field.errorFlow.value = "Empty alteration are not allowed."
                    isValid = false
                }
            }

        // check for duplicate alterations id.
        _form.value?.alterationsFlow?.value?.values
            ?.duplicates { it.valueFlow.value }
            ?.forEach { field ->
                field.errorFlow.value = "Duplicate alteration are not allowed."
                isValid = false
            }

        return isValid
    }

    fun addTag(tag: GMTagUio) {
        _form.value?.tagFlow?.update { tags ->
            tags.toMutableList().also {
                val index = it.indexOf(tag)
                if (index > -1) {
                    it[index] = tag.copy(highlight = tag.highlight.not())
                }
            }
        }
    }

    fun toggleAlteration(
        field: GMAlterationFieldItemUio,
        selected: GMAlterationFieldItemUio.Field?,
    ) {
        _form.value?.alterationsFlow?.update { alterations ->
            alterations.toMutableMap().also {
                it[field.key]?.valueFlow?.value = selected?.id
                it[field.key]?.errorFlow?.value = null
            }
        }
    }

    fun addAlteration() {
        _form.value?.alterationsFlow?.update { alterations ->
            alterations.toMutableMap().also {
                val flow = GMItemEditForm.GMAlterationFieldItemFlow(
                    errorFlow = MutableStateFlow(null),
                    valueFlow = MutableStateFlow(null),
                )
                it[flow.key] = flow
            }
        }
    }

    fun removeAlteration(key: String) {
        _form.value?.alterationsFlow?.update { alterations ->
            alterations.toMutableMap().also {
                it.remove(key)
            }
        }
    }

    data class GMItemEditForm(
        val idFlow: LwaTextFieldFlow,
        val labelFlow: LwaTextFieldFlow,
        val descriptionFlow: LwaTextFieldFlow,
        val imageFlow: LwaTextFieldFlow,
        val thumbnailFlow: LwaTextFieldFlow,
        val stackableFlow: MutableStateFlow<Boolean>,
        val equipableFlow: MutableStateFlow<Boolean>,
        val consumableFlow: MutableStateFlow<Boolean>,
        val alterationsFlow: MutableStateFlow<Map<String, GMAlterationFieldItemFlow>>,
        val tagFlow: MutableStateFlow<List<GMTagUio>>,
    ) {
        data class GMAlterationFieldItemFlow(
            val key : String = "${UUID.randomUUID()}-${System.currentTimeMillis()}",
            val errorFlow: MutableStateFlow<String?>,
            val valueFlow: MutableStateFlow<String?>,
        )
    }

    private inline fun <T, R> Iterable<T>.duplicates(selector: (T) -> R): List<T> {
        val set = HashSet<R>()
        val list = ArrayList<T>()
        for (e in this) {
            val key = selector(e)
            if (!set.add(key)) list.add(e)
        }
        return list
    }
}