"""C2.5 rerank DTOs (L1 cross-component layer; AZ-342).

The two-DTO surface is frozen by
``contracts/c2_5_rerank/rerank_strategy_protocol.md`` v1.0.0:
slotted, immutable, ``produced_at`` stamped with the producer's
``monotonic_ns`` so the C13 FDR record can correlate without a
wall-clock dependency.

:class:`RerankCandidate.tile_id` is a plain
``tuple[int, float, float]`` of ``(zoom_level, lat, lon)`` —
identical encoding to :class:`VprCandidate.tile_id` — keeping the L1
layer free of an L1→L3 import per ``module-layout.md`` (consumers
reconstruct :class:`gps_denied_onboard.components.c6_tile_cache.TileId`
at the C6 boundary).

:class:`RerankCandidate.tile_pixels_handle` is intentionally typed
``object``: C6 owns the actual handle type and the rerank Protocol
treats it as opaque per Invariant 6 (the handle is a reference, NOT
a copy — copying tile pixels would defeat AC-4.1's latency budget).
"""

from __future__ import annotations

from dataclasses import dataclass

__all__ = ["RerankCandidate", "RerankResult"]


@dataclass(frozen=True, slots=True)
class RerankCandidate:
    """One re-rank survivor.

    Carries the C2-stage ``descriptor_distance`` + ``descriptor_dim``
    forward unchanged (INV-5) so the FDR record retains the full
    provenance chain. ``inlier_count`` is the new field produced by
    the single-pair LightGlue forward at re-rank time; ``> 0`` for
    every survivor.
    """

    tile_id: tuple[int, float, float]
    inlier_count: int
    descriptor_distance: float
    descriptor_dim: int
    tile_pixels_handle: object


@dataclass(frozen=True, slots=True)
class RerankResult:
    """Top-N survivors from :meth:`ReRankStrategy.rerank`.

    Consumed by C3 CrossDomainMatcher. ``candidates`` is a tuple
    (not a list) so the frozen+slots invariant truly holds — a frozen
    dataclass holding a mutable list lets consumers mutate it; the
    tuple closes that door.

    ``candidates_input`` / ``candidates_dropped`` make the
    drop-and-continue accounting (INV-8) observable per-frame so a
    post-flight aggregate alert can flag flights whose
    ``candidates_dropped`` p95 climbs.
    """

    frame_id: int
    candidates: tuple[RerankCandidate, ...]
    reranked_at: int
    rerank_label: str
    candidates_input: int
    candidates_dropped: int
