#!/usr/bin/env python3
"""Copy authoritative schematic fields to an independent camera-EVB PCB candidate."""
from __future__ import annotations

import hashlib
import json
from pathlib import Path
import re
import subprocess
import xml.etree.ElementTree as ET

import wx
import pcbnew

_wx_app = wx.App(False)
HERE = Path(__file__).resolve().parent
SOURCE = HERE.parent / "v1.40-board-audit-2026-10-09.nosync/localized-sources/lamp_vision_soc/camera_evb_a0"
CLI = Path("/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli")
SCH = HERE / "camera_evb_a0.kicad_sch"
PCB = HERE / "camera_evb_a0.kicad_pcb"
NETLIST = HERE / "camera_evb_a0.xml"


def make_tp1_dnp(path: Path) -> None:
    """A bare copper test pad is not a BOM component; align its symbol with PCB."""
    text = path.read_text(encoding="utf-8")
    marker = '(property "Reference" "TP1"'
    prop_at = text.find(marker)
    if prop_at < 0:
        raise RuntimeError("TP1 reference property not found")
    block_start = text.rfind("(symbol", 0, prop_at)
    if block_start < 0:
        raise RuntimeError("TP1 symbol block not found")
    block_end = sexpr_block_end(text, block_start)
    block = text[block_start:block_end]
    if marker not in block:
        raise RuntimeError("TP1 reference was not inside the selected symbol block")
    updated, count = re.subn(r"\(in_bom\s+yes\)", "(in_bom no)", block, count=1)
    if count != 1:
        if "(in_bom no)" in block:
            return
        raise RuntimeError("TP1 schematic in_bom setting was not the expected value")
    path.write_text(text[:block_start] + updated + text[block_end:], encoding="utf-8")


def sexpr_block_end(text: str, start: int) -> int:
    depth = 0
    in_string = False
    escaped = False
    for index in range(start, len(text)):
        char = text[index]
        if in_string:
            if escaped:
                escaped = False
            elif char == "\\":
                escaped = True
            elif char == '"':
                in_string = False
            continue
        if char == '"':
            in_string = True
        elif char == "(":
            depth += 1
        elif char == ")":
            depth -= 1
            if depth == 0:
                return index + 1
    raise RuntimeError("Could not find end of KiCad S-expression block")


def create_local_symbol_libraries(path: Path) -> list[str]:
    text = path.read_text(encoding="utf-8")
    lib_start = text.find("(lib_symbols")
    if lib_start < 0:
        raise RuntimeError("Schematic has no embedded library symbols")
    lib_end = sexpr_block_end(text, lib_start)
    embedded = text[lib_start:lib_end]
    libraries = {
        "Core1106": ["Core1106"],
        "CameraEVB": ["Conn_01x20", "Conn_01x04", "Conn_01x02", "R", "TestPoint", "PWR_FLAG"],
    }
    out = HERE / "symbols"
    out.mkdir(exist_ok=True)
    generated = []
    for library, names in libraries.items():
        symbols = []
        for name in names:
            marker = f'(symbol "{library}:{name}"'
            start = embedded.find(marker)
            if start < 0:
                raise RuntimeError(f"Embedded symbol not found: {library}:{name}")
            end = sexpr_block_end(embedded, start)
            block = embedded[start:end]
            block = block.replace(marker, f'(symbol "{name}"', 1)
            symbols.append(block)
        output = '(kicad_symbol_lib (version 20250114) (generator "kicad_symbol_editor")\n' + "\n".join(symbols) + "\n)\n"
        (out / f"{library}.kicad_sym").write_text(output, encoding="utf-8")
        generated.append(f"symbols/{library}.kicad_sym")
    return generated


make_tp1_dnp(SCH)
generated_libraries = create_local_symbol_libraries(SCH)
subprocess.run([
    str(CLI), "sch", "export", "netlist", "--format", "kicadxml",
    "--output", str(NETLIST), str(SCH),
], check=True)
root = ET.parse(NETLIST).getroot()
components = {comp.get("ref"): comp for comp in root.findall("./components/comp")}
board = pcbnew.LoadBoard(str(PCB))
footprints = {fp.GetReference(): fp for fp in board.GetFootprints()}
source_board = pcbnew.LoadBoard(str(SOURCE / "camera_evb_a0.kicad_pcb"))
source_footprints = {fp.GetReference(): fp for fp in source_board.GetFootprints()}
updates = []
for ref, component in components.items():
    fp = footprints.get(ref)
    if fp is None:
        continue
    value = component.findtext("value") or ""
    footprint_id = component.findtext("footprint") or ""
    fields = {field.get("name"): field.text or "" for field in component.findall("./fields/field")}
    description = fields.get("Description", "")
    source_fp = source_footprints[ref]
    changes = {}
    if source_fp.GetValue() != value:
        changes["value"] = {"old": source_fp.GetValue(), "new": value}
    if fp.GetValue() != value:
        fp.SetValue(value)
    if source_fp.GetFPIDAsString() != footprint_id:
        changes["footprint_id"] = {"old": source_fp.GetFPIDAsString(), "new": footprint_id}
    if footprint_id and fp.GetFPIDAsString() != footprint_id:
        fp.SetFPIDAsString(footprint_id)
    description_field = next((field for field in fp.GetFields() if field.GetName() == "Description"), None)
    source_description = next((field.GetText() for field in source_fp.GetFields() if field.GetName() == "Description"), "")
    if source_description != description:
        changes["description"] = {"old": source_description, "new": description}
    if description_field is not None and description_field.GetText() != description:
        description_field.SetText(description)
    if ref == "TP1" and not source_fp.IsExcludedFromBOM():
        changes["exclude_from_bom"] = {"old": False, "new": True}
    if ref == "TP1" and not fp.IsExcludedFromBOM():
        fp.SetExcludedFromBOM(True)
    if changes:
        updates.append({"reference": ref, "changes": changes})

pcbnew.SaveBoard(str(PCB), board)
report = {
    "status": "METADATA_ONLY_CANDIDATE_UPDATED",
    "schematic_tp1_in_bom": False,
    "board_tp1_excluded_from_bom": next(fp for fp in board.GetFootprints() if fp.GetReference() == "TP1").IsExcludedFromBOM(),
    "generated_local_symbol_libraries": generated_libraries,
    "footprint_metadata_updates": updates,
    "source_sha256": {
        str(path.relative_to(SOURCE)): hashlib.sha256(path.read_bytes()).hexdigest()
        for path in [SOURCE / "camera_evb_a0.kicad_sch", SOURCE / "camera_evb_a0.kicad_pcb"]
    },
    "electrical_or_geometry_changes": False,
    "qualification": {
        "product_fit_verified": False,
        "impedance_verified": False,
        "signal_integrity_verified": False,
        "hardware_safety_verified": False,
        "manufacturing_release": False,
    },
}
(HERE / "metadata_sync_candidate.json").write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(json.dumps(report, ensure_ascii=False, indent=2))
