#!/usr/bin/env python3
"""Generate a consolidated BOM from the integrated board.net.

Run INSIDE the EDA container:
    cd hardware/eda && ./run.sh python3 ../eda/gen_bom.py
Reads hardware/kicad/board.net, groups like parts, attaches MPNs, writes
hardware/BOM_board.csv (authoritative part list keyed to the board.net references).
"""
import re, csv, os, collections

ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
NET = os.path.join(ROOT, "hardware/kicad/board.net")
OUT = os.path.join(ROOT, "hardware/BOM_board.csv")

# value -> (manufacturer, MPN, note)
MPN = {
 "RP2350A": ("Raspberry Pi", "RP2350A", "MCU (QFN-60); via KiCad lib symbol"),
 "W25Q128JVS": ("Winbond", "W25Q128JVSIQ", "16MB QSPI flash"),
 "PCM5102A": ("TI", "PCM5102APWR", "I2S DAC; SCK->GND (MCLK-less)"),
 "THAT1240": ("THAT Corp", "THAT1240S08-U", "0dB balanced line receiver; 2nd-src INA134/SSM2141"),
 "THAT1646": ("THAT Corp", "THAT1646S08-U", "balanced line driver, +6dB; 2nd-src DRV134/SSM2142"),
 "OPA1641": ("TI", "OPA1641AID", "JFET Hi-Z DI buffer"),
 "OPA1612": ("TI", "OPA1612AIDR", "dual: recon filter + summer"),
 "TPS65131": ("TI", "TPS65131RGER", "dual boost/inverter -> +/-18V"),
 "TPS7A4901": ("TI", "TPS7A4901DGNR", "+15V ultra-low-noise LDO; confirm Vfb"),
 "TPS7A3001": ("TI", "TPS7A3001DGNR", "-15V ultra-low-noise LDO; confirm Vfb"),
 "AP2112K-3.3": ("Diodes", "AP2112K-3.3TRG1", "3V3 LDO; confirm SOT-23-5 pinout"),
 "ULN2003A": ("TI", "ULN2003ADR", "shared relay driver (3 of 7 ch used)"),
 "RV-8803-C7": ("Micro Crystal", "RV-8803-C7", "I2C RTC; confirm footprint"),
 "LM393": ("TI", "LM393DR", "DNP - SIG/CLIP comparator"),
 "H11L1": ("Vishay", "H11L1M", "DNP - MIDI opto IN; confirm pinout"),
 "74LVC14": ("Nexperia", "74LVC14APW", "DNP - MIDI OUT/THRU buffer"),
 "PAM8302A": ("Diodes", "PAM8302AASCR", "DNP - monitor speaker amp"),
 "USBLC6-2SC6": ("STMicro", "USBLC6-2SC6", "USB ESD"),
 "TQ2SA-5V": ("Panasonic", "TQ2SA-5V", "DPDT signal relay, gold; K1 select/K2 mute/K3 gnd-lift"),
 "12MHz": ("Abracon", "ABM8-272-12.000MHZ-T3", "RP2350 crystal; confirm load caps"),
 "USB_C_Receptacle": ("GCT", "USB4085-GF-A", "USB-C; 24-pin sym vs 16-pin part - resolve at layout"),
 "CR2032": ("Keystone", "1066", "coin-cell holder (RTC backup)"),
 "MBRM120": ("onsemi", "MBRM120ET3G", "Schottky rectifier (switcher)"),
 "BAT54": ("onsemi", "BAT54", "Schottky (RTC diode-OR / peak-detect)"),
 "1N4148WS": ("onsemi", "1N4148WS", "fast diode (input clamps / MIDI protect)"),
 "4.7uH": ("Wurth/EPCOS", "7447789004 / B82462-G4472", "switcher inductor"),
 "3.3uH": ("Abracon", "AOTA-B201610S3R3-101-T", "RP2350 core SMPS inductor"),
 "600R": ("Murata", "BLM18KG..", "ferrite bead (USB VBUS input)"),
}

comps = re.findall(r'\(comp\s*\(ref "([^"]+)"\)\s*\(value "([^"]+)"\)(.*?)\(libsource', open(NET).read(), re.S)
def fp(blk):
    m = re.search(r'\(footprint "([^"]+)"', blk); return m.group(1) if m else ""

groups = collections.OrderedDict()
for ref, val, blk in comps:
    key = (val, fp(blk))
    groups.setdefault(key, []).append(ref)

def sortkey(r):
    m = re.match(r'([A-Za-z]+)(\d+)', r); return (m.group(1), int(m.group(2))) if m else (r, 0)

rows = []
for (val, foot), refs in groups.items():
    refs = sorted(refs, key=sortkey)
    man, mpn, note = MPN.get(val, ("", "", ""))
    if "Potentiometer" in foot:                       # the level-cal trimmer (value "10k" but a pot)
        man, mpn, note = ("Bourns", "3296W-1-103LF", "output level cal trim (25-turn)")
    # heuristic DNP flag
    dnp = any(s in note for s in ("DNP",))
    rows.append([len(refs), " ".join(refs), val, foot, man, mpn, ("DNP" if dnp else ""), note])

rows.sort(key=lambda r: (r[6] == "DNP", -r[0], r[2]))   # populated first, then by qty
with open(OUT, "w", newline="") as f:
    w = csv.writer(f)
    w.writerow(["Qty", "Refs", "Value", "Footprint", "Manufacturer", "MPN", "Populate", "Notes"])
    w.writerows(rows)

print("wrote", OUT)
print("line items:", len(rows), " total placements:", sum(r[0] for r in rows))
