"""Spec-backed runtime classes for NACA 4-digit airfoils."""

from __future__ import annotations

from copy import deepcopy
from typing import Literal, Self, cast

from .airfoil import OrthogonalAirfoil
from .airfoil_schema import Naca4AirfoilParamsSpec, Naca4AirfoilSpec
from .naca4_camber import Naca4DigitCamber
from .naca45_thickness import (
    Naca45DigitThicknessClassic,
    Naca45DigitThicknessParams,
)

NACA4_CODE_LENGTH = 4
NACA4_MAX_CAMBER_LIMIT = 0.1
NACA4_MAX_CAMBER_LOCATION_LIMIT = 0.9
NACA4_MAX_THICKNESS_LIMIT = 0.4


def _validate_naca4_designation(designation: str) -> None:
    """
    Validate a classic 4-digit NACA designation string.

    Parameters
    ----------
    designation : str
        Four-digit NACA designation string.

    Raises
    ------
    ValueError
        If ``designation`` is not exactly four decimal digits.
    """
    if len(designation) != NACA4_CODE_LENGTH or not designation.isdigit():
        raise ValueError("NACA 4-digit designations must be a 4-digit string.")


def _validate_naca4_params(params: Naca4AirfoilParamsSpec) -> None:
    """
    Validate the explicit NACA 4-digit parameter specification.

    Parameters
    ----------
    params : Naca4AirfoilParamsSpec
        Explicit NACA 4-digit parameter specification to validate.

    Raises
    ------
    ValueError
        If any parameter violates the supported NACA 4-digit bounds or
        consistency rules.
    """
    if not 0.0 <= params.m < NACA4_MAX_CAMBER_LIMIT:
        raise ValueError("NACA 4-digit parameter m must satisfy 0 <= m < 0.1.")
    if not 0.0 <= params.p <= NACA4_MAX_CAMBER_LOCATION_LIMIT:
        raise ValueError("NACA 4-digit parameter p must satisfy 0 <= p <= 0.9.")
    if not 0.0 <= params.t <= NACA4_MAX_THICKNESS_LIMIT:
        raise ValueError("NACA 4-digit parameter t must satisfy 0 <= t <= 0.4.")
    if params.m == 0.0 and params.p != 0.0:
        raise ValueError("NACA 4-digit parameter p must be 0 when m is 0.")
    if params.m > 0.0 and params.p <= 0.0:
        raise ValueError(
            "NACA 4-digit parameter p must be positive when m > 0."
        )


def _copy_naca4_spec(spec: Naca4AirfoilSpec) -> Naca4AirfoilSpec:
    """
    Return a defensive copy of a NACA 4-digit spec.

    Parameters
    ----------
    spec : Naca4AirfoilSpec
        Source specification to copy.

    Returns
    -------
    Naca4AirfoilSpec
        Deep copy of ``spec``.
    """
    return deepcopy(spec)


class Naca4AirfoilClassic(OrthogonalAirfoil):
    """
    Classic NACA 4-digit airfoil built from a designation.

    Parameters
    ----------
    spec : Naca4AirfoilSpec
        Serialized specification containing a classic 4-digit designation.

    Raises
    ------
    ValueError
        If ``spec`` does not contain a valid designation-only definition.
    """

    def __init__(self, spec: Naca4AirfoilSpec) -> None:
        """
        Build a classic NACA 4-digit airfoil from its source spec.

        Parameters
        ----------
        spec : Naca4AirfoilSpec
            Serialized specification containing a classic 4-digit
            designation.

        Raises
        ------
        ValueError
            If ``spec`` does not contain a valid designation-only
            definition.
        """
        if spec.designation is None or spec.params is not None:
            raise ValueError(
                "Classic NACA 4-digit airfoils require designation-only specs."
            )
        _validate_naca4_designation(spec.designation)
        camber = Naca4DigitCamber(
            mci=float(spec.designation[0]),
            lci=float(spec.designation[1]),
        )
        thickness = Naca45DigitThicknessClassic(
            mti=float(spec.designation[2:4])
        )
        super().__init__(camber=camber, thickness=thickness)
        self._spec = _copy_naca4_spec(spec)

    @classmethod
    def from_designation(cls, designation: str) -> Self:
        """
        Build a classic NACA 4-digit airfoil from a designation.

        Parameters
        ----------
        designation : str
            Four-digit NACA designation such as ``"2412"``.

        Returns
        -------
        Self
            Runtime airfoil built from ``designation``.

        Raises
        ------
        ValueError
            If ``designation`` is not a valid 4-digit NACA code.
        """
        return cls(Naca4AirfoilSpec(designation=designation))

    @property
    def spec(self) -> Naca4AirfoilSpec:
        """
        Return the source spec for this airfoil.

        Returns
        -------
        Naca4AirfoilSpec
            Defensive copy of the serialized source spec.
        """
        return _copy_naca4_spec(self._spec)

    @property
    def camber(self) -> Naca4DigitCamber:
        """Return the concrete NACA 4-digit camber model."""
        return cast(Naca4DigitCamber, self._camber)

    @property
    def thickness(self) -> Naca45DigitThicknessClassic:
        """Return the concrete classic NACA thickness model."""
        return cast(Naca45DigitThicknessClassic, self._thickness)

    def to_spec(self) -> Naca4AirfoilSpec:
        """
        Return the schema definition needed to recreate this airfoil.

        Returns
        -------
        Naca4AirfoilSpec
            Serialized source spec for this airfoil.
        """
        return self.spec


