#!/usr/bin/env python3
"""KiCad tour runner — executes the 8-step tour via `adom-desktop kicad_*`
CLI calls and writes each step's screenshot to an output dir.

Usage:
    python tour_runner.py --pack tour-pack          --output /tmp/tour
    python tour_runner.py --pack tour-pack-rp2040   --output /tmp/rp2040-tour

The script is deliberately a thin orchestrator: every step is a single
CLI invocation, and every wait is a fixed `sleep`. Caller can read the
script as the canonical sequence of bridge calls; the same calls work
from a `bash` tour or any other orchestrator.
"""

from __future__ import annotations
import argparse
import json
import os
import shutil
import subprocess
import sys
import time
from pathlib import Path
from typing import Any

# Per-pack config — each pack has its own symbol/footprint/template names
PACK_CONFIG = {
    "tour-pack": {
        "library": "AdomTour",
        "symbol": "R_AdomTour",
        "footprint": "R_0805_AdomTour",
        "sch_filename": "tour-demo.kicad_sch",
        "pcb_filename": "tour-demo.kicad_pcb",
    },
    "tour-pack-rp2040": {
        "library": "AdomRP2040",
        "symbol": "RP2040_AdomTour",
        "footprint": "QFN-56_AdomRP2040",
        "sch_filename": "rp2040-breakout.kicad_sch",
        "pcb_filename": "rp2040-breakout.kicad_pcb",
    },
}


def adom(verb: str, args: dict | None = None) -> dict[str, Any]:
    """Run `adom-desktop <verb> '<json>'` and return the parsed response."""
    cmd = ["adom-desktop", verb]
    if args is not None:
        cmd.append(json.dumps(args))
    print(f"  $ adom-desktop {verb} {json.dumps(args) if args else ''}")
    proc = subprocess.run(cmd, capture_output=True, text=True, timeout=180)
    if proc.returncode != 0 and not proc.stdout.strip():
        print(f"  ! exit {proc.returncode}: {proc.stderr.strip()[:200]}")
    try:
        return json.loads(proc.stdout)
    except json.JSONDecodeError:
        return {"success": False, "error": f"non-JSON stdout: {proc.stdout[:300]}"}


def pull_screenshot(hwnd: int, dest: Path, label: str) -> bool:
    """Capture a window via desktop_screenshot_window + adom-desktop pull_file."""
    resp = adom("desktop_screenshot_window", {"hwnd": hwnd})
    full = resp.get("fullPathHost") or (resp.get("data") or {}).get("fullPathHost")
    if not full:
        print(f"  ! {label}: no fullPathHost in response")
        return False
    # Try the safe (downscaled) variant if the full one is unwieldy
    safe = resp.get("safePathHost") or (resp.get("data") or {}).get("safePathHost")
    src = safe or full
    pull = adom("pull_file", {"filePaths": [src], "saveTo": str(dest.parent)})
    # adom-desktop's pull_file lands files in saveTo with their basename. Rename.
    landed = dest.parent / Path(src).name
    if landed.exists():
        landed.rename(dest)
        print(f"  ✓ {label} → {dest.name}")
        return True
    # Fall back to copying via WSL bash if pull_file didn't land (relay vs direct)
    if Path(src.replace("C:", "/c").replace("\\", "/")).exists():
        shutil.copy(src.replace("C:", "/c").replace("\\", "/"), dest)
        print(f"  ✓ {label} → {dest.name} (fallback copy)")
        return True
    print(f"  ! {label}: pull_file did not land the file ({src})")
    return False


def find_window(title_substring: str) -> int | None:
    """Find a KiCad window whose title contains `title_substring`."""
    resp = adom("kicad_window_info")
    editors = (resp.get("data") or {}).get("editors") or []
    for w in editors:
        if title_substring in (w.get("title") or ""):
            return int(w["hwnd"])
    return None


