#!/usr/bin/env python3
"""
gen_resistors_0402.py
=====================
Reads the approved parts list spreadsheet and generates KiCad 9 symbols
for every 0402 resistor into kicad-lib/symbols/Res_0402.kicad_sym.

The existing R_temp template symbol is kept in the file.  One new symbol
is added per row, named by the internal part number (GLE P/N), with:
    Value       ← Value1   (e.g. "10k", "4.7k", "100")
    Description ← Description column
    UMPN        ← GLE P/N
    MFG         ← Mfg.1
    MPN         ← Mfg.1 P/N

All geometry and pin definitions are copied verbatim from R_temp.

Usage:
    python3 gen_resistors_0402.py <parts_list.xlsx> <Res_0402.kicad_sym>
"""

import re
import sys
import pandas as pd
from pathlib import Path


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def kicad_str(value: str) -> str:
    """Escape a string for use inside a KiCad S-expression quoted string."""
    return str(value).replace('\\', '\\\\').replace('"', '\\"')


def make_symbol(name: str, value: str, description: str,
                umpn: str, mfg: str, mpn: str,
                geometry_body: str) -> str:
    """
    Render a complete (symbol ...) block.

    geometry_body is the text of the two inner sub-symbols
    (originally named R_temp_0_1 and R_temp_1_1) with the name
    already substituted.
    """
    return f'''\t(symbol "{kicad_str(name)}"
\t\t(pin_numbers
\t\t\t(hide yes)
\t\t)
\t\t(pin_names
\t\t\t(offset 0)
\t\t)
\t\t(exclude_from_sim no)
\t\t(in_bom yes)
\t\t(on_board yes)
\t\t(property "Reference" "R"
\t\t\t(at 2.54 0 90)
\t\t\t(effects
\t\t\t\t(font
\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t)
\t\t\t)
\t\t)
\t\t(property "Value" "{kicad_str(value)}"
\t\t\t(at -2.54 0 90)
\t\t\t(effects
\t\t\t\t(font
\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t)
\t\t\t)
\t\t)
\t\t(property "Footprint" "Resistor_SMD:R_0402_1005Metric"
\t\t\t(at 1.016 -0.254 90)
\t\t\t(effects
\t\t\t\t(font
\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t)
\t\t\t\t(hide yes)
\t\t\t)
\t\t)
\t\t(property "Datasheet" "~"
\t\t\t(at 0 0 0)
\t\t\t(effects
\t\t\t\t(font
\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t)
\t\t\t\t(hide yes)
\t\t\t)
\t\t)
\t\t(property "Description" "{kicad_str(description)}"
\t\t\t(at 0 0 0)
\t\t\t(effects
\t\t\t\t(font
\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t)
\t\t\t\t(hide yes)
\t\t\t)
\t\t)
\t\t(property "MPN" "{kicad_str(mpn)}"
\t\t\t(at 0 0 0)
\t\t\t(effects
\t\t\t\t(font
\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t)
\t\t\t\t(hide yes)
\t\t\t)
\t\t)
\t\t(property "MFG" "{kicad_str(mfg)}"
\t\t\t(at 0 0 0)
\t\t\t(effects
\t\t\t\t(font
\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t)
\t\t\t\t(hide yes)
\t\t\t)
\t\t)
\t\t(property "UMPN" "{kicad_str(umpn)}"
\t\t\t(at 0 0 0)
\t\t\t(effects
\t\t\t\t(font
\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t)
\t\t\t\t(hide yes)
\t\t\t)
\t\t)
\t\t(property "ki_keywords" "R res resistor"
\t\t\t(at 0 0 0)
\t\t\t(effects
\t\t\t\t(font
\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t)
\t\t\t\t(hide yes)
\t\t\t)
\t\t)
\t\t(property "ki_fp_filters" "R_*"
\t\t\t(at 0 0 0)
\t\t\t(effects
\t\t\t\t(font
\t\t\t\t\t(size 1.27 1.27)
\t\t\t\t)
\t\t\t\t(hide yes)
\t\t\t)
\t\t)
{geometry_body}
\t\t(embedded_fonts no)
\t)'''


# ---------------------------------------------------------------------------
# Extract geometry from R_temp
# ---------------------------------------------------------------------------

