"""Planner constants. Tune here without touching algorithm code."""
from __future__ import annotations

from dataclasses import dataclass
from typing import Any


@dataclass(frozen=True)
class PlannerConfig:
    # Hard constraints
    recency_weeks: int = 4              # constraint #3: no repeat within N weeks
    calorie_tolerance_pct: int = 20     # constraint #4: ±X% of family.calorie_target
    max_total_minutes: int = 45         # constraint #5: prep + cook
    max_meal_cost: float = 50.00        # constraint #6: dollars per serving (dimensionless proxy — unit conversion not yet implemented)

    # Scoring weights (must sum to 1.0)
    w_savings: float = 0.30
    w_coverage: float = 0.25
    w_pantry: float = 0.10
    w_time: float = 0.15
    w_recency: float = 0.20

    # Time bonus boundaries
    time_ideal_minutes: int = 25        # full bonus at <= this
    time_full_minutes: int = 45         # zero bonus at this; matches max_total_minutes

    # Recency bonus boundary
    recency_full_weeks: int = 12        # full bonus when last cooked >= this many weeks ago

    # Set selection
    top_k: int = 10                     # how many feasible recipes to enumerate over
    set_size: int = 3                   # 3 dinners/week
    p_protein: float = 0.15             # diversity penalty per shared-protein pair
    p_cuisine: float = 0.10             # diversity penalty per shared-cuisine pair

    def validate(self) -> None:
        total = self.w_savings + self.w_coverage + self.w_pantry + self.w_time + self.w_recency
        if abs(total - 1.0) > 1e-6:
            raise ValueError(f"weights must sum to 1.0, got {total}")

    def merge(self, overrides: "dict[str, Any]") -> "PlannerConfig":
        """Return a new PlannerConfig with overrides applied."""
        current = {
            "recency_weeks": self.recency_weeks,
            "calorie_tolerance_pct": self.calorie_tolerance_pct,
            "max_total_minutes": self.max_total_minutes,
            "max_meal_cost": self.max_meal_cost,
            "w_savings": self.w_savings,
            "w_coverage": self.w_coverage,
            "w_pantry": self.w_pantry,
            "w_time": self.w_time,
            "w_recency": self.w_recency,
            "time_ideal_minutes": self.time_ideal_minutes,
            "time_full_minutes": self.time_full_minutes,
            "recency_full_weeks": self.recency_full_weeks,
            "top_k": self.top_k,
            "set_size": self.set_size,
            "p_protein": self.p_protein,
            "p_cuisine": self.p_cuisine,
        }
        for key, value in overrides.items():
            if key in current:
                current[key] = value
        inst = PlannerConfig(**current)
        inst.validate()
        return inst

    def to_dict(self) -> dict[str, Any]:
        return {
            "recency_weeks": self.recency_weeks,
            "calorie_tolerance_pct": self.calorie_tolerance_pct,
            "max_total_minutes": self.max_total_minutes,
            "max_meal_cost": self.max_meal_cost,
            "w_savings": self.w_savings,
            "w_coverage": self.w_coverage,
            "w_pantry": self.w_pantry,
            "w_time": self.w_time,
            "w_recency": self.w_recency,
            "time_ideal_minutes": self.time_ideal_minutes,
            "time_full_minutes": self.time_full_minutes,
            "recency_full_weeks": self.recency_full_weeks,
            "top_k": self.top_k,
            "set_size": self.set_size,
            "p_protein": self.p_protein,
            "p_cuisine": self.p_cuisine,
        }


DEFAULT = PlannerConfig()
DEFAULT.validate()
