"""Unified CLI for the Gallia KiCad tour — used by Claude Code.

Subcommands:
    preflight               Check all prerequisites
    screenshot [path]       Capture desktop to PNG
    highlight x y w h       Show highlight overlay (non-blocking)
    bridge <cmd> [k=v ...]  Send bridge command, print JSON
    ipc-test [--section N]  Run IPC test suite
    ipc-layer <id>          Set active PCB layer via IPC
    window <action> <title> Window management (find/close/front/position/wait)

Zero pip deps — only stdlib.
"""

import argparse
import json
import os
import subprocess
import sys
import time

# Ensure tour package is importable
TOUR_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, TOUR_DIR)

PYTHON = sys.executable


# ═══════════════════════════════════════════════════════════════════
# Subcommands
# ═══════════════════════════════════════════════════════════════════

def cmd_preflight(_args):
    """Check all tour prerequisites."""
    from bridge_client import health_check
    from ipc_client import has_test_binary, has_layer_binary
    import win_manager

    print("=== Gallia Tour Preflight ===\n")

    # Bridge server
    health = health_check()
    status = "ONLINE" if health else "OFFLINE"
    print(f"Bridge server:    {status}")
    if health:
        print(f"  URL: http://127.0.0.1:8772")

    # IPC binaries
    print(f"IPC test binary:  {'FOUND' if has_test_binary() else 'MISSING'}")
    print(f"IPC layer binary: {'FOUND' if has_layer_binary() else 'MISSING'}")

    # Screen size
    sw, sh = win_manager.get_screen_size()
    print(f"Screen size:      {sw} x {sh}")

    # KiCad windows
    found_kicad = False
    for name in ["KiCad", "Schematic Editor", "PCB Editor",
                  "Symbol Editor", "Footprint Editor"]:
        wins = win_manager.find_windows_by_title(name)
        if wins:
            title_ascii = wins[0][1].encode("ascii", "replace").decode()
            print(f"Window [{name}]:    OPEN - {title_ascii}")
            found_kicad = True
    if not found_kicad:
        print("KiCad windows:    none open")

    # Demo project
    demo = os.path.join(TOUR_DIR, "demo-project")
    sch = os.path.join(demo, "demo-project.kicad_sch")
    pcb = os.path.join(demo, "demo-project.kicad_pcb")
    print(f"\nDemo schematic:   {'EXISTS' if os.path.exists(sch) else 'MISSING'}")
    print(f"Demo PCB:         {'EXISTS' if os.path.exists(pcb) else 'MISSING'}")
    print(f"Demo dir:         {demo}")

    # Print paths for Claude to use
    if os.path.exists(sch):
        print(f"\nSCH_PATH = {sch}")
    if os.path.exists(pcb):
        print(f"PCB_PATH = {pcb}")


def cmd_screenshot(args):
    """Capture the desktop to a PNG file."""
    from screenshot import capture_screen

    path = args.output
    if not path:
        temp = os.environ.get("TEMP", os.environ.get("TMP", "."))
        path = os.path.join(temp, "gallia_tour_screenshot.png")

    capture_screen(path)
    # Print path so Claude can read it
    print(path)


def cmd_highlight(args):
    """Show a highlight overlay. Spawns a separate process (non-blocking)."""
    script = os.path.join(TOUR_DIR, "highlight.py")
    cmd = [
        PYTHON, script,
        str(args.x), str(args.y), str(args.width), str(args.height),
    ]
    if args.color:
        cmd.extend(["--color", args.color])
    if args.duration:
        cmd.extend(["--duration", str(args.duration)])
    if args.label:
        cmd.extend(["--label", args.label])
    if args.thickness:
        cmd.extend(["--thickness", str(args.thickness)])

    # Spawn without a console window on Windows
    flags = 0
    if os.name == "nt":
        flags = subprocess.CREATE_NO_WINDOW
    subprocess.Popen(cmd, creationflags=flags)
    print(f"Highlight: ({args.x}, {args.y}) {args.width}x{args.height}"
          f" color={args.color} duration={args.duration}ms"
          + (f' label="{args.label}"' if args.label else ""))


def cmd_bridge(args):
    """Send a command to the KiCad bridge server."""
    from bridge_client import send_command, health_check

    if args.command == "health":
        result = health_check()
        if result:
            print(json.dumps(result, indent=2))
        else:
            print('{"status": "offline"}')
        return

    # Parse key=value argument pairs
    cmd_args = {}
    for item in (args.args or []):
        key, _, val = item.partition("=")
        if not val:
            print(f"Warning: skipping malformed arg '{item}' (expected key=value)")
            continue
        cmd_args[key] = val

    result = send_command(args.command, cmd_args)
    print(json.dumps(result, indent=2, default=str))


