"""Navigation-side DTOs: camera frames, IMU samples, attitude, VIO output.

Type-only stubs created by AZ-263 (Bootstrap) and extended by AZ-331
(C1 VioStrategy contract freeze, v1.0.0). Concrete field semantics
are defined in ``_docs/02_document/architecture.md § 4`` and the C1
/ C5 / C8 component specs. Concrete subclasses are owned by the
components that emit them; downstream consumers depend on the DTOs
declared here. Cross-component DTOs (``VioOutput``, ``VioHealth``)
live at this L1 layer per module-layout.md — components MUST NOT
import them from other components.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    import numpy.typing as npt

    from gps_denied_onboard.helpers.se3_utils import SE3


@dataclass(frozen=True)
class NavCameraFrame:
    """A single nav-camera frame routed into the pipeline."""

    frame_id: int
    timestamp: datetime
    image: Any
    camera_calibration_id: str
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass(frozen=True)
class ImuSample:
    """A single IMU sample.

    Timestamp is monotonic nanoseconds (per FC clock) per the
    `imu_preintegrator` contract — the preintegrator enforces strict
    monotonicity on this field, so it MUST be the producer's monotonic
    source-of-truth, not a wall-clock conversion.
    """

    ts_ns: int
    accel_xyz: tuple[float, float, float]
    gyro_xyz: tuple[float, float, float]


@dataclass(frozen=True)
class ImuWindow:
    """A short window of IMU samples for preintegration."""

    samples: tuple[ImuSample, ...]
    ts_start_ns: int
    ts_end_ns: int


@dataclass(frozen=True)
class ImuBias:
    """IMU bias estimate consumed by the preintegrator.

    `accel_bias` and `gyro_bias` are 3-vectors in the FC's IMU frame.
    The preintegrator never re-estimates bias internally; consumers
    (C1 VIO, C5 StateEstimator) call `reset_with_bias(...)` whenever
    their estimate changes.
    """

    accel_bias: tuple[float, float, float]
    gyro_bias: tuple[float, float, float]


@dataclass(frozen=True)
class AttitudeWindow:
    """Attitude samples over a time window (quaternion + timestamp)."""

    quaternions: tuple[tuple[float, float, float, float], ...]
    timestamps: tuple[datetime, ...]


# `FlightStateSignal` and `GpsHealth` moved to ``_types/fc.py`` as part
# of AZ-390 — they belong on C8's public Protocol surface (the contract's
# canonical shape uses enums + monotonic_ns timestamps; the old stubs
# from AZ-263 used `str` + `datetime` and were never wired by any
# production producer or consumer).


# ----------------------------------------------------------------------
# AZ-331 — C1 VioStrategy contract DTOs (v1.0.0).

from enum import Enum


class VioState(str, Enum):
    """C1 VIO health state reported on every ``VioHealth`` snapshot."""

    INIT = "init"
    TRACKING = "tracking"
    DEGRADED = "degraded"
    LOST = "lost"


@dataclass(frozen=True)
class FeatureQuality:
    """Per-frame feature-tracking diagnostics (C1 contract v1.0.0).

    Surfaced inside :class:`VioOutput`; consumed by C13 FDR + C5 fusion
    for adaptive-gating decisions.
    """

    tracked: int
    new: int
    lost: int
    mean_parallax: float
    mre_px: float


@dataclass(frozen=True)
class WarmStartPose:
    """Hint passed to ``VioStrategy.reset_to_warm_start`` after F8 reboot.

    ``body_T_world`` is a :class:`gtsam.Pose3` (= ``SE3``); the warm-start
    + F8 reboot recovery wiring task in E-C1 owns the on-disk persistence
    pattern. ``captured_at_ns`` is :func:`time.monotonic_ns` at hint
    production time.
    """

    body_T_world: "SE3"
    velocity_b: tuple[float, float, float]
    bias: "ImuBias"
    captured_at_ns: int


@dataclass(frozen=True)
class VioOutput:
    """C1 strategy per-frame output (C1 contract v1.0.0).

    ``frame_id`` is a stable string identifier echoed from the input
    ``NavCameraFrame.frame_id`` (stringified if the source is an int
    or UUID); C5 uses it as a hashable key into its frame→GTSAM-key
    map (Invariant — alignment).

    ``relative_pose_T`` is the strategy's current pose as a
    :class:`gtsam.Pose3`; expressed in the strategy's own internal
    frame (VIO has no absolute world reference — the internal frame
    drifts from world over time). C5 computes the between-factor
    delta via ``prev.between(curr)`` and inserts ``curr`` as the
    iSAM2 Values entry.

    ``pose_covariance_6x6`` is the symmetric positive-definite 6×6
    covariance in the rotation-then-translation tangent-space order;
    strategies MUST NOT tighten this during a degradation event
    (Invariant — honest covariance is the AC-NEW-4 / AC-NEW-7 floor).

    ``imu_bias`` is the strategy's latest bias estimate;
    ``feature_quality`` is per-frame tracker diagnostics;
    ``emitted_at_ns`` is :func:`time.monotonic_ns` at output time.
    """

    frame_id: str
    relative_pose_T: "SE3"
    pose_covariance_6x6: "npt.NDArray[Any]"
    imu_bias: "ImuBias"
    feature_quality: FeatureQuality
    emitted_at_ns: int


@dataclass(frozen=True)
class VioHealth:
    """C1 strategy health snapshot (C1 contract v1.0.0).

    Returned from :meth:`VioStrategy.health_snapshot`; consumed by C13
    FDR and the composition-root watchdog. ``consecutive_lost`` ticks
    every time ``state == LOST``; the strategy raises
    :class:`VioFatalError` once this exceeds
    ``config.vio.lost_frame_threshold`` (default 9).
    """

    state: VioState
    consecutive_lost: int
    bias_norm: float
