#!/usr/bin/env bash
# Aggregate REC-72 prompt-authoring regression gate.
#
# This is intentionally the final Phase 8 gate: it runs the Phase 1-7 suites,
# then adds black-box dispatch dry-run probes for fallback observability and
# audit-path author/bind coverage.
set -euo pipefail

HERE="$(cd "$(dirname "$0")" && pwd)"
REPO="$(cd "$HERE/../../../.." && pwd)"
cd "$REPO"

export PYTHONPATH="${PYTHONPATH:+$PYTHONPATH:}."

echo "== REC-72 Phase 1-7 regression aggregate =="
python3 -m pytest -q \
  recoil/pipeline/_lib/tests/test_shot_primitive.py \
  recoil/pipeline/_lib/tests/test_author_strategies.py \
  recoil/pipeline/_lib/tests/test_bind_named_prose.py \
  recoil/pipeline/_lib/tests/test_prompt_engine.py \
  recoil/pipeline/_lib/tests/test_author_pass.py \
  recoil/tests/test_coverage_validator.py \
  recoil/pipeline/_lib/tests/test_prose_verify_flow.py \
  recoil/pipeline/_lib/tests/test_dispatch_payload_gates.py \
  recoil/pipeline/_lib/tests/test_payload_serialize_split.py \
  recoil/pipeline/_lib/tests/test_rec72_acceptance.py

echo "== REC-72 fallback observability probe =="
PROSE_AUTHOR_FALLBACK=1 python3 - <<'PY'
import logging

from recoil.pipeline._lib import dispatch_payload as dp
from recoil.pipeline._lib.plan_loader import CanonicalShot, CharacterEntry


class Capture(logging.Handler):
    def __init__(self):
        super().__init__(logging.WARNING)
        self.lines = []

    def emit(self, record):
        self.lines.append(record.getMessage())


def shot(shot_id, char_id):
    raw = {
        "shot_id": shot_id,
        "scene_index": 1,
        "duration_s": 4.0,
        "shot_type": "MS",
        "camera_side": "A",
        "screen_direction": "left-to-right",
        "source_text": f"{char_id.title()} moves through the corridor.",
        "asset_data": {"characters": [char_id], "location_id": "corridor"},
        "prompt_data": {
            "prompt_skeleton": {
                "action_line": f"{char_id.title()} advances.",
                "emotion_line": "Breath held tight.",
            }
        },
    }
    return CanonicalShot(
        shot_id=shot_id,
        scene_index=1,
        sequence_id=None,
        pipeline="video",
        previs_model="gemini-3-pro-image-preview",
        video_model="seeddance-2.0",
        location_id="corridor",
        characters=[CharacterEntry(char_id=char_id)],
        shot_type="MS",
        duration_s=4.0,
        is_env_only=False,
        has_dialogue=False,
        aspect_ratio="9:16",
        raw=raw,
    )


handler = Capture()
root = logging.getLogger()
old_level = root.level
root.addHandler(handler)
root.setLevel(logging.WARNING)

dp._project_config_cache.clear()
dp.load_project_config = lambda _project: {}
dp.get_builder = lambda *a, **k: (lambda *ba, **bk: "TEMPLATE")

jade = shot("EP001_SH30", "jade")
result = dp._build_author_aware_prompt(
    dp.PayloadContext(
        project="tartarus",
        modality="r2v_multi",
        shot_id="EP001_PASS_FALLBACK",
        shot=jade,
        batch_shots=[jade, shot("EP001_SH31", "jade")],
        model_id="seeddance-2.0",
        bible={},
    ),
    model_id="seeddance-2.0",
    ref_manifest={"identity_1": 1},
    segment_timestamps=[0.0, 4.0],
    primitive_segment_timestamps=[(0.0, 4.0), (4.0, 8.0)],
    bible={},
    project_config={},
)

root.removeHandler(handler)
root.setLevel(old_level)

logs = "\n".join(handler.lines)
assert result.strategy == "deterministic_template", result
assert result.prompt == "TEMPLATE", result.prompt
assert "prose_author_fallback" in logs, logs
assert "reason=env_short_circuit" in logs, logs
print("fallback observability OK")
PY

echo "== REC-72 audit_dispatch author/bind path probe =="
python3 - <<'PY'
from recoil.pipeline.tools import audit_dispatch
from recoil.pipeline._lib import dispatch_payload as dp


def shot(shot_id, action):
    return {
        "shot_id": shot_id,
        "scene_index": 1,
        "kind": "batch_member",
        "modality": "r2v_multi",
        "model": "seeddance-2.0",
        "duration_s": 4.0,
        "aspect_ratio": "9:16",
        "shot_type": "MS",
        "camera_side": "A",
        "screen_direction": "left-to-right",
        "source_text": action,
        "asset_data": {"characters": ["jade"], "location_id": "corridor"},
        "prompt_data": {
            "prompt_skeleton": {
                "action_line": action,
                "emotion_line": "Controlled urgency under pressure.",
            }
        },
    }


calls = {"author": 0}


def fake_author_pass(primitive, strategy, **_kwargs):
    calls["author"] += 1
    assert strategy.name == "directed_prose", strategy
    assert primitive.camera_side == "A", primitive
    assert primitive.screen_direction == "left-to-right", primitive
    return (
        "[0:00-0:04] Jade drives forward as the lens pushes with her tight breath.\n"
        "[0:04-0:08] Jade braces at the hatch, jaw locked while the handheld move settles."
    )


def legacy_builder_must_not_run(*_args, **_kwargs):
    raise AssertionError("legacy deterministic builder was called")


dp._project_config_cache.clear()
dp.load_project_config = lambda _project: {}
dp._collect_reference_images = lambda *a, **k: (
    ["/tmp/jade_ref.png"],
    {"identity_1": 1},
)
dp.author_pass = fake_author_pass
dp.get_builder = legacy_builder_must_not_run

shots = [
    shot("EP001_SH30", "Jade crosses the corridor."),
    shot("EP001_SH31", "Jade reaches the hatch."),
]
payload = audit_dispatch.build_payload_dry_run(
    "r2v_multi",
    "seeddance-2.0",
    {"shots": shots, "overrides": {}, "plan": {"shots": shots}},
    "tartarus",
    "ep_001",
)

prompt = payload.get("prompt") or ""
assert calls["author"] == 1, calls
assert "@Image1" in prompt, prompt
assert "Jade" not in prompt, prompt
assert payload.get("provider_hints", {}).get("r2v_multi") is True, payload
assert payload.get("reference_images") == ["/tmp/jade_ref.png"], payload
print("audit_dispatch author/bind path OK")
PY

echo "REC-72 prompt-authoring aggregate OK"
