package dev.ulfrx.recipe.ui.screens.mealplaneditor

import androidx.lifecycle.ViewModel
import dev.ulfrx.recipe.ui.components.calendar.todayInSystemTz
import dev.ulfrx.recipe.ui.components.recipe.MealSlot
import dev.ulfrx.recipe.ui.components.recipe.options
import dev.ulfrx.recipe.ui.screens.recipedetail.RecipeDetailUi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.datetime.LocalDate

class MealPlanEditorViewModel : ViewModel() {
    private val _state = MutableStateFlow<MealPlanEditorState>(MealPlanEditorState.Hidden)
    val state: StateFlow<MealPlanEditorState> = _state.asStateFlow()

    fun open(
        recipe: RecipeDetailUi,
        initialSubstitutions: Map<String, String> = emptyMap(),
        initialServings: Int = MIN_PLAN_SERVINGS,
        initialDate: LocalDate = todayInSystemTz(),
    ) {
        val slot = recipe.allowedSlots.firstOrNull() ?: MealSlot.entries.first()
        _state.value =
            MealPlanEditorState.Editing(
                recipe = recipe,
                selectedDate = initialDate,
                selectedSlot = slot,
                servings = initialServings.coerceIn(MIN_PLAN_SERVINGS, MAX_PLAN_SERVINGS),
                substitutions = initialSubstitutions.filterValid(recipe),
            )
    }

    fun close() {
        _state.value = MealPlanEditorState.Hidden
    }

    fun confirm(): PlannedMealUi? {
        val editing = _state.value as? MealPlanEditorState.Editing ?: return null
        _state.value = MealPlanEditorState.Hidden
        return PlannedMealUi(
            recipeId = editing.recipe.id,
            date = editing.selectedDate,
            slot = editing.selectedSlot,
            servings = editing.servings,
            substitutions = editing.substitutions,
            excludedIngredients = editing.excludedIngredients,
            addedIngredients = editing.addedIngredients,
        )
    }

    fun selectDate(date: LocalDate) =
        updateEditing { it.copy(selectedDate = date) }

    fun setCalendarExpanded(expanded: Boolean) =
        updateEditing { it.copy(calendarExpanded = expanded) }

    fun selectSlot(slot: MealSlot) =
        updateEditing {
            if (slot in it.recipe.allowedSlots) it.copy(selectedSlot = slot) else it
        }

    fun setServings(value: Int) =
        updateEditing { it.copy(servings = value.coerceIn(MIN_PLAN_SERVINGS, MAX_PLAN_SERVINGS)) }

    fun selectSubstitution(
        slotId: String,
        optionId: String,
    ) = updateEditing { editing ->
        val slot = editing.recipe.ingredients.firstOrNull { it.id == slotId } ?: return@updateEditing editing
        if (slot.options.none { it.id == optionId }) return@updateEditing editing

        val substitutions =
            if (optionId == slot.default.id) {
                editing.substitutions - slotId
            } else {
                editing.substitutions + (slotId to optionId)
            }
        editing.copy(substitutions = substitutions)
    }

    fun removeRecipeIngredient(slotId: String) =
        updateEditing { it.copy(excludedIngredients = it.excludedIngredients + slotId) }

    fun restoreRemovedIngredients() =
        updateEditing { it.copy(excludedIngredients = emptySet()) }

    fun addIngredient(ingredient: AddableIngredientUi) =
        updateEditing { editing ->
            if (editing.addedIngredients.any { it.ingredientId == ingredient.ingredientId }) {
                editing
            } else {
                editing.copy(addedIngredients = editing.addedIngredients + ingredient.toAdded())
            }
        }

    fun removeAddedIngredient(ingredientId: String) =
        updateEditing { it.copy(addedIngredients = it.addedIngredients.filterNot { added -> added.ingredientId == ingredientId }) }

    private inline fun updateEditing(crossinline transform: (MealPlanEditorState.Editing) -> MealPlanEditorState.Editing) {
        _state.update { current ->
            if (current is MealPlanEditorState.Editing) transform(current) else current
        }
    }

    private fun Map<String, String>.filterValid(recipe: RecipeDetailUi): Map<String, String> =
        filter { (slotId, optionId) ->
            val slot = recipe.ingredients.firstOrNull { it.id == slotId }
            slot != null && slot.options.any { it.id == optionId }
        }

    private fun AddableIngredientUi.toAdded() =
        AddedIngredientUi(
            ingredientId = ingredientId,
            name = name,
            amount = defaultAmount,
            unit = defaultUnit,
        )
}
