#!/usr/bin/env python3
"""Generate camera movement reference clips via Seedance T2V.

Produces 15 camera movement clips using empty-room prompts (no characters)
to build a reusable camera reference library. Each clip demonstrates a
specific camera movement for use as @Video1 refs in R2V prompts.

Cost: ~$23 total (15 clips x 5s x $0.3034/sec standard).

Usage:
    python3 tools/generate_camera_refs.py --dry-run          # Preview prompts only
    python3 tools/generate_camera_refs.py                    # Generate all 15
    python3 tools/generate_camera_refs.py --movements pan_left,dolly  # Subset
    python3 tools/generate_camera_refs.py --output-dir /path  # Custom output
    python3 tools/generate_camera_refs.py --project my-refs   # Custom project name

Output:
    {output_dir}/camera_refs/
        tracking_01.mp4
        dolly_01.mp4
        ...
        manifest.yaml
"""

import argparse
import sys
import time
import yaml
from datetime import datetime, timezone
from pathlib import Path

# Ensure pipeline root is importable (matches test_via_steprunner.py pattern)
PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))

from recoil.pipeline.core.cost import read_cost_from_record_safe  # noqa: E402

# ──────────────────────────────────────────────────────────────────────
# Camera movement prompts — empty rooms, no characters
# ──────────────────────────────────────────────────────────────────────