def main() -> int:
    parser = argparse.ArgumentParser(description="KiCad tour runner.")
    parser.add_argument("--pack", required=True, choices=list(PACK_CONFIG.keys()))
    parser.add_argument(
        "--pack-dir",
        default=None,
        help="Override the pack root path. Default uses the bundled location.",
    )
    parser.add_argument("--output", required=True, help="Output dir for screenshots.")
    parser.add_argument(
        "--no-cleanup",
        action="store_true",
        help="Don't call kicad_close at the end.",
    )
    parser.add_argument(
        "--skip-fp-3d",
        action="store_true",
        help="Skip step 4 (FP-editor 3D viewer) — KiCad 10 doesn't always respond to the WM_COMMAND.",
    )
    args = parser.parse_args()

    cfg = PACK_CONFIG[args.pack]
    pack_dir = Path(args.pack_dir) if args.pack_dir else Path(
        f"C:/Github/adom-desktop/plugins/kicad/{args.pack}"
    )
    out_dir = Path(args.output)
    out_dir.mkdir(parents=True, exist_ok=True)

    sym_lib = pack_dir / f"{cfg['library']}.kicad_sym"
    fp_lib = pack_dir / f"{cfg['library']}.pretty"
    sch_path = pack_dir / "template" / cfg["sch_filename"]
    pcb_path = pack_dir / "template" / cfg["pcb_filename"]

    print(f"\n=== KiCad Tour: {args.pack} ===")
    print(f"  library  : {cfg['library']}")
    print(f"  symbol   : {cfg['symbol']}")
    print(f"  footprint: {cfg['footprint']}")
    print(f"  output   : {out_dir}\n")

    fails: list[str] = []

    def step(n: int, name: str) -> None:
        print(f"\n--- Step {n}: {name} ---")

    # Step 1a — install symbol library
    step(1, "install symbol library")
    resp = adom("kicad_install_library", {
        "libraryPath": str(sym_lib).replace("\\", "/"),
        "libraryType": "symbol",
        "libraryName": cfg["library"],
    })
    if not resp.get("success"):
        fails.append(f"1a: {resp.get('error')}")

    # Step 1b — install footprint library
    step(1, "install footprint library")
    resp = adom("kicad_install_library", {
        "libraryPath": str(fp_lib).replace("\\", "/"),
        "libraryType": "footprint",
        "libraryName": cfg["library"],
    })
    if not resp.get("success"):
        fails.append(f"1b: {resp.get('error')}")

    # Step 2 — open Symbol Editor
    step(2, f"open Symbol Editor on {cfg['symbol']}")
    resp = adom("kicad_open_symbol_editor", {
        "libraryName": cfg["library"],
        "symbolName": cfg["symbol"],
    })
    if not resp.get("success"):
        fails.append(f"2: {resp.get('error')}")
    time.sleep(6)
    sym_hwnd = find_window("Symbol Editor")
    if sym_hwnd:
        pull_screenshot(sym_hwnd, out_dir / "02-symbol-editor.png", "Symbol Editor")
    else:
        fails.append("2: Symbol Editor window not found")

    # Step 3 — open Footprint Editor
    step(3, f"open Footprint Editor on {cfg['footprint']}")
    resp = adom("kicad_open_footprint_editor", {
        "libraryName": cfg["library"],
        "footprintName": cfg["footprint"],
    })
    if not resp.get("success"):
        fails.append(f"3: {resp.get('error')}")
    time.sleep(6)
    fp_hwnd = find_window("Footprint Editor")
    if fp_hwnd:
        pull_screenshot(fp_hwnd, out_dir / "03-footprint-editor.png", "Footprint Editor")
    else:
        fails.append("3: Footprint Editor window not found")

    # Step 4 — show 3D viewer for footprint (optional)
    if not args.skip_fp_3d:
        step(4, "FP-editor 3D viewer (Alt+3)")
        resp = adom("kicad_open_3d_viewer", {"editor": "fp"})
        if resp.get("success"):
            time.sleep(4)
            v_hwnd = find_window("3D Viewer")
            if v_hwnd:
                pull_screenshot(v_hwnd, out_dir / "04-footprint-3d.png", "FP 3D Viewer")
                adom("kicad_close_3d_viewer")
                time.sleep(2)
            else:
                print("  ⚠ FP 3D viewer reported success but window not found.")
        else:
            print(f"  ⚠ FP 3D viewer failed (known KiCad 10 issue): {resp.get('error')}")

    # Close FP editor so step 7's 3D viewer routes to PCB
    adom("kicad_close_footprint_editor")
    time.sleep(2)

    # Step 5 — open schematic
    step(5, "open schematic")
    resp = adom("kicad_open_schematic", {"filePath": str(sch_path).replace("\\", "/")})
    if not resp.get("success"):
        fails.append(f"5: {resp.get('error')}")
    time.sleep(8)
    sch_hwnd = find_window("Schematic Editor")
    if sch_hwnd:
        pull_screenshot(sch_hwnd, out_dir / "05-schematic.png", "Schematic Editor")
    else:
        fails.append("5: Schematic Editor window not found")

    # Step 6 — open board
    step(6, "open board")
    resp = adom("kicad_open_board", {"filePath": str(pcb_path).replace("\\", "/")})
    if not resp.get("success"):
        fails.append(f"6: {resp.get('error')}")
    time.sleep(8)
    pcb_hwnd = find_window("PCB Editor")
    if pcb_hwnd:
        pull_screenshot(pcb_hwnd, out_dir / "06-board.png", "PCB Editor")
    else:
        fails.append("6: PCB Editor window not found")

    # Step 7 — show 3D viewer for board (always works; uses editor='pcb' to
    # skip the FP path)
    step(7, "PCB 3D viewer")
    resp = adom("kicad_open_3d_viewer", {"editor": "pcb"})
    if not resp.get("success"):
        fails.append(f"7: {resp.get('error')}")
    time.sleep(6)
    v_hwnd = find_window("3D Viewer")
    if v_hwnd:
        pull_screenshot(v_hwnd, out_dir / "07-board-3d.png", "Board 3D Viewer")
    else:
        fails.append("7: 3D Viewer window not found")

    # Step 8 — export gerbers
    step(8, "export gerbers")
    gerber_dir = str(out_dir / "gerbers").replace("\\", "/")
    resp = adom("kicad_export_gerbers", {
        "filePath": str(pcb_path).replace("\\", "/"),
        "outputDir": gerber_dir,
    })
    if not resp.get("success"):
        fails.append(f"8: {resp.get('error')}")
    files = (resp.get("data") or {}).get("files") or []
    print(f"  ✓ wrote {len(files)} gerber files to {gerber_dir}")

    # Cleanup
    if not args.no_cleanup:
        print("\n--- Cleanup: close KiCad ---")
        adom("kicad_close")

    print(f"\n=== Done — {len(fails)} failures ===")
    for f in fails:
        print(f"  • {f}")
    return 1 if fails else 0


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