def cmd_ipc_test(args):
    """Run the IPC test suite and print output."""
    from ipc_client import run_ipc_test, has_test_binary

    if not has_test_binary():
        print("ERROR: test_kicad_ipc.exe not found")
        print("Build with: cargo build --release --example test_kicad_ipc")
        sys.exit(1)

    sections = run_ipc_test()
    if args.section is not None:
        text = sections.get(args.section)
        if text:
            print(text)
        else:
            print(f"Section [{args.section}] not found in output")
            print(f"Available sections: {sorted(sections.keys())}")
    else:
        for k in sorted(sections.keys()):
            print(sections[k])
            print()


def cmd_ipc_layer(args):
    """Set KiCad's active PCB layer via IPC."""
    from ipc_client import set_active_layer, has_layer_binary

    if not has_layer_binary():
        print("ERROR: ipc_set_layer.exe not found")
        print("Build with: cargo build --release --example ipc_set_layer")
        sys.exit(1)

    # Layer name map for user-friendly output
    layer_names = {3: "F.Cu", 4: "In1.Cu", 5: "In2.Cu", 34: "B.Cu"}
    name = layer_names.get(args.layer_id, f"layer {args.layer_id}")

    result = set_active_layer(args.layer_id)
    if result.get("success"):
        print(f"Active layer set to: {name} (id={args.layer_id})")
        if result.get("output"):
            print(result["output"])
    else:
        print(f"ERROR: Failed to set layer to {name}")
        print(result.get("output", "Unknown error"))
        sys.exit(1)


def cmd_window(args):
    """Window management: find, close, front, position, wait."""
    import win_manager

    if args.action == "find":
        windows = win_manager.find_windows_by_title(args.title)
        if windows:
            for hwnd, title in windows:
                safe = title.encode("ascii", "replace").decode()
                print(f"  hwnd={hwnd}  title={safe}")
        else:
            print(f"No windows matching '{args.title}'")

    elif args.action == "close":
        ok = win_manager.close_window_by_title(args.title)
        print("Closed" if ok else f"Window not found: '{args.title}'")

    elif args.action == "front":
        windows = win_manager.find_windows_by_title(args.title)
        if windows:
            win_manager.bring_to_front(windows[0][0])
            safe = windows[0][1].encode("ascii", "replace").decode()
            print(f"Brought to front: {safe}")
        else:
            print(f"Window not found: '{args.title}'")

    elif args.action == "position":
        if args.x is None or args.y is None or args.w is None or args.h is None:
            print("ERROR: position requires x y w h arguments")
            sys.exit(1)
        windows = win_manager.find_windows_by_title(args.title)
        if windows:
            win_manager.position_window(windows[0][0], args.x, args.y,
                                        args.w, args.h)
            safe = windows[0][1].encode("ascii", "replace").decode()
            print(f"Positioned: {safe} at ({args.x},{args.y}) "
                  f"{args.w}x{args.h}")
        else:
            print(f"Window not found: '{args.title}'")

    elif args.action == "wait":
        timeout = args.timeout or 15
        deadline = time.monotonic() + timeout
        while time.monotonic() < deadline:
            windows = win_manager.find_windows_by_title(args.title)
            if windows:
                safe = windows[0][1].encode("ascii", "replace").decode()
                print(f"Found: {safe}")
                return
            time.sleep(0.5)
        print(f"Timeout ({timeout}s) waiting for: '{args.title}'")
        sys.exit(1)

    elif args.action == "screen-size":
        sw, sh = win_manager.get_screen_size()
        print(f"{sw} {sh}")


def cmd_panel(args):
    """Control the narration side panel."""
    import urllib.request

    port = args.port or 8799
    base = f"http://127.0.0.1:{port}"

    if args.action == "start":
        # Launch narration.py as a background process
        script = os.path.join(TOUR_DIR, "narration.py")
        cmd = [PYTHON, script, "--port", str(port)]
        # On Windows, use `pythonw.exe` (windowless Python) to launch
        # the tkinter GUI without a console window. Falls back to
        # regular python.exe if pythonw doesn't exist.
        pythonw = PYTHON.replace("python.exe", "pythonw.exe")
        if os.path.exists(pythonw):
            cmd[0] = pythonw
        proc = subprocess.Popen(cmd)

        # Wait for the HTTP server to come up
        for _ in range(30):
            try:
                req = urllib.request.Request(f"{base}/health")
                with urllib.request.urlopen(req, timeout=1) as resp:
                    if resp.status == 200:
                        print(f"Panel started (pid={proc.pid}, port={port})")
                        return
            except Exception:
                time.sleep(0.3)
        print(f"Panel process started (pid={proc.pid}) but health check timed out")

    elif args.action == "update":
        payload = {}
        if args.step is not None:
            payload["step"] = args.step
        if args.total is not None:
            payload["total"] = args.total
        if args.title:
            payload["title"] = args.title
        if args.description:
            payload["description"] = args.description.replace("\\n", "\n")
        if args.act:
            payload["act"] = args.act
        if args.status:
            payload["status"] = args.status
        if args.status_color:
            payload["status_color"] = args.status_color
        if args.data:
            # Convert literal \n sequences to actual newlines
            payload["data"] = args.data.replace("\\n", "\n")
        if args.data_color:
            payload["data_color"] = args.data_color

        data = json.dumps(payload).encode("utf-8")
        req = urllib.request.Request(
            f"{base}/update", data=data,
            headers={"Content-Type": "application/json"},
            method="POST",
        )
        try:
            with urllib.request.urlopen(req, timeout=5):
                print("Panel updated")
        except Exception as e:
            print(f"Panel update failed: {e}")

    elif args.action == "stop":
        req = urllib.request.Request(f"{base}/quit", method="POST",
                                     data=b"{}")
        req.add_header("Content-Type", "application/json")
        try:
            with urllib.request.urlopen(req, timeout=5):
                print("Panel stopped")
        except Exception:
            print("Panel may already be stopped")

    elif args.action == "health":
        try:
            req = urllib.request.Request(f"{base}/health")
            with urllib.request.urlopen(req, timeout=3) as resp:
                print(json.loads(resp.read().decode()))
        except Exception:
            print("Panel not running")