CAMERA_REF_PROMPTS = [
    {
        "id": "tracking_01",
        "movement": "tracking",
        "prompt": (
            "Camera tracks laterally through an empty industrial warehouse, "
            "concrete floor stretching ahead, overhead fluorescent tubes casting "
            "long shadows. Motivated lighting from high windows on the left wall. "
            "Steady lateral tracking shot moving left to right at walking pace. "
            "Kodak Vision3 500T. 4K, Ultra HD, rich details, sharp clarity, "
            "cinematic texture, natural colors, stable picture. No music, no score."
        ),
    },
    {
        "id": "dolly_01",
        "movement": "dolly",
        "prompt": (
            "Camera dollies forward through a dimly lit corridor, wooden "
            "paneling on both sides, practical wall sconces casting warm pools "
            "of light at regular intervals. Smooth forward dolly at slow pace, "
            "corridor perspective converging ahead. Warm tungsten bounce. "
            "Kodak Vision3 500T. 4K, Ultra HD, rich details, sharp clarity, "
            "cinematic texture, natural colors, stable picture. No music, no score."
        ),
    },
    {
        "id": "push_in_01",
        "movement": "push_in",
        "prompt": (
            "Camera pushes in slowly toward an empty leather chair behind a "
            "polished desk in a glass-walled corner office, city lights glittering "
            "through the windows behind. Motivated lighting from a single desk lamp. "
            "Slow deliberate push in, narrowing the frame. "
            "ARRI Alexa color science. 4K, Ultra HD, rich details, sharp clarity, "
            "cinematic texture, natural colors, stable picture. No music, no score."
        ),
    },
    {
        "id": "pull_out_01",
        "movement": "pull_out",
        "prompt": (
            "Camera pulls back slowly from a rain-streaked window, revealing "
            "an empty apartment interior with scattered moving boxes and bare "
            "hardwood floors. Practical light sources visible in frame from "
            "street lamps outside. Gradual pull out widening the frame. "
            "35mm film grain. 4K, Ultra HD, rich details, sharp clarity, "
            "cinematic texture, natural colors, stable picture. No music, no score."
        ),
    },
    {
        "id": "crane_up_01",
        "movement": "crane_up",
        "prompt": (
            "Camera cranes up from street level past fire escapes and brick "
            "facades of a narrow urban alley, revealing the skyline above. "
            "Practical light sources visible in frame from neon signs and "
            "hanging lanterns below. Smooth vertical crane up. "
            "Kodak Vision3 500T. 4K, Ultra HD, rich details, sharp clarity, "
            "cinematic texture, natural colors, stable picture. No music, no score."
        ),
    },
    {
        "id": "crane_down_01",
        "movement": "crane_down",
        "prompt": (
            "Camera cranes down from a rooftop vantage point, descending past "
            "concrete ledges and ventilation units to reveal a rain-slicked "
            "alley below. Volumetric dust particles in the amber streetlight. "
            "Smooth vertical crane down. "
            "Kodak Vision3 500T. 4K, Ultra HD, rich details, sharp clarity, "
            "cinematic texture, natural colors, stable picture. No music, no score."
        ),
    },
    {
        "id": "pan_left_01",
        "movement": "pan_left",
        "prompt": (
            "Camera pans left across an empty subway platform, tiled walls "
            "with peeling transit maps, dim strip lights overhead, tracks "
            "visible at the edge of frame. Motivated lighting from overhead "
            "fluorescents. Smooth horizontal pan right to left. "
            "ARRI Alexa color science. 4K, Ultra HD, rich details, sharp clarity, "
            "cinematic texture, natural colors, stable picture. No music, no score."
        ),
    },
    {
        "id": "pan_right_01",
        "movement": "pan_right",
        "prompt": (
            "Camera pans right across rows of empty theater seats in a vintage "
            "cinema, red velvet upholstery, art deco wall sconces casting warm "
            "amber light. Practical light sources visible in frame. "
            "Smooth horizontal pan left to right. "
            "Kodak Vision3 500T. 4K, Ultra HD, rich details, sharp clarity, "
            "cinematic texture, natural colors, stable picture. No music, no score."
        ),
    },
    {
        "id": "tilt_up_01",
        "movement": "tilt_up",
        "prompt": (
            "Camera tilts up from a concrete floor scattered with autumn leaves "
            "to reveal the full height of an abandoned cathedral interior, "
            "shafts of light streaming through broken stained glass windows. "
            "Volumetric dust particles in the light beams. Slow tilt up. "
            "35mm film grain. 4K, Ultra HD, rich details, sharp clarity, "
            "cinematic texture, natural colors, stable picture. No music, no score."
        ),
    },
    {
        "id": "tilt_down_01",
        "movement": "tilt_down",
        "prompt": (
            "Camera tilts down from a vaulted ceiling with exposed steel beams "
            "to reveal an empty factory floor, machinery draped in tarps, "
            "industrial pendant lights hanging at various heights. "
            "Motivated lighting from the pendant fixtures. Slow tilt down. "
            "ARRI Alexa color science. 4K, Ultra HD, rich details, sharp clarity, "
            "cinematic texture, natural colors, stable picture. No music, no score."
        ),
    },
    {
        "id": "zoom_in_01",
        "movement": "zoom_in",
        "prompt": (
            "Camera zooms in slowly on an old rotary telephone sitting on a "
            "scarred wooden desk, dust motes floating in a cone of desk lamp "
            "light. Negative fill on the shadow side. Gradual optical zoom "
            "tightening from medium shot to close-up. "
            "Kodak Vision3 500T. 4K, Ultra HD, rich details, sharp clarity, "
            "cinematic texture, natural colors, stable picture. No music, no score."
        ),
    },
    {
        "id": "zoom_out_01",
        "movement": "zoom_out",
        "prompt": (
            "Camera zooms out slowly from a close-up of a steaming coffee cup "
            "on a diner counter to reveal the full empty diner interior, "
            "checkered floor, red vinyl booths, neon OPEN sign in the window. "
            "Warm tungsten bounce from practical overhead fixtures. Gradual "
            "optical zoom out. Kodak Vision3 500T. 4K, Ultra HD, rich details, "
            "sharp clarity, cinematic texture, natural colors, stable picture. "
            "No music, no score."
        ),
    },
    {
        "id": "orbit_01",
        "movement": "orbit",
        "prompt": (
            "Camera orbits slowly around an empty chair in the center of a "
            "bare concrete room, single overhead bulb swinging slightly, "
            "casting rotating shadows on the walls. Motivated lighting from "
            "the hanging bulb. 180-degree slow orbit. "
            "35mm film grain. 4K, Ultra HD, rich details, sharp clarity, "
            "cinematic texture, natural colors, stable picture. No music, no score."
        ),
    },
    {
        "id": "handheld_01",
        "movement": "handheld",
        "prompt": (
            "Handheld camera moves through a crowded night market alley with "
            "no people, hanging lanterns in red and gold, steam rising from "
            "empty food stalls, wet cobblestones reflecting lantern light. "
            "Practical light sources visible in frame. Handheld organic sway "
            "at walking pace. Kodak Vision3 500T. 4K, Ultra HD, rich details, "
            "sharp clarity, cinematic texture, natural colors, stable picture. "
            "No music, no score."
        ),
    },
    {
        "id": "static_01",
        "movement": "static",
        "prompt": (
            "Static locked-off camera observing an empty park bench at dusk, "
            "leaves drifting across the path, a street lamp flickering to life "
            "in the background. Warm tungsten bounce from the lamp. "
            "Completely static frame, no camera movement. "
            "Kodak Vision3 500T. 4K, Ultra HD, rich details, sharp clarity, "
            "cinematic texture, natural colors, stable picture. No music, no score."
        ),
    },
]


