"""Rudder actuator configuration.

Supports the actuator families enumerated in section 3 of the brief.
Sterndrive, IPS and Zeus interfaces are reserved for Phase 3 and are
intentionally **not** enabled in Phase 1 configurations.
"""

from __future__ import annotations

from enum import StrEnum

from pydantic import BaseModel, ConfigDict, Field, field_validator


class ActuatorType(StrEnum):
    """Actuator family driving the rudder.

    Phase-1 supported families have a regular value. Reserved (Phase 3)
    families also live here so that configurations and the UI can model
    them, but the firmware refuses to operate them in Phase 1.
    """

    HYDRAULIC_REVERSIBLE = "hydraulic_reversible"
    """Reversible hydraulic pump (Hynautic, Hypro, Octopus, Vetus, L&S). Phase 1."""

    ELECTRIC_DC_REVERSIBLE = "electric_dc_reversible"
    """Reversible DC motor with mechanical end-stops (Lewmar, Simpson Lawrence). Phase 1."""

    SERVOMOTOR_FEEDBACK = "servomotor_feedback"
    """Servomotor with built-in position feedback. Phase 1."""

    STERNDRIVE_ANALOG = "sterndrive_analog"
    """Analog directional sterndrive. Phase 1."""

    VOLVO_IPS = "volvo_ips"
    """Volvo IPS via EVC. Phase 3 — reserved."""

    MERCURY_ZEUS = "mercury_zeus"
    """Mercury Zeus via SmartCraft. Phase 3 — reserved."""


_PHASE_1_ACTUATORS: frozenset[ActuatorType] = frozenset(
    {
        ActuatorType.HYDRAULIC_REVERSIBLE,
        ActuatorType.ELECTRIC_DC_REVERSIBLE,
        ActuatorType.SERVOMOTOR_FEEDBACK,
        ActuatorType.STERNDRIVE_ANALOG,
    }
)


def is_phase_1(actuator_type: ActuatorType) -> bool:
    """Return ``True`` iff the firmware is allowed to drive this actuator in Phase 1."""
    return actuator_type in _PHASE_1_ACTUATORS


class ActuatorConfig(BaseModel):
    """Configuration of the rudder actuator and its calibration.

    All angles are in degrees. ``deadband_pct`` and ``min_useful_pwm_pct``
    are percentages of full command. Asymmetry is the ratio of starboard
    speed to port speed: ``1.0`` means symmetric, ``1.10`` means the pump
    pushes 10 % faster to starboard than to port.
    """

    model_config = ConfigDict(extra="forbid", validate_assignment=True)

    type: ActuatorType
    """Actuator family."""

    name: str = Field(
        default="",
        max_length=120,
        description="Free-form label, e.g. 'Hynautic K-21 + Capilano cylinder'.",
    )

    # -- Calibration / non-linearity compensation ------------------------------
    deadband_pct: float = Field(
        default=5.0,
        ge=0.0,
        le=30.0,
        description="Initial percentage of command that produces no motion (static friction).",
    )
    min_useful_pwm_pct: float = Field(
        default=8.0,
        ge=0.0,
        le=50.0,
        description="Minimum PWM where the actuator actually moves; commands below this snap up to it.",
    )
    asymmetry_stbd_over_port: float = Field(
        default=1.0,
        ge=0.50,
        le=2.00,
        description="Ratio of starboard speed to port speed; 1.0 = symmetric.",
    )

    # -- Mechanical / electrical limits ---------------------------------------
    max_rudder_angle_deg: float = Field(
        default=35.0,
        gt=0.0,
        le=45.0,
        description="Mechanical limit on either side from amidships; typically 35 deg.",
    )
    max_rate_dps: float = Field(
        default=4.5,
        gt=0.0,
        le=15.0,
        description="Maximum slew rate in degrees per second (typical 3-6 dps).",
    )
    max_current_a: float = Field(
        default=15.0,
        gt=0.0,
        le=200.0,
        description="Overcurrent trip threshold (A) for the actuator power line.",
    )

    # -- Safety policy --------------------------------------------------------
    feedback_required: bool = Field(
        default=True,
        description=(
            "Closed loop only. Open-loop operation requires explicit integrator "
            "override (brief section 3, Phase 1). Default True."
        ),
    )

    @field_validator("min_useful_pwm_pct")
    @classmethod
    def _min_useful_must_cover_deadband(cls, v: float, info: object) -> float:
        # info.data is provided by pydantic-core for cross-field validation
        data = getattr(info, "data", {}) or {}
        deadband = data.get("deadband_pct")
        if deadband is not None and v < deadband:
            raise ValueError(
                f"min_useful_pwm_pct ({v}) must be >= deadband_pct ({deadband})"
            )
        return v

    def is_phase_1_supported(self) -> bool:
        """Return ``True`` iff this actuator can be driven in Phase 1."""
        return is_phase_1(self.type)
