"""VideoRunner — modality `video_i2v`.

Thin wrapper over StepRunner.execute_video. Covers both i2v (start_frame
provided) and t2v (start_frame None) — execute_video already branches
internally. CP-4 keeps both paths under one modality string; CP-5 may
split into video_i2v / video_t2v if the workflow object model demands it.

Payload schema:
    {
        "shot_id": str (required),
        "prompt": str (required),
        "model": str (required, e.g. "kling-o3", "wan-2.7-i2v", "seeddance-2.0"),
        "start_frame": Optional[Path | str],  # if None, t2v path
        "end_frame": Optional[Path | str],
        "duration": Optional[int] (default 5),
        "aspect_ratio": Optional[str] (REQUIRED — no default; omit only when dispatch() injects from Project),
        "gates": Optional[list[GateFunction]],
        "elements_payload": Optional[dict],
        "generate_audio": Optional[bool] (default True),
        "on_status": Optional[Callable],
        "reference_images": Optional[list[str]],
        "reference_videos": Optional[list[str]],   # v2v edit (Kling O3/O1)
        "provider_hints": Optional[dict],          # e.g. {"endpoint": "o1_edit_standard", "keep_audio": False}
        "negative_prompt": Optional[str],
        "inputs_snapshot": Optional[dict],
        "grouping": Optional[dict],
    }
"""

from __future__ import annotations

import time
from pathlib import Path
from typing import Any, Optional

from recoil.pipeline.core.registry import (
    MODALITY_VIDEO_I2V,
    RunResult,
)
from recoil.pipeline.core.runners._shared import (
    _failure_metadata_production as _failure_metadata,
    _require_aspect_ratio,
)

# Metadata key contract: see RUN_RESULT_METADATA_KEYS in pipeline.core.registry
from recoil.pipeline.core.runners.image_runner import (
    _step_result_to_run_result,
)


class VideoRunner:
    """ModalityRunner for `video_i2v` — image-to-video AND text-to-video.

    Wraps StepRunner.execute_video. The same StepRunner method handles
    both i2v (start_frame is a Path) and t2v (start_frame is None);
    VideoRunner exposes both via a single modality string.
    """

    modality: str = MODALITY_VIDEO_I2V

    def __init__(self, step_runner) -> None:
        self._step_runner = step_runner

    def run(self, payload: dict) -> RunResult:
        shot_id = payload.get("shot_id")
        prompt = payload.get("prompt")
        model = payload.get("model")

        if not shot_id or not prompt or not model:
            return RunResult(
                id=f"{shot_id or 'unknown'}_video_i2v_{int(time.time())}",
                modality=self.modality,
                success=False,
                error=(
                    f"VideoRunner payload missing required keys: "
                    f"shot_id={shot_id!r}, prompt={'...' if prompt else None!r}, "
                    f"model={model!r}"
                ),
                metadata=_failure_metadata(),
            )

        def _to_path(v: Any) -> Optional[Path]:
            if v is None:
                return None
            return v if isinstance(v, Path) else Path(v)

        from recoil.execution.step_runner import build_identity_gates_from_payload

        gates = build_identity_gates_from_payload(payload)

        try:
            step_result = self._step_runner.execute_video(
                shot_id=shot_id,
                prompt=prompt,
                model=model,
                start_frame=_to_path(payload.get("start_frame")),
                end_frame=_to_path(payload.get("end_frame")),
                image_tail=payload.get("image_tail"),
                duration=payload.get("duration", 5),
                aspect_ratio=_require_aspect_ratio(
                    payload, payload.get("shot_id", "unknown")
                ),
                gates=gates,
                elements_payload=payload.get("elements_payload"),
                generate_audio=payload.get("generate_audio", True),
                on_status=payload.get("on_status"),
                reference_images=payload.get("reference_images"),
                reference_videos=payload.get("reference_videos"),
                provider_hints=payload.get("provider_hints"),
                negative_prompt=payload.get("negative_prompt"),
                inputs_snapshot=payload.get("inputs_snapshot"),
                parent_take_id=payload.get("parent_take_id"),
                grouping=payload.get("grouping"),
            )
        except Exception as e:  # noqa: BLE001
            return RunResult(
                id=f"{shot_id}_video_i2v_{int(time.time())}",
                modality=self.modality,
                success=False,
                error=f"{type(e).__name__}: {e}",
                metadata=_failure_metadata(),
            )

        return _step_result_to_run_result(step_result, shot_id, self.modality)


def _make_video_runner(step_runner=None):
    """Factory parallel to _make_image_runner; Phase 6 docs."""
    if step_runner is None:
        raise RuntimeError(
            "VideoRunner factory was invoked without a step_runner. "
            "Pass step_runner explicitly OR register a VideoRunner "
            "instance via pipeline.core.registry.register_runner()."
        )
    return VideoRunner(step_runner)
