#!/usr/bin/env python3
"""Record all web UI demos.

This script orchestrates recording of web UI demos using Playwright,
then converts the WebM recordings to GIF format.

Usage:
    python docs/demos/web/record.py           # Record all demos
    python docs/demos/web/record.py navigation  # Record specific demo

Requirements:
    - Playwright with Chromium: playwright install chromium
    - ffmpeg for GIF conversion: apt install ffmpeg / brew install ffmpeg
"""

from __future__ import annotations

import os
import re
import shutil
import subprocess
import sys
from pathlib import Path

from rich.console import Console

console = Console()

SCRIPT_DIR = Path(__file__).parent
REPO_DIR = SCRIPT_DIR.parent.parent.parent
OUTPUT_DIR = REPO_DIR / "docs" / "assets"

DEMOS = [
    "navigation",
    "stack",
    "themes",
    "workflow",
    "console",
    "shell",
]

# High-quality ffmpeg settings for VP8 encoding
# See: https://github.com/microsoft/playwright/issues/10855
# See: https://github.com/microsoft/playwright/issues/31424
#
# MAX_QUALITY: Lossless-like, largest files
# BALANCED_QUALITY: ~43% file size, nearly indistinguishable quality
MAX_QUALITY_ARGS = "-c:v vp8 -qmin 0 -qmax 0 -crf 0 -deadline best -speed 0 -b:v 0 -threads 0"
BALANCED_QUALITY_ARGS = "-c:v vp8 -qmin 0 -qmax 10 -crf 4 -deadline best -speed 0 -b:v 0 -threads 0"

# Choose which quality to use
VIDEO_QUALITY_ARGS = MAX_QUALITY_ARGS


def patch_playwright_video_quality() -> None:
    """Patch Playwright's videoRecorder.js to use high-quality encoding settings."""
    from playwright._impl._driver import compute_driver_executable  # noqa: PLC0415

    # compute_driver_executable returns (node_path, cli_path)
    result = compute_driver_executable()
    node_path = result[0] if isinstance(result, tuple) else result
    driver_path = Path(node_path).parent

    video_recorder = driver_path / "package" / "lib" / "server" / "chromium" / "videoRecorder.js"

    if not video_recorder.exists():
        msg = f"videoRecorder.js not found at {video_recorder}"
        raise FileNotFoundError(msg)

    content = video_recorder.read_text()

    # Check if already patched
    if "deadline best" in content:
        return  # Already patched

    # Pattern to match the ffmpeg args line
    pattern = (
        r"-c:v vp8 -qmin \d+ -qmax \d+ -crf \d+ -deadline \w+ -speed \d+ -b:v \w+ -threads \d+"
    )

    if not re.search(pattern, content):
        msg = "Could not find ffmpeg args pattern in videoRecorder.js"
        raise ValueError(msg)

    # Replace with high-quality settings
    new_content = re.sub(pattern, VIDEO_QUALITY_ARGS, content)
    video_recorder.write_text(new_content)
    console.print("[green]Patched Playwright for high-quality video recording[/green]")


def record_demo(name: str) -> Path | None:
    """Run a single demo and return the video path."""
    console.print(f"[green]Recording:[/green] web-{name}")

    demo_file = SCRIPT_DIR / f"demo_{name}.py"
    if not demo_file.exists():
        console.print(f"[red]  Demo file not found: {demo_file}[/red]")
        return None

    # Create temp output dir for this recording
    temp_dir = SCRIPT_DIR / ".recordings"
    temp_dir.mkdir(exist_ok=True)

    # Run pytest with video recording
    # Set PYTHONPATH so conftest.py imports work
    env = {**os.environ, "PYTHONPATH": str(SCRIPT_DIR)}
    result = subprocess.run(
        [
            sys.executable,
            "-m",
            "pytest",
            str(demo_file),
            "-v",
            "--no-cov",
            "-x",  # Stop on first failure
            f"--basetemp={temp_dir}",
        ],
        check=False,
        cwd=REPO_DIR,
        capture_output=True,
        text=True,
        env=env,
    )

    if result.returncode != 0:
        console.print(f"[red]  Failed to record {name}[/red]")
        console.print(result.stdout)
        console.print(result.stderr)
        return None

    # Find the recorded video
    videos = list(temp_dir.rglob("*.webm"))
    if not videos:
        console.print(f"[red]  No video found for {name}[/red]")
        return None

    # Use the most recent video
    video = max(videos, key=lambda p: p.stat().st_mtime)
    console.print(f"[green]  Recorded: {video.name}[/green]")
    return video