class Naca4AirfoilParams(OrthogonalAirfoil):
    """
    Parametric NACA 4-digit airfoil built from explicit parameters.

    Parameters
    ----------
    spec : Naca4AirfoilSpec
        Serialized specification containing explicit 4-digit parameters.

    Raises
    ------
    ValueError
        If ``spec`` does not contain a valid params-only definition.
    """

    def __init__(self, spec: Naca4AirfoilSpec) -> None:
        """
        Build a parametric NACA 4-digit airfoil from its source spec.

        Parameters
        ----------
        spec : Naca4AirfoilSpec
            Serialized specification containing explicit 4-digit
            parameters.

        Raises
        ------
        ValueError
            If ``spec`` does not contain a valid params-only definition.
        """
        if spec.designation is not None or spec.params is None:
            raise ValueError(
                "Parametric NACA 4-digit airfoils require params-only specs."
            )
        _validate_naca4_params(spec.params)
        camber = Naca4DigitCamber(
            mci=100.0 * spec.params.m,
            lci=10.0 * spec.params.p,
        )
        thickness = Naca45DigitThicknessParams(
            mti=100.0 * spec.params.t,
            closed_te=spec.params.trailing_edge == "sharp",
            use_radius=spec.params.leading_edge_radius == "exact",
        )
        super().__init__(camber=camber, thickness=thickness)
        self._spec = _copy_naca4_spec(spec)

    @property
    def camber(self) -> Naca4DigitCamber:
        """Return the concrete NACA 4-digit camber model."""
        return cast(Naca4DigitCamber, self._camber)

    @property
    def thickness(self) -> Naca45DigitThicknessParams:
        """Return the concrete parametric NACA thickness model."""
        return cast(Naca45DigitThicknessParams, self._thickness)

    @classmethod
    def from_params(
        cls,
        *,
        m: float,
        p: float,
        t: float,
        trailing_edge: Literal["standard", "sharp"] = "standard",
        leading_edge_radius: Literal["standard", "exact"] = "standard",
    ) -> Self:
        """
        Build a parametric NACA 4-digit airfoil from explicit params.

        Parameters
        ----------
        m : float
            Maximum camber as a fraction of chord.
        p : float
            Chordwise location of maximum camber as a fraction of chord.
        t : float
            Maximum thickness as a fraction of chord.
        trailing_edge : {"standard", "sharp"}, default="standard"
            Trailing-edge closure model for the thickness distribution.
        leading_edge_radius : {"standard", "exact"}, default="standard"
            Leading-edge radius treatment for the thickness distribution.

        Returns
        -------
        Self
            Runtime airfoil built from the explicit parameters.

        Raises
        ------
        ValueError
            If the parameter set falls outside the supported NACA 4-digit
            range.
        """
        params = Naca4AirfoilParamsSpec(
            m=m,
            p=p,
            t=t,
            trailing_edge=trailing_edge,
            leading_edge_radius=leading_edge_radius,
        )
        return cls(Naca4AirfoilSpec(params=params))

    @property
    def spec(self) -> Naca4AirfoilSpec:
        """
        Return the source spec for this airfoil.

        Returns
        -------
        Naca4AirfoilSpec
            Defensive copy of the serialized source spec.
        """
        return _copy_naca4_spec(self._spec)

    def to_spec(self) -> Naca4AirfoilSpec:
        """
        Return the schema definition needed to recreate this airfoil.

        Returns
        -------
        Naca4AirfoilSpec
            Serialized source spec for this airfoil.
        """
        return self.spec


__all__ = ["Naca4AirfoilClassic", "Naca4AirfoilParams"]
