"""KiCad automation-capability reporting (version-awareness).

The bridge should be able to tell the user, in plain terms:
  - what automation THEIR installed KiCad supports,
  - what's gated behind enabling the API server,
  - and when upgrading KiCad would unlock more deterministic automation.

This is deliberately separate from kicad_detect.py: detection answers
"what's installed and where"; this answers "what can we actually drive,
and what should the user do to get more."

Ground truth (measured against KiCad 10.0.1 on 2026-06-25, and the
public KiCad IPC-API roadmap):

  * KiCad < 9.0  → no IPC API at all. Only kicad-cli (headless convert/
    lint/erc/drc) + GUI launch. Symbol/footprint *delivery* works (file +
    lib-table), but nothing can be driven on the canvas.
  * KiCad 9.0 / 10.x → IPC API present and **board automation WORKS**
    (get_board() → place via create_items()+push_commit()). CRITICAL gotcha
    (measured 10.0.1, 2026-06-25): KiCad exposes ONE API socket, owned by
    whichever instance bound it. If the PROJECT MANAGER (kicad.exe) owns it,
    board commands return "no handler available". When **pcbnew is the
    API-owning instance**, get_board() works. So deterministic footprint
    placement requires routing IPC to a pcbnew that owns the socket.
    The schematic/symbol editor has NO IPC handler yet, so opening a
    *symbol* on the canvas stays assisted (Win32) automation.
  * KiCad 11 (when it ships) → SWIG bindings removed; IPC is the only
    Python path and broadens to the schematic editor. Confirmed against
    kipy (kicad-python): `KiCad.get_schematic()` is tagged "Added in …
    (KiCad 11)" — the schematic object is NOT in KiCad 10.x, so on 10.x
    symbol/schematic work stays assisted (Win32) automation. When 11 lands,
    symbol delivery flips from "assisted" to "ipc" with no caller change —
    they just read `automation.symbol`. (Verified 2026-07: get_board() is
    the only document object exposed by kipy on 10.0.x.)

The IPC API also requires the user to ENABLE the API server
(Preferences > Plugins > "Enable KiCad API server"); we detect that from
kicad_common.json so we can tell them (or fix it) instead of timing out.
"""

import json
import re
from pathlib import Path

# Latest stable KiCad major we know about, for "is an upgrade available?"
# messaging. Bump when a new stable major ships. (KiCad 10 is current as of
# 2026-06; 11 removes SWIG and is expected to broaden IPC.)
LATEST_KNOWN_STABLE_MAJOR = 10


def _version_major_minor(version: str) -> tuple[int, int]:
    m = re.match(r"^(\d+)(?:\.(\d+))?", str(version or ""))
    if not m:
        return (0, 0)
    return (int(m.group(1)), int(m.group(2) or 0))


def _api_server_enabled(config_dir: str | None) -> bool | None:
    """Read api.enable_server from <config_dir>/kicad_common.json.

    Returns True/False if we can read it, or None if the file/field is
    missing (older KiCad without the setting, or unreadable). The bridge
    needs this on for any IPC call to connect — when it's off, every IPC
    verb otherwise times out silently.
    """
    if not config_dir:
        return None
    common = Path(config_dir) / "kicad_common.json"
    if not common.exists():
        return None
    try:
        data = json.loads(common.read_text(encoding="utf-8", errors="replace"))
    except (OSError, ValueError):
        return None
    api = data.get("api")
    if not isinstance(api, dict) or "enable_server" not in api:
        return None
    return bool(api.get("enable_server"))


