"""Versioned photographic-convention policy, without quality prediction."""

from __future__ import annotations

import math
from dataclasses import asdict, dataclass
from enum import Enum
from typing import Any

from mountain_twin.rci.contracts import ThresholdOrigin, ThresholdProvenance


class PhotographicConvention(str, Enum):
    """Named conventions, distinct from astronomical or terrain states."""

    GOLDEN_HOUR = "GOLDEN_HOUR"
    BLUE_HOUR = "BLUE_HOUR"


class ConventionSupportState(str, Enum):
    """Whether v0.1 has an accepted rule for a convention."""

    SUPPORTED = "SUPPORTED"
    NOT_CONFIGURED = "NOT_CONFIGURED"


@dataclass(frozen=True)
class GoldenHourPolicy:
    """The repository's existing geometric solar-center convention."""

    convention: PhotographicConvention
    lower_elevation_deg: float
    upper_elevation_deg: float
    lower_inclusive: bool
    upper_inclusive: bool
    solar_elevation_semantics: str
    support_state: ConventionSupportState
    threshold_provenance: ThresholdProvenance

    def __post_init__(self) -> None:
        if self.convention is not PhotographicConvention.GOLDEN_HOUR:
            raise ValueError("golden-hour policy requires the GOLDEN_HOUR convention")
        if not all(math.isfinite(value) for value in (self.lower_elevation_deg, self.upper_elevation_deg)):
            raise ValueError("golden-hour bounds must be finite")
        if self.lower_elevation_deg > self.upper_elevation_deg:
            raise ValueError("golden-hour lower bound must not exceed upper bound")
        if self.support_state is not ConventionSupportState.SUPPORTED:
            raise ValueError("golden-hour policy must be supported")

    def contains(self, solar_elevation_deg: float) -> bool:
        """Return convention membership without adding a numerical tolerance."""
        if not math.isfinite(solar_elevation_deg):
            raise ValueError("solar elevation must be finite")
        lower = (
            solar_elevation_deg >= self.lower_elevation_deg
            if self.lower_inclusive
            else solar_elevation_deg > self.lower_elevation_deg
        )
        upper = (
            solar_elevation_deg <= self.upper_elevation_deg
            if self.upper_inclusive
            else solar_elevation_deg < self.upper_elevation_deg
        )
        return lower and upper

    def to_dict(self) -> dict[str, Any]:
        return _json_value(asdict(self))


@dataclass(frozen=True)
class BlueHourPolicy:
    """A versioned geometric solar-center photographic convention."""

    convention: PhotographicConvention
    lower_elevation_deg: float
    upper_elevation_deg: float
    lower_inclusive: bool
    upper_inclusive: bool
    solar_elevation_semantics: str
    support_state: ConventionSupportState
    threshold_provenance: ThresholdProvenance

    def __post_init__(self) -> None:
        if self.convention is not PhotographicConvention.BLUE_HOUR:
            raise ValueError("blue-hour policy requires the BLUE_HOUR convention")
        if not all(math.isfinite(value) for value in (self.lower_elevation_deg, self.upper_elevation_deg)):
            raise ValueError("blue-hour bounds must be finite")
        if self.lower_elevation_deg > self.upper_elevation_deg:
            raise ValueError("blue-hour lower bound must not exceed upper bound")
        if self.support_state is not ConventionSupportState.SUPPORTED:
            raise ValueError("blue-hour policy must be supported")

    def contains(self, solar_elevation_deg: float) -> bool:
        """Return convention membership without adding a numerical tolerance."""
        if not math.isfinite(solar_elevation_deg):
            raise ValueError("solar elevation must be finite")
        lower = (
            solar_elevation_deg >= self.lower_elevation_deg
            if self.lower_inclusive
            else solar_elevation_deg > self.lower_elevation_deg
        )
        upper = (
            solar_elevation_deg <= self.upper_elevation_deg
            if self.upper_inclusive
            else solar_elevation_deg < self.upper_elevation_deg
        )
        return lower and upper

    def to_dict(self) -> dict[str, Any]:
        return _json_value(asdict(self))


@dataclass(frozen=True)
class PhotographerPolicy:
    """The small, versioned policy surface used by Photographer v0.1."""

    policy_id: str
    version: str
    golden_hour: GoldenHourPolicy
    blue_hour: BlueHourPolicy

    def __post_init__(self) -> None:
        if not self.policy_id or not self.version:
            raise ValueError("photographer policy requires ID and version")

    def to_dict(self) -> dict[str, Any]:
        return _json_value(asdict(self))


PHOTOGRAPHER_POLICY_V0_1 = PhotographerPolicy(
    policy_id="photographer_science_policy_v0_1",
    version="v0_1",
    golden_hour=GoldenHourPolicy(
        convention=PhotographicConvention.GOLDEN_HOUR,
        lower_elevation_deg=-4.0,
        upper_elevation_deg=6.0,
        lower_inclusive=True,
        upper_inclusive=True,
        solar_elevation_semantics="GEOMETRIC_SOLAR_CENTER_ELEVATION_DEG",
        support_state=ConventionSupportState.SUPPORTED,
        threshold_provenance=ThresholdProvenance(
            origin=ThresholdOrigin.MT_RND_POLICY,
            reference_id="MT_LEGACY_GOLDEN_HOUR_ELEVATION_V0_1",
            version="v0_1",
            note=(
                "Repository legacy convention; no cited scientific reference standard is "
                "asserted."
            ),
        ),
    ),
    blue_hour=BlueHourPolicy(
        convention=PhotographicConvention.BLUE_HOUR,
        lower_elevation_deg=-6.0,
        upper_elevation_deg=-4.0,
        lower_inclusive=True,
        upper_inclusive=True,
        solar_elevation_semantics="GEOMETRIC_SOLAR_CENTER_ELEVATION_DEG",
        support_state=ConventionSupportState.SUPPORTED,
        threshold_provenance=ThresholdProvenance(
            origin=ThresholdOrigin.MT_RND_POLICY,
            reference_id="MT_BLUE_HOUR_ELEVATION_V0_1",
            version="v0_1",
            note=(
                "Versioned photographic convention compatible with Mountain Twin's "
                "legacy Golden Hour convention; not an astronomical standard."
            ),
        ),
    ),
)


def _json_value(value: Any) -> Any:
    if isinstance(value, Enum):
        return value.value
    if isinstance(value, dict):
        return {str(key): _json_value(item) for key, item in value.items()}
    if isinstance(value, (tuple, list)):
        return [_json_value(item) for item in value]
    return value
