#!/usr/bin/env python3
"""Sync snapshot fixtures from real engine state.

Reads projects/<project>/state/visual/**/*.json, sanitizes for fixture use,
writes recoil/console-v2/packages/fixtures/src/snapshots/<project>.json.

Sanitization:
- Truncate take lists to first 3-5 per beat.
- Drop binary blob fields (anything > 1KB string).
- Strip absolute paths to project-relative.

CI gate (when wired):  pnpm fixtures:sync && git diff --exit-code

Idempotent: re-running produces byte-identical output unless real state changed.
"""
from __future__ import annotations

import json
import sys
from pathlib import Path

from recoil.core.paths import projects_root, ProjectPaths

PROJECTS = ("tartarus", "driver-beware", "afterimage")
SNAPSHOTS_DIR = Path(__file__).resolve().parent.parent / "packages" / "fixtures" / "src" / "snapshots"


def _sanitize(value):
    if isinstance(value, str):
        if len(value) > 1024:
            return "<truncated:blob>"
        # Strip absolute paths to project-relative.
        proot = str(projects_root())
        if value.startswith(proot):
            return value[len(proot):].lstrip("/")
        return value
    if isinstance(value, list):
        return [_sanitize(v) for v in value]
    if isinstance(value, dict):
        return {k: _sanitize(v) for k, v in value.items()}
    return value


def _truncate_takes(snapshot: dict, per_beat_max: int = 5) -> dict:
    """If snapshot has a beats->takes structure, truncate per-beat take lists."""
    def walk(node):
        if isinstance(node, dict):
            if "takes" in node and isinstance(node["takes"], list):
                node["takes"] = node["takes"][:per_beat_max]
            for v in node.values():
                walk(v)
        elif isinstance(node, list):
            for v in node:
                walk(v)
    walk(snapshot)
    return snapshot


def snapshot_project(project: str) -> dict:
    root = ProjectPaths.for_project(project).visual_state_dir
    if not root.exists():
        return {"project": project, "shots": []}
    shots = []
    for jpath in sorted(root.rglob("*.json")):
        if not jpath.is_file():
            continue
        try:
            data = json.loads(jpath.read_text())
        except (json.JSONDecodeError, UnicodeDecodeError, OSError):
            continue
        shots.append({"path": str(jpath.relative_to(projects_root())), "data": _sanitize(data)})
    return _truncate_takes({"project": project, "shots": shots})


def main() -> int:
    SNAPSHOTS_DIR.mkdir(parents=True, exist_ok=True)
    for project in PROJECTS:
        snapshot = snapshot_project(project)
        out_path = SNAPSHOTS_DIR / f"{project}.json"
        out_path.write_text(json.dumps(snapshot, indent=2, sort_keys=True) + "\n")
        print(f"[sync_fixtures] wrote {out_path} ({len(snapshot.get('shots', []))} shots)")
    return 0


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