def capability_for_version(info: dict) -> dict:
    """Build the capability report for a single installed KiCad version."""
    version = info.get("version") or "0.0"
    major, _minor = _version_major_minor(version)

    ipc_supported = major >= 9          # KiCad 9.0+ ships the IPC API server
    api_enabled = _api_server_enabled(info.get("config_dir"))

    # What can each artifact kind be driven by, on THIS version?
    #   "ipc"      → deterministic via the IPC API (best path; no AI). Verified
    #               working on 10.0.1 — get_board()+create_items()+push_commit,
    #               PROVIDED pcbnew (not the project manager) owns the API socket.
    #   "assisted" → IPC can't do it; needs assisted/Win32 automation
    #   "cli"      → only file delivery + headless kicad-cli; no canvas drive
    fp_board = "ipc" if ipc_supported else "cli"
    automation = {
        "footprint": fp_board,
        "board": fp_board,
        "symbol": "assisted",          # no schematic/symbol IPC handler yet
        "schematic": "assisted",
    }

    # Upgrade guidance — the "hey, your KiCad doesn't have the automation
    # APIs, let's upgrade you" message the user asked for.
    upgrade = None
    if not ipc_supported:
        upgrade = {
            "recommended": True,
            "reason": (
                f"KiCad {version} predates the IPC automation API (added in 9.0). "
                f"Upgrading unlocks deterministic, no-AI footprint/board automation "
                f"and live canvas control."
            ),
            "unlocks": ["ipc_api", "deterministic_footprint", "deterministic_board"],
        }
    elif major < LATEST_KNOWN_STABLE_MAJOR:
        upgrade = {
            "recommended": False,
            "reason": (
                f"KiCad {version} has the IPC API but a newer stable "
                f"(KiCad {LATEST_KNOWN_STABLE_MAJOR}+) may broaden automation."
            ),
            "unlocks": [],
        }

    notes = []
    if ipc_supported and api_enabled is False:
        notes.append(
            "IPC API server is DISABLED (Preferences > Plugins > 'Enable KiCad "
            "API server'). Enable it (the bridge can do this for you) or every "
            "IPC automation call will hang."
        )
    if ipc_supported and automation["symbol"] == "assisted":
        notes.append(
            "Symbol-editor IPC is not exposed in this KiCad yet, so opening a "
            "symbol on the canvas uses assisted automation. It will become "
            "deterministic automatically when KiCad ships the schematic IPC API."
        )

    return {
        "version": version,
        "ipcApiSupported": ipc_supported,      # does this KiCad have the API at all
        "ipcApiServerEnabled": api_enabled,    # True/False/None (None = unknown/older)
        "ipcClientInstalled": bool(info.get("ipcApiAvailable")),  # kipy importable
        "automation": automation,
        "upgrade": upgrade,
        "notes": notes,
    }


def build_capability_report(all_kicad_versions: list[dict]) -> dict:
    """Top-level capability map across all installed KiCad versions.

    Shape:
        {
          "kicadInstalled": bool,
          "latestInstalled": "10.0" | None,
          "perVersion": [ capability_for_version(...) , ... ],
          "summary": "<one-line human summary for the newest install>",
        }
    """
    if not all_kicad_versions:
        return {
            "kicadInstalled": False,
            "latestInstalled": None,
            "perVersion": [],
            "summary": (
                "KiCad is not installed. The bridge can install it for you "
                "(winget) — ask to 'install KiCad'."
            ),
        }

    per_version = [capability_for_version(v) for v in all_kicad_versions]
    newest = per_version[0]
    nv = newest["version"]

    if not newest["ipcApiSupported"]:
        summary = (
            f"KiCad {nv} is installed but too old for the automation API. "
            f"Upgrade to KiCad {LATEST_KNOWN_STABLE_MAJOR}+ to unlock deterministic automation."
        )
    elif newest["ipcApiServerEnabled"] is False:
        summary = (
            f"KiCad {nv} supports IPC automation, but the API server is OFF. "
            f"Enable it to drive KiCad (the bridge can do this for you)."
        )
    else:
        summary = (
            f"KiCad {nv}: IPC automation ready. Footprints/boards are deterministic; "
            f"symbols use assisted automation until KiCad exposes the schematic IPC API."
        )

    return {
        "kicadInstalled": True,
        "latestInstalled": nv,
        "perVersion": per_version,
        "summary": summary,
    }