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

import androidx.lifecycle.ViewModel
import dev.ulfrx.recipe.navigation.MealPlanEditorSource
import dev.ulfrx.recipe.ui.components.calendar.todayInSystemTz
import dev.ulfrx.recipe.ui.components.recipe.MealSlot
import dev.ulfrx.recipe.ui.components.recipe.RecipeUi
import dev.ulfrx.recipe.ui.components.recipe.options
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.datetime.LocalDate
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid

class MealPlanEditorViewModel(
    source: MealPlanEditorSource,
    recipeProvider: (String) -> RecipeUi?,
    plannedMealProvider: (String) -> PlannedMealUi?,
) : ViewModel() {
    private val _state = MutableStateFlow(loadInitial(source, recipeProvider, plannedMealProvider))
    val state: StateFlow<MealPlanEditorState> = _state.asStateFlow()

    fun confirm(): PlannedMealUi? {
        val editing = _state.value as? MealPlanEditorState.Editing ?: return null
        return PlannedMealUi(
            id = editing.id,
            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 AddableIngredientUi.toAdded() =
        AddedIngredientUi(
            ingredientId = ingredientId,
            name = name,
            amount = defaultAmount,
            unit = defaultUnit,
        )
}

@OptIn(ExperimentalUuidApi::class)
private fun loadInitial(
    source: MealPlanEditorSource,
    recipeProvider: (String) -> RecipeUi?,
    plannedMealProvider: (String) -> PlannedMealUi?,
): MealPlanEditorState =
    when (source) {
        is MealPlanEditorSource.NewFromRecipe -> {
            val recipe = recipeProvider(source.recipeId)
            if (recipe == null) {
                MealPlanEditorState.NotFound
            } else {
                MealPlanEditorState.Editing(
                    id = "plan_${Uuid.random()}",
                    recipe = recipe,
                    selectedDate = todayInSystemTz(),
                    selectedSlot = recipe.allowedSlots.firstOrNull() ?: MealSlot.entries.first(),
                    servings = source.initialServings.coerceIn(MIN_PLAN_SERVINGS, MAX_PLAN_SERVINGS),
                    substitutions = source.initialSubstitutions.filterValid(recipe),
                )
            }
        }

        is MealPlanEditorSource.EditExistingPlan -> {
            val planned = plannedMealProvider(source.plannedMealId)
            val recipe = planned?.let { recipeProvider(it.recipeId) }
            if (planned == null || recipe == null) {
                MealPlanEditorState.NotFound
            } else {
                MealPlanEditorState.Editing(
                    id = planned.id,
                    recipe = recipe,
                    selectedDate = planned.date,
                    selectedSlot = planned.slot,
                    servings = planned.servings.coerceIn(MIN_PLAN_SERVINGS, MAX_PLAN_SERVINGS),
                    substitutions = planned.substitutions.filterValid(recipe),
                    excludedIngredients = planned.excludedIngredients,
                    addedIngredients = planned.addedIngredients,
                )
            }
        }
    }

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