"""C2.5 ReRankStrategy config block (AZ-342).

Registered into ``config.components['c2_5_rerank']`` by the package
``__init__.py``. The composition-root factory
:func:`gps_denied_onboard.runtime_root.rerank_factory.build_rerank_strategy`
reads this block to select the strategy and configure the top-N cut.

``top_n`` is the strategy-side cap on the returned
:attr:`RerankResult.candidates` length; the composition root binds
``n`` per-frame from this value (default 3 per the epic's K=10 → N=3
spec).
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Final

from gps_denied_onboard.config.schema import ConfigError

__all__ = [
    "C2_5RerankConfig",
    "KNOWN_STRATEGIES",
]

KNOWN_STRATEGIES: Final[frozenset[str]] = frozenset({"inlier_count"})


@dataclass(frozen=True)
class C2_5RerankConfig:
    """Per-component config for C2.5 ReRank.

    ``strategy`` selects exactly one of the registered re-rankers
    (today only ``inlier_count``); the composition-root factory
    respects compile-time ``BUILD_RERANK_<variant>`` gating on top
    of this label.

    ``top_n`` is the per-frame N cap (1..K-1). Default 3 (the epic's
    K=10 → N=3 spec).

    ``debug_per_frame_log`` gates the two DEBUG events
    (``c2_5.rerank.zero_inliers`` per dropped candidate and
    ``c2_5.rerank.frame_done`` per frame); flooding journald at
    ``3 Hz × K=10 = 30 events/sec`` by default would violate
    description.md § 9. Operators flip this to ``True`` for the
    debug-build flight binary.
    """

    strategy: str = "inlier_count"
    top_n: int = 3
    debug_per_frame_log: bool = False

    def __post_init__(self) -> None:
        if self.strategy not in KNOWN_STRATEGIES:
            raise ConfigError(
                f"C2_5RerankConfig.strategy={self.strategy!r} not in "
                f"{sorted(KNOWN_STRATEGIES)}"
            )
        if self.top_n < 1:
            raise ConfigError(
                f"C2_5RerankConfig.top_n must be >= 1; got {self.top_n}"
            )