def _generate_single_ref(
    ref_entry: dict,
    output_dir: Path,
    project: str,
    dry_run: bool = False,
) -> dict:
    """Generate a single camera ref clip via StepRunner.

    Returns manifest entry dict with id, movement, path, cost, latency.
    """
    ref_id = ref_entry["id"]
    movement = ref_entry["movement"]
    prompt = ref_entry["prompt"]

    if dry_run:
        word_count = len(prompt.split())
        print(f"  [{ref_id}] {movement} — {word_count} words")
        print(f"    {prompt[:120]}...")
        return {"id": ref_id, "movement": movement, "status": "dry_run"}

    # Import StepRunner (deferred to avoid import errors in dry-run)
    from recoil.execution.execution_store import ExecutionStore
    from recoil.execution.step_runner import StepRunner
    from recoil.execution.step_types import ProjectPaths
    from recoil.pipeline.core.dispatch import dispatch
    from recoil.pipeline.core.dispatch_context import DispatchContext

    store = ExecutionStore(project)
    paths = ProjectPaths.for_episode(project, episode=1)
    runner = StepRunner(store=store, paths=paths)

    ctx = DispatchContext(
        caller_id="generate_camera_refs",
        step_runner=runner,
        project=project,
        episode=1,
    )

    shot_id = f"CAMREF_{ref_id}"

    print(f"  Generating [{ref_id}] ({movement})...", end=" ", flush=True)
    start_time = time.time()

    try:
        receipt = dispatch(
            "video_i2v",
            {
                "shot_id": shot_id,
                "prompt": prompt,
                "model": "seeddance-2.0",
                "start_frame": None,  # T2V — no start frame
                "duration": 5,
                "aspect_ratio": "9:16",
                "generate_audio": False,
            },
            context=ctx,
        )
        result = receipt.run_result
        latency = time.time() - start_time
        cost = 5 * 0.3034  # 5s x standard rate

        if result.success:
            # Move/copy output to our camera refs directory
            src = Path(result.output_path) if result.output_path else None
            dst = output_dir / f"{ref_id}.mp4"
            if src and src.exists():
                import shutil
                dst.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(str(src), str(dst))

            print(f"OK ({latency:.0f}s, ${cost:.2f})")
            return {
                "id": ref_id,
                "movement": movement,
                "path": str(dst),
                "cost_usd": round(cost, 2),
                "latency_seconds": round(latency, 1),
                "status": "success",
                "generated_at": datetime.now(timezone.utc).isoformat(),
            }
        else:
            print(f"FAILED ({latency:.0f}s): {result.error}")
            return {
                "id": ref_id,
                "movement": movement,
                "status": "failed",
                "error": result.error or "unknown error",
            }
    except Exception as e:
        latency = time.time() - start_time
        print(f"FAILED ({latency:.0f}s): {e}")
        return {
            "id": ref_id,
            "movement": movement,
            "status": "failed",
            "error": str(e),
        }


def _write_manifest(entries: list[dict], output_dir: Path):
    """Write manifest.yaml alongside the generated clips."""
    manifest = {
        "camera_refs": {
            "generated_at": datetime.now(timezone.utc).isoformat(),
            "model": "seeddance-2.0",
            "endpoint": "t2v",
            "duration_per_clip": 5,
            "cost_per_second": 0.3034,
            "total_clips": len([e for e in entries if e.get("status") == "success"]),
            "total_cost_usd": round(
                sum(read_cost_from_record_safe(e) for e in entries), 2
            ),
        },
        "clips": entries,
    }

    manifest_path = output_dir / "manifest.yaml"
    with open(manifest_path, "w") as f:
        yaml.dump(manifest, f, default_flow_style=False, sort_keys=False)
    print(f"\nManifest written: {manifest_path}")


def main():
    parser = argparse.ArgumentParser(
        description="Generate camera movement reference clips via Seedance T2V",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Preview prompts without generating (zero cost)",
    )
    parser.add_argument(
        "--movements",
        help="Comma-separated movement types to generate (default: all 15)",
    )
    parser.add_argument(
        "--output-dir",
        default=str(PROJECT_ROOT / "output" / "refs" / "camera_refs"),
        help="Output directory for clips and manifest",
    )
    parser.add_argument(
        "--project",
        default="camera-refs",
        help="Project name for ExecutionStore and ProjectPaths (default: camera-refs)",
    )
    args = parser.parse_args()

    output_dir = Path(args.output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)

    # Filter by movements if specified
    prompts = CAMERA_REF_PROMPTS
    if args.movements:
        requested = {m.strip() for m in args.movements.split(",")}
        prompts = [p for p in prompts if p["movement"] in requested]
        if not prompts:
            print(f"No matching movements. Available: "
                  f"{', '.join(p['movement'] for p in CAMERA_REF_PROMPTS)}")
            sys.exit(1)

    total_cost_est = len(prompts) * 5 * 0.3034
    print(f"Camera Ref Generation — {len(prompts)} clips")
    print(f"Estimated cost: ${total_cost_est:.2f}")
    print(f"Output: {output_dir}")
    print()

    if args.dry_run:
        print("DRY RUN — prompts only:\n")
        for p in prompts:
            _generate_single_ref(p, output_dir, project=args.project, dry_run=True)
        print(f"\nDry run complete. {len(prompts)} prompts previewed.")
        return

    entries = []
    for p in prompts:
        entry = _generate_single_ref(p, output_dir, project=args.project)
        entries.append(entry)

    _write_manifest(entries, output_dir)

    succeeded = sum(1 for e in entries if e.get("status") == "success")
    failed = sum(1 for e in entries if e.get("status") == "failed")
    total_cost = sum(read_cost_from_record_safe(e) for e in entries)
    print(f"\nDone: {succeeded} succeeded, {failed} failed, ${total_cost:.2f} total")


if __name__ == "__main__":
    main()