def extract_geometry(sym_text: str) -> str:
    """
    Pull out the two inner sub-symbol blocks (R_temp_0_1 and R_temp_1_1)
    from the raw file text and return them as a single string with the
    'R_temp' name prefix replaced by a placeholder that callers substitute.
    """
    # Match (symbol "R_temp_0_1" ...) and (symbol "R_temp_1_1" ...)
    # by tracking brace depth after the opening paren of each sub-symbol.
    results = []
    for sub in ('R_temp_0_1', 'R_temp_1_1'):
        pattern = f'(symbol "{sub}"'
        start = sym_text.find(pattern)
        if start == -1:
            raise ValueError(f"Could not find sub-symbol '{sub}' in template")
        # Walk forward to find the matching closing paren
        depth = 0
        i = start
        while i < len(sym_text):
            if sym_text[i] == '(':
                depth += 1
            elif sym_text[i] == ')':
                depth -= 1
                if depth == 0:
                    results.append(sym_text[start:i + 1])
                    break
            i += 1
    return '\n'.join(results)


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

def main(xlsx_path: Path, sym_path: Path):
    # ---- Load spreadsheet ----
    df = pd.read_excel(xlsx_path, sheet_name='PCB', dtype=str)
    df = df.fillna('')

    # Filter to 0402 resistors
    mask = (
        df['Footprint'].str.contains('0402', na=False) &
        df['Description'].str.contains('[Rr]es', na=False, regex=True)
    )
    resistors = df[mask].copy()
    print(f"Found {len(resistors)} 0402 resistors in parts list")

    # ---- Read existing symbol file ----
    existing = sym_path.read_text(encoding='utf-8')

    # Extract geometry from R_temp once
    raw_geom = extract_geometry(existing)

    # ---- Build new symbols ----
    new_symbols = []
    skipped = []

    for _, row in resistors.iterrows():
        gle_pn      = str(row['GLE P/N']).strip()
        value       = str(row['Value1']).strip()
        description = str(row['Description']).strip()
        mfg         = str(row['Mfg.1']).strip()
        mpn         = str(row['Mfg.1 P/N']).strip()

        if not gle_pn:
            skipped.append(('(no GLE P/N)', value))
            continue

        # Substitute R_temp → this symbol's name in the geometry block.
        # Also re-indent so sub-symbol opening parens align with the template.
        geom = raw_geom.replace('R_temp_0_1', f'{gle_pn}_0_1') \
                       .replace('R_temp_1_1', f'{gle_pn}_1_1')
        # Each sub-symbol block starts at column 0 after extract; add two tabs.
        geom = '\n'.join('\t\t' + line if line.startswith('(symbol ') else line
                         for line in geom.splitlines())

        sym = make_symbol(
            name=gle_pn,
            value=value,
            description=description,
            umpn=gle_pn,
            mfg=mfg,
            mpn=mpn,
            geometry_body=geom,
        )
        new_symbols.append(sym)

    print(f"Generated {len(new_symbols)} symbols  ({len(skipped)} skipped)")

    # ---- Rebuild file from scratch (idempotent) ----
    # Extract the library header lines (everything before the first symbol)
    # and keep only the R_temp template symbol, then append generated symbols.
    header_end = existing.find('\n\t(symbol "R_temp"')
    if header_end == -1:
        raise ValueError("Could not find R_temp template in symbol file")
    header = existing[:header_end]

    # Extract R_temp block by brace depth
    rt_start = existing.index('\n\t(symbol "R_temp"')
    depth, i = 0, rt_start + 1  # skip leading newline
    while i < len(existing):
        if existing[i] == '(':
            depth += 1
        elif existing[i] == ')':
            depth -= 1
            if depth == 0:
                rt_block = existing[rt_start:i + 1]
                break
        i += 1

    lib_body = header + rt_block + '\n' + '\n'.join(new_symbols) + '\n)'

    # Back up original
    backup = sym_path.with_suffix('.bak')
    backup.write_text(existing, encoding='utf-8')
    print(f"Backup written to {backup.name}")

    sym_path.write_text(lib_body, encoding='utf-8')
    print(f"Wrote {sym_path}")

    # Quick sanity check
    symbol_count = lib_body.count('\n\t(symbol "')
    print(f"Total symbols in file (including R_temp): {symbol_count}")


if __name__ == '__main__':
    if len(sys.argv) == 3:
        xlsx = Path(sys.argv[1])
        sym  = Path(sys.argv[2])
    else:
        # Default paths relative to repo root
        repo = Path(__file__).parent.parent
        xlsx = repo.parent / 'parts_list_pcb.xlsx'
        sym  = repo / 'symbols' / 'Res_0402.kicad_sym'

    if not xlsx.exists():
        print(f"Error: spreadsheet not found at {xlsx}", file=sys.stderr)
        sys.exit(1)
    if not sym.exists():
        print(f"Error: symbol file not found at {sym}", file=sys.stderr)
        sys.exit(1)

    main(xlsx, sym)
