from pathlib import Path
import sys,re,json,hashlib,csv,xml.etree.ElementTree as E
R=Path(__file__).resolve().parent;D=R/'power_board';P=D/'lamp_buck_lmr51450_a5_ctrl3v3.kicad_pcb'
sys.path.insert(0,str(R/'verification_support'))
from prepare_candidates import forms
from align_and_verify import netlist,footprint_rows
fps,pins=netlist(D/'netlist.xml');_,oldpins=netlist(R/'baseline/netlist.xml')
assert all(pins[k]==n for k,n in oldpins.items()),'existing electrical mapping changed'
expected={('U4','1'):'/SYS_5V0_ALWAYS_ON',('U4','2'):'/GND',('U4','3'):'/SYS_5V0_ALWAYS_ON',('U4','5'):'/CTRL_3V3',('C15','1'):'/SYS_5V0_ALWAYS_ON',('C15','2'):'/GND',('C16','1'):'/CTRL_3V3',('C16','2'):'/GND'}
assert all(pins[k]==n for k,n in expected.items());assert pins['U4','4'].startswith('unconnected-')
s=P.read_text();seen={}
for _,_,f,ref,_ in footprint_rows(s):
 for _,_,pad in forms(f,'pad'):
  pin=re.match(r'\(pad "([^"]+)"',pad)[1];n=re.search(r'\(net "([^"]+)"',pad)
  if n:seen[(ref,pin)]=n[1].replace('{slash}','/')
assert all(seen[k]==n for k,n in pins.items() if k[0] not in ['C9','C10','D1','R5'])
# PCB serialization can reorder properties; only normalise that and whitespace.
def canonical(f,tag):
 if tag=='footprint':
  props=[]
  for x,y,p in reversed(list(forms(f,'property'))):props.append(p);f=f[:x]+f[y:]
  f+=''.join(sorted(props))
 return re.sub(r'\s+',' ',f)
def records(text,tag):return {re.search(r'\(uuid "([^"]+)"',f)[1]:canonical(f,tag) for _,_,f in forms(text,tag)}
old=(R/'baseline/lamp_buck_lmr51450_a4_frontend.kicad_pcb').read_text();changes={}
for tag in ['footprint','segment','via']:
 before=records(old,tag);after=records(s,tag)
 assert not before.keys()-after.keys(),tag
 assert all(before[k]==after[k] for k in before),tag+' existing geometry or metadata changed'
 changes[tag]=len(after.keys()-before.keys())
assert changes['footprint']==3
j=json.loads((D/'drc.json').read_text());assert not j['violations'] and not j['unconnected_items']
assert len(j['schematic_parity'])==4 and all(v['type']=='missing_footprint' and 'DNP' in v['description'] for v in j['schematic_parity'])
e=json.loads((D/'erc.json').read_text());assert not [v for sh in e['sheets'] for v in sh.get('violations',[])]
_,sp=netlist(R/'safety_A39_netlist.xml');_,dp=netlist(R/'driver_A59_netlist.xml');rows=[]
for i in range(1,6):
 assert pins['J2',str(i)]==sp['J3',str(i)];rows.append(['power J2.'+str(i),'safety J3.'+str(i),pins['J2',str(i)]])
for i in range(1,7):
 sn=sp['J2',str(i)];dn=dp['J1',str(i)]
 assert sn==dn or (i==1 and sn=='/CTRL_3V3' and dn=='/VDD_3V3');rows.append(['safety J2.'+str(i),'driver J1.'+str(i),sn])
for i,jpin in [(1,7),(2,8)]:
 assert pins['J3',str(i)]==dp['J1',str(jpin)];rows.append(['power J3.'+str(i),'driver J1.'+str(jpin),pins['J3',str(i)]])
with (R/'cross_board_connections.csv').open('w',newline='') as f:
 w=csv.writer(f);w.writerow(['from','to','net']);w.writerows(rows)
report={'status':'A69_CONTROL_RAIL_SOURCE_NATIVE_CAD_PASS_FABRICATION_HOLD','ERC':0,'DRC':0,'unconnected_items':0,'schematic_parity_DNP_warnings':4,'existing_electrical_mapping_preserved':True,'existing_footprints_segments_vias_preserved':True,'added_records':changes,'connected_pad_mapping_checked':len(seen),'cross_board_pairs':len(rows),'CTRL_3V3_source':'U4 AP2112K-3.3TRG1 from SYS_5V0_ALWAYS_ON','PCB_sha256':hashlib.sha256(P.read_bytes()).hexdigest(),'physical_validation':False,'manufacturing':'HOLD'}
(R/'checks.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report,indent=2))