def convert_to_gif(webm_path: Path, output_name: str) -> Path:
    """Convert WebM to GIF using ffmpeg with palette optimization."""
    gif_path = OUTPUT_DIR / f"{output_name}.gif"
    palette_path = webm_path.parent / "palette.png"

    # Two-pass approach for better quality
    # Pass 1: Generate palette
    subprocess.run(
        [  # noqa: S607
            "ffmpeg",
            "-y",
            "-i",
            str(webm_path),
            "-vf",
            "fps=10,scale=1280:-1:flags=lanczos,palettegen=stats_mode=diff",
            str(palette_path),
        ],
        check=True,
        capture_output=True,
    )

    # Pass 2: Generate GIF with palette
    subprocess.run(
        [  # noqa: S607
            "ffmpeg",
            "-y",
            "-i",
            str(webm_path),
            "-i",
            str(palette_path),
            "-lavfi",
            "fps=10,scale=1280:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle",
            str(gif_path),
        ],
        check=True,
        capture_output=True,
    )

    palette_path.unlink(missing_ok=True)
    return gif_path


def move_recording(video_path: Path, name: str) -> tuple[Path, Path]:
    """Move WebM and convert to GIF, returning both paths."""
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

    output_name = f"web-{name}"
    webm_dest = OUTPUT_DIR / f"{output_name}.webm"

    shutil.copy2(video_path, webm_dest)
    console.print(f"[blue]  WebM: {webm_dest.relative_to(REPO_DIR)}[/blue]")

    gif_path = convert_to_gif(video_path, output_name)
    console.print(f"[blue]  GIF:  {gif_path.relative_to(REPO_DIR)}[/blue]")

    return webm_dest, gif_path


def cleanup() -> None:
    """Clean up temporary recording files."""
    temp_dir = SCRIPT_DIR / ".recordings"
    if temp_dir.exists():
        shutil.rmtree(temp_dir)


def main() -> int:
    """Record all web UI demos."""
    console.print("[blue]Recording web UI demos...[/blue]")
    console.print(f"Output directory: {OUTPUT_DIR}")
    console.print()

    # Patch Playwright for high-quality video recording
    patch_playwright_video_quality()

    # Determine which demos to record
    if len(sys.argv) > 1:
        demos_to_record = [d for d in sys.argv[1:] if d in DEMOS]
        if not demos_to_record:
            console.print(f"[red]Unknown demo(s). Available: {', '.join(DEMOS)}[/red]")
            return 1
    else:
        demos_to_record = DEMOS

    results: dict[str, tuple[Path | None, Path | None]] = {}

    try:
        for i, demo in enumerate(demos_to_record, 1):
            console.print(f"[yellow]=== Demo {i}/{len(demos_to_record)}: {demo} ===[/yellow]")

            video_path = record_demo(demo)
            if video_path:
                webm, gif = move_recording(video_path, demo)
                results[demo] = (webm, gif)
            else:
                results[demo] = (None, None)
            console.print()
    finally:
        cleanup()

    # Summary
    console.print("[blue]=== Summary ===[/blue]")
    success_count = sum(1 for w, _ in results.values() if w is not None)
    console.print(f"Recorded: {success_count}/{len(demos_to_record)} demos")
    console.print()

    for demo, (webm, gif) in results.items():  # type: ignore[assignment]
        status = "[green]OK[/green]" if webm else "[red]FAILED[/red]"
        console.print(f"  {demo}: {status}")
        if webm:
            console.print(f"    {webm.relative_to(REPO_DIR)}")
        if gif:
            console.print(f"    {gif.relative_to(REPO_DIR)}")

    return 0 if success_count == len(demos_to_record) else 1


if __name__ == "__main__":
    sys.exit(main())
