package dev.ulfrx.recipe.shared.domain.model

import kotlinx.datetime.LocalDate
import kotlinx.serialization.Serializable

/**
 * One ingredient line on a planned meal. Self-contained — substitutions,
 * exclusions, amount overrides, and product picks are not modeled as
 * overlays on the recipe; they're just whatever ends up in this list.
 *
 * A swap (butter → margarine) is just a `PlanIngredient` with the swapped
 * `ingredientId`. A removal is absence from the list. An amount tweak is
 * the [quantity] field. A pinned brand is [productPick].
 */
@Serializable
public data class PlanIngredient(
    public val ingredientId: IngredientId,
    public val quantity: Quantity,
    public val productPick: ProductId? = null,
)

/**
 * One planned meal: a recipe at a date/slot pair, with a fully-materialized
 * snapshot of the ingredients as the user planned them. Identity is the UUID
 * [PlanEntryId] — never the composite `(date, slot)` — so concurrent edits
 * across devices can survive a delete + recreate race without colliding on a
 * natural key (PLAN-14).
 *
 * [recipeId] is provenance only: it powers the "open recipe" link, the "cook
 * again" action, and analytics. The recipe's current ingredient list is not
 * consulted at read time — [ingredients] is the source of truth for this
 * meal. This way, editing a recipe never mutates historic plan entries
 * (load-bearing once consumption tracking lands in a later phase).
 *
 * "Skipped slot" (PLAN-12) is modeled as absence — no [PlanEntry] row exists
 * for that `(date, slotId)`. No sentinel field, no sealed sibling.
 */
@Serializable
public data class PlanEntry(
    public val id: PlanEntryId,
    public val sync: SyncMeta,
    public val date: LocalDate,
    public val slotId: MealSlotId,
    public val recipeId: RecipeId,
    public val servings: Double,
    public val ingredients: List<PlanIngredient>,
)