# ═══════════════════════════════════════════════════════════════════
# Argument parser
# ═══════════════════════════════════════════════════════════════════

def build_parser():
    parser = argparse.ArgumentParser(
        description="Gallia KiCad Tour CLI — used by Claude Code",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    sub = parser.add_subparsers(dest="cmd")

    # preflight
    sub.add_parser("preflight", help="Check all prerequisites")

    # screenshot
    p = sub.add_parser("screenshot", help="Capture desktop screenshot")
    p.add_argument("output", nargs="?", help="Output PNG path")

    # highlight
    p = sub.add_parser("highlight", help="Show highlight overlay on screen")
    p.add_argument("x", type=int, help="X position")
    p.add_argument("y", type=int, help="Y position")
    p.add_argument("width", type=int, help="Width of highlight")
    p.add_argument("height", type=int, help="Height of highlight")
    p.add_argument("--color", default="#00b8b1", help="Border color")
    p.add_argument("--duration", type=int, default=5000, help="Duration ms")
    p.add_argument("--label", default="", help="Label text")
    p.add_argument("--thickness", type=int, default=3, help="Border px")

    # bridge
    p = sub.add_parser("bridge", help="Send bridge command")
    p.add_argument("command", help="Command name (or 'health')")
    p.add_argument("args", nargs="*", help="key=value pairs")

    # ipc-test
    p = sub.add_parser("ipc-test", help="Run IPC test suite")
    p.add_argument("--section", type=int, help="Show only section N")

    # ipc-layer
    p = sub.add_parser("ipc-layer", help="Set active PCB layer via IPC")
    p.add_argument("layer_id", type=int,
                   help="Layer ID (F.Cu=3, In1.Cu=4, In2.Cu=5, B.Cu=34)")

    # window
    p = sub.add_parser("window", help="Window management")
    p.add_argument("action",
                   choices=["find", "close", "front", "position", "wait",
                            "screen-size"],
                   help="Action to perform")
    p.add_argument("title", nargs="?", default="",
                   help="Window title substring")
    p.add_argument("x", type=int, nargs="?", help="X position")
    p.add_argument("y", type=int, nargs="?", help="Y position")
    p.add_argument("w", type=int, nargs="?", help="Width")
    p.add_argument("h", type=int, nargs="?", help="Height")
    p.add_argument("--timeout", type=int, default=15,
                   help="Wait timeout in seconds")

    # panel
    p = sub.add_parser("panel", help="Control the narration side panel")
    p.add_argument("action", choices=["start", "update", "stop", "health"],
                   help="Panel action")
    p.add_argument("--port", type=int, default=8799, help="Panel HTTP port")
    p.add_argument("--step", type=int, help="Current step number")
    p.add_argument("--total", type=int, help="Total steps")
    p.add_argument("--title", help="Step title")
    p.add_argument("--description", help="Step description / narration")
    p.add_argument("--act", help="Act name")
    p.add_argument("--status", help="Status text")
    p.add_argument("--status-color", dest="status_color", help="Status color")
    p.add_argument("--data", help="Data area content")
    p.add_argument("--data-color", dest="data_color", help="Data area color")

    return parser


def main():
    parser = build_parser()
    args = parser.parse_args()

    handlers = {
        "preflight": cmd_preflight,
        "screenshot": cmd_screenshot,
        "highlight": cmd_highlight,
        "bridge": cmd_bridge,
        "ipc-test": cmd_ipc_test,
        "ipc-layer": cmd_ipc_layer,
        "window": cmd_window,
        "panel": cmd_panel,
    }

    handler = handlers.get(args.cmd)
    if handler:
        handler(args)
    else:
        parser.print_help()


if __name__ == "__main__":
    main()