"""Modality runners — concrete ModalityRunner implementations.

Importing this package registers the two STUB runners (audio_t2a,
lipsync_post) automatically. The two LIVE runners (image_t2i, video_i2v)
are registered by `pipeline.core.dispatch.register_default_runners` on
first dispatch() call.

The canonical home of `register_default_runners` is
`pipeline.core.dispatch`. The CP-5→CP-6 deprecated re-export was removed
in engine-fix Phase D Phase 8 (3-CP overdue per the original CP-6 promise).
"""

from __future__ import annotations

from recoil.pipeline.core.registry import (
    MODALITY_AUDIO_T2A,
    MODALITY_LIPSYNC_POST,
    is_registered,
    register_runner,
)
from recoil.pipeline.core.runners.audio_runner import AudioRunner
from recoil.pipeline.core.runners.image_runner import ImageRunner  # noqa: F401  (re-exported)
from recoil.pipeline.core.runners.lipsync_post import LipSyncPostProcessor
from recoil.pipeline.core.runners.video_runner import VideoRunner  # noqa: F401  (re-exported)


def _register_stubs_once() -> None:
    """Register the two CP-8 stubs idempotently.

    Called at module import. Safe to call multiple times — re-registration
    of the same instance is allowed by the registry.
    """
    if not is_registered(MODALITY_AUDIO_T2A):
        register_runner(MODALITY_AUDIO_T2A, AudioRunner())
    if not is_registered(MODALITY_LIPSYNC_POST):
        register_runner(MODALITY_LIPSYNC_POST, LipSyncPostProcessor())


_register_stubs_once()


__all__ = [
    "AudioRunner",
    "ImageRunner",
    "LipSyncPostProcessor",
    "VideoRunner",
]


# ── CP-9 Phase 4: eval modality runners (opt-in registration) ─────────
#
# Eval runners are NOT auto-registered on import — they require GEMINI_API_KEY
# at the EvalNode layer (GeminiVisionEvalNode), and auto-registration on
# import would break test environments and developer machines that don't
# have the key. Callers opt in by calling `register_eval_runners()` (or the
# canonical `pipeline.core.dispatch.register_default_eval_runners`).

from recoil.pipeline.core.runners.eval_image_runner import EvalImageRunner  # noqa: F401, E402
from recoil.pipeline.core.runners.eval_video_runner import EvalVideoRunner  # noqa: F401, E402
from recoil.pipeline.core.runners.eval_audio_runner import EvalAudioRunner  # noqa: F401, E402
from recoil.pipeline.core.runners._gemini_vision_eval_node import GeminiVisionEvalNode  # noqa: F401, E402


def register_eval_runners(*, force: bool = False) -> None:
    """Opt-in registration for the three CP-9 eval runners.

    Thin re-export of :func:`pipeline.core.dispatch.register_default_eval_runners`
    for symmetry with this module's deprecated ``register_default_runners``
    re-export. Callers may import either path:

        from recoil.pipeline.core.runners import register_eval_runners
        # or
        from recoil.pipeline.core.dispatch import register_default_eval_runners

    Both are zero-arg-other-than-``force`` (eval runners delegate to
    ``gemini_vision.score_artifact`` directly and do NOT require a
    StepRunner — distinct from ``register_default_runners`` which DOES).
    """
    from recoil.pipeline.core.dispatch import register_default_eval_runners as _canonical
    _canonical(force=force)


__all__ += [
    "EvalImageRunner",
    "EvalVideoRunner",
    "EvalAudioRunner",
    "GeminiVisionEvalNode",
    "register_eval_runners",
]
