"""Handler for place_footprint command.

DETERMINISTIC footprint placement onto a PCB — no AI, no fragile protobuf.
Approach: take the footprint's s-expression (a .kicad_mod is already a valid
`(footprint ...)` block), give it a position + uuid, splice it into a known-good
blank KiCad board template, and open the result in pcbnew. Proven 2026-06-25
(kipy get_footprints() confirmed the placed footprint + pad count).

This is a PREVIEW placement (a fresh board with just this footprint) — it never
touches the user's existing board, so it can't clobber their work. The footprint
is also already in the Adom library (via install_footprint), so the user can drag
it into their own board from there.

Args:
    fileName (required):    e.g. "BMI423.kicad_mod"
    fileContent (required): base64-encoded .kicad_mod content
    footprintName (opt):    parsed from the content if omitted
    x, y (opt):             placement in mm (default board-center-ish 148,105)
    open (opt):             open the board in pcbnew (default True)

Returns: {success, boardPath, footprintName, placedAt, opened, output}
"""

import base64
import re
import subprocess
import sys
import uuid
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))
from proc import run as _safe_run  # noqa: E402

_TEMPLATE = Path(__file__).resolve().parent.parent / "templates" / "blank-board.kicad_pcb"


def handle_place_footprint(kicad_info: dict, args: dict) -> dict:
    if not kicad_info.get("installed"):
        return {"success": False, "error": "KiCad not installed",
                "_hint": "Install KiCad from https://www.kicad.org/download/ then retry."}

    file_name = args.get("fileName", "")
    file_content = args.get("fileContent", "")
    if not file_content:
        return {"success": False, "error": "No fileContent (base64 .kicad_mod) specified"}

    try:
        fp_text = base64.b64decode(file_content).decode("utf-8", "replace").strip()
    except Exception as e:
        return {"success": False, "error": f"could not decode fileContent: {e}"}
    # Accept legacy `(module ...)` footprints (KiCad ≤5 / EAGLE-imported / many
    # chip-fetcher parts still ship these). KiCad auto-migrates them on load, but the
    # board *file* parser wants the modern `(footprint ...)` keyword, so normalize:
    # rename the wrapper and quote the top-level (layer X) token the splice relies on.
    if fp_text.startswith("(module"):
        fp_text = re.sub(r'^\(module\b', '(footprint', fp_text, count=1)
        # quote an unquoted top-level layer name: (layer F.Cu) -> (layer "F.Cu")
        fp_text = re.sub(r'\(layer\s+([A-Za-z][\w.]*)\s*\)', r'(layer "\1")', fp_text, count=1)
    if not fp_text.startswith("(footprint"):
        return {"success": False, "error": "fileContent is not a .kicad_mod (must start with '(footprint' or '(module')"}

    fp_name = args.get("footprintName") or ""
    if not fp_name:
        m = re.search(r'\(footprint\s+"([^"]+)"', fp_text)
        fp_name = m.group(1) if m else Path(file_name).stem or "footprint"

    x = args.get("x", 148.0)
    y = args.get("y", 105.0)

    if not _TEMPLATE.exists():
        return {"success": False, "error": f"board template missing at {_TEMPLATE}"}
    skel = _TEMPLATE.read_text(encoding="utf-8")

    # Make the footprint a board item: inject a uuid + position right after its
    # first (layer "...") token, then splice before the board's final paren.
    uid = str(uuid.uuid4())
    fp_board = re.sub(
        r'(\(layer\s+"[^"]+"\))',
        r'\1\n\t\t(uuid "%s")\n\t\t(at %s %s)' % (uid, x, y),
        fp_text, count=1,
    )
    idx = skel.rstrip().rfind(")")
    combined = skel[:idx].rstrip() + "\n" + fp_board + "\n)\n"

    user_dir = kicad_info.get("user_dir") or str(Path.home())
    out_path = Path(user_dir) / f"adom-place-{fp_name}.kicad_pcb"
    try:
        out_path.write_text(combined, encoding="utf-8")
    except OSError as e:
        return {"success": False, "error": f"could not write board: {e}"}

    opened = False
    open_it = args.get("open", True)
    if open_it:
        pcbnew_exe = kicad_info.get("pcbnew_exe")
        if pcbnew_exe and Path(pcbnew_exe).exists():
            try:
                # DETACHED launch — a GUI must NOT go through subprocess.run(timeout=…),
                # which KILLS the child on timeout. Popen fire-and-forget, DETACHED_PROCESS
                # so pcbnew outlives the bridge and inherits no console handle.
                flags = 0x00000008 if sys.platform == "win32" else 0  # DETACHED_PROCESS
                _si = None
                try:
                    from handlers import win_focus
                    _si = win_focus.background_startupinfo()  # SW_SHOWNOACTIVATE — open behind the user
                except Exception:
                    _si = None
                subprocess.Popen(
                    [pcbnew_exe, str(out_path)],
                    creationflags=flags,
                    startupinfo=_si,
                    stdin=subprocess.DEVNULL,
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.DEVNULL,
                    close_fds=True,
                )
                opened = True
            except OSError as e:
                return {"success": True, "boardPath": str(out_path), "footprintName": fp_name,
                        "placedAt": [x, y], "opened": False,
                        "output": f"Board written but pcbnew launch failed: {e}",
                        "_hint": f"Open it manually: {out_path}"}

    return {
        "success": True,
        "boardPath": str(out_path),
        "footprintName": fp_name,
        "placedAt": [x, y],
        "opened": opened,
        "output": f"Placed footprint '{fp_name}' at ({x}, {y}) mm on a preview board"
                  + (" and opened it in pcbnew" if opened else ""),
    }