"""C2-internal ``BackbonePreprocessor`` Protocol (AZ-336).

The preprocessor is the resize / crop / normalise step that turns a
``NavCameraFrame`` into the tensor the backbone's forward pass
expects. It is C2-internal — each concrete :class:`VprStrategy`
owns its own preprocessor; sharing across backbones is forbidden per
``components/02_c2_vpr/description.md`` § 6 (preprocessing parameters
are tightly coupled to the backbone weights, so a shared
preprocessor would let a NetVLAD instance corrupt UltraVPR's input
layout).

This Protocol is NOT re-exported from ``c2_vpr.__init__`` — keeping
it inside the package enforces the description.md § 6 boundary.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Protocol, runtime_checkable

if TYPE_CHECKING:
    import numpy as np

    from gps_denied_onboard._types.calibration import CameraCalibration
    from gps_denied_onboard._types.nav import NavCameraFrame

__all__ = ["BackbonePreprocessor"]


@runtime_checkable
class BackbonePreprocessor(Protocol):
    """Resize / crop / normalise per backbone's input contract.

    Each :class:`VprStrategy` implementation owns its concrete
    preprocessor (NOT shared across backbones). The strategy calls
    :meth:`preprocess` inside :meth:`VprStrategy.embed_query` before
    running the forward pass.
    """

    def preprocess(
        self,
        frame: "NavCameraFrame",
        calibration: "CameraCalibration",
    ) -> "np.ndarray":
        """Return the preprocessed input tensor in the backbone's layout.

        Typical shape: ``(1, 3, H, W)`` NCHW float16 for TRT engines.

        Raises :class:`VprPreprocessError` when the input frame
        violates the backbone's contract (wrong colour channels,
        calibration mismatch).
        """
        ...

    def input_shape(self) -> tuple[int, ...]:
        """``(H, W)`` resize target the backbone expects.

        Stable for the preprocessor's lifetime; consumed by tests to
        assert preprocessing fidelity.
        """
        ...
