from pathlib import Path
import uuid,re,json,shutil
P=Path(__file__).resolve().parent; P.mkdir(exist_ok=True)
SRC=P/'baseline'; NAME='drill_motor_a85_cutoff'; ROOT='8bddf17b-6b2d-4f6e-849c-d7c3b90fe3ea'
NS=uuid.UUID('d21b2130-00cc-4fb1-b1c5-581e88e908bc')
def uid(k):return str(uuid.uuid5(NS,k))
def end(s,a):
 d=0;q=False;esc=False
 for i in range(a,len(s)):
  c=s[i]
  if q:
   if esc:esc=False
   elif c=='\\':esc=True
   elif c=='"':q=False
  elif c=='"':q=True
  elif c=='(':d+=1
  elif c==')':
   d-=1
   if not d:return i+1
 raise ValueError('unbalanced')
def prop(n,v,x,y,hide=False):return f'(property "{n}" "{v}" (at {x} {y} 0) (effects (font (size 1 1)){" (hide yes)" if hide else ""}))'
s=(SRC/'drill_motor_evt_a4_bus_divider.kicad_sch').read_text()
s=s.replace('(paper "A2")','(paper "A0")').replace('project "drill_motor_evt_a4_bus_divider"',f'project "{NAME}"')
s=s.replace('Drill motor stage A1 RUN-GATE FIX','A85 DRILL MOTOR - CPU INDEPENDENT CUTOFF CANDIDATE').replace('(rev "A1-RUN-GATE")','(rev "A85-SCH-ONLY")')
s=s.replace('6x PWM cycle-by-cycle current-limit mode. Divider sets about 2A for prototype; verify actual motor and stall current.','A82 FEEDBACK mode: no CBC. R2=0R, R3=DNP; A85 window/latch remains UNQUALIFIED. No motor energization.')
s=s.replace('DRILL MOTOR POWER STAGE - ENGINEERING PROTOTYPE A1 RUN-GATE FIX','A85 MOTOR POWER STAGE + SIX WINDOWS + MANUAL BENCH ARM - NOT FOR FABRICATION')
s=s.replace('Q1 sinks DRVOFF only when DRV_RUN_REQ is high. Gate low/reset leaves the driver disabled.','Q1 sinks DRVOFF only from gated RUN. J4 is a RAW request and MUST NOT bypass A85 latch.')
# Replace only the label on R5 input, preserving both raw RUN connectors.
a=s.index('(property "Reference" "R5"'); a=s.index('(label "DRV_RUN_REQ"',a)
s=s[:a]+s[a:].replace('(label "DRV_RUN_REQ"','(label "DRV_RUN_GATED"',1)
libs=[];items=[];components=[];connections={}
def library(key,pins):
 # Pin tuple: number,name,electrical_type,side,index. Schematic y flips local y.
 h=max(5.08,max(abs(p[4]) for p in pins)+2.54)
 out=f'(symbol "Project:{key}" (pin_names (offset 0.7)) (in_bom yes) (on_board yes) '+prop('Reference','U',0,0)+prop('Value',key,0,0)+f'(symbol "{key}_0_1" (rectangle (start -10.16 {h}) (end 10.16 {-h}) (stroke (width 0.254) (type default)) (fill (type background)))) (symbol "{key}_1_1" '
 for num,name,typ,side,y in pins:
  x=-15.24 if side=='L' else 15.24;ang=0 if side=='L' else 180
  out+=f'(pin {typ} line (at {x} {y} {ang}) (length 5.08) (name "{name}" (effects (font (size 0.9 0.9)))) (number "{num}" (effects (font (size 0.9 0.9)))))'
 out+='))';libs.append(out);return pins
comp_pins=library('TLV9024_PW',[(4,'IN1-', 'input','L',17.78),(5,'IN1+','input','L',12.7),(6,'IN2-','input','L',7.62),(7,'IN2+','input','L',2.54),(8,'IN3-','input','L',-2.54),(9,'IN3+','input','L',-7.62),(10,'IN4-','input','L',-12.7),(11,'IN4+','input','L',-17.78),(3,'V+','power_in','R',17.78),(2,'OUT1','open_collector','R',7.62),(1,'OUT2','open_collector','R',2.54),(14,'OUT3','open_collector','R',-2.54),(13,'OUT4','open_collector','R',-7.62),(12,'V-','power_in','R',-17.78)])
sh_pins=library('LVC2G17_DBV',[(1,'1A','input','L',5.08),(3,'2A','input','L',-5.08),(6,'1Y','output','R',5.08),(4,'2Y','output','R',-5.08),(5,'VCC','power_in','R',12.7),(2,'GND','power_in','R',-12.7)])
ff_pins=library('LVC1G74_DCU',[(1,'CLK','input','L',10.16),(2,'D','input','L',5.08),(6,'CLR_N','input','L',0),(7,'PRE_N','input','L',-5.08),(5,'Q','output','R',5.08),(3,'Q_N','output','R',0),(8,'VCC','power_in','R',12.7),(4,'GND','power_in','R',-12.7)])
and_pins=library('LVC1G08_DBV',[(1,'A','input','L',2.54),(2,'B','input','L',-2.54),(4,'Y','output','R',0),(5,'VCC','power_in','R',10.16),(3,'GND','power_in','R',-10.16)])
inv_pins=library('LVC1G04_DBV',[(2,'A','input','L',0),(1,'NC','no_connect','L',-5.08),(4,'Y','output','R',0),(5,'VCC','power_in','R',10.16),(3,'GND','power_in','R',-10.16)])
sup_pins=library('TLV803E_DBZ',[(3,'VDD','power_in','L',5.08),(2,'RESET_N','open_collector','R',0),(1,'GND','power_in','L',-5.08)])
# Separate RUN Schmitt avoids slow/floating external request at inverter/AND input.
# U4 shapes FAULT and ARM. U10 shapes RUN.
single_sh=library('LVC1G17_DBV',[(2,'A','input','L',0),(1,'NC','no_connect','L',-5.08),(4,'Y','output','R',0),(5,'VCC','power_in','R',10.16),(3,'GND','power_in','R',-10.16)])
def label(net,x,y,k):return f'(label "{net}" (at {x} {y} 0) (effects (font (size 0.9 0.9)) (justify left bottom)) (uuid "{uid(k)}"))'
def wire(x,y,xx,yy,k):return f'(wire (pts (xy {x} {y}) (xy {xx} {yy})) (stroke (width 0) (type default)) (uuid "{uid(k)}"))'
def add(lib,ref,value,fp,x,y,pins,nets):
 items.append(f'(symbol (lib_id "Project:{lib}") (at {x} {y} 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "{uid(ref)}") '+prop('Reference',ref,x,y-28)+prop('Value',value,x,y-25)+prop('Footprint',fp,x,y,True)+f'(instances (project "{NAME}" (path "/{ROOT}" (reference "{ref}") (unit 1)))))')
 components.append(dict(ref=ref,value=value,footprint=fp));connections[ref]={str(k):v for k,v in nets.items()}
 for num,_,typ,side,yp in pins:
  px=x+(-15.24 if side=='L' else 15.24);py=y-yp;net=nets[num]
  if net is None:
   items.append(f'(no_connect (at {px} {py}) (uuid "{uid(ref+str(num)+"nc")}"))')
  else:
   xx=px+(-7.62 if side=='L' else 7.62)
   items.append(wire(px,py,xx,py,ref+str(num)+'w'));items.append(label(net,xx,py,ref+str(num)+'l'))
quad1={3:'DRV_AVDD',12:'GND',4:'WIN_LO',5:'ADC_IA',6:'ADC_IA',7:'WIN_HI',8:'WIN_LO',9:'ADC_IB',10:'ADC_IB',11:'WIN_HI',2:'CUT_FAULT_N',1:'CUT_FAULT_N',14:'CUT_FAULT_N',13:'CUT_FAULT_N'}
quad2={3:'DRV_AVDD',12:'GND',4:'WIN_LO',5:'ADC_IC',6:'ADC_IC',7:'WIN_HI',8:'DRV_AVDD',9:'GND',10:'DRV_AVDD',11:'GND',2:'CUT_FAULT_N',1:'CUT_FAULT_N',14:None,13:None}
add('TLV9024_PW','U2','TLV9024PWR','Package_SO:TSSOP-14_4.4x5mm_P0.65mm',650.24,101.6,comp_pins,quad1)
add('TLV9024_PW','U3','TLV9024PWR','Package_SO:TSSOP-14_4.4x5mm_P0.65mm',774.7,101.6,comp_pins,quad2)
add('LVC2G17_DBV','U4','SN74LVC2G17DBVR','Package_TO_SOT_SMD:SOT-23-6',650.24,187.96,sh_pins,{1:'CUT_FAULT_N',3:'ARM_RC',6:'CUT_CLEAR_N',4:'ARM_CLK',5:'CTRL_3V3',2:'GND'})
add('LVC1G74_DCU','U5','SN74LVC1G74DCUR','Package_SO:VSSOP-8_2.3x2mm_P0.5mm',774.7,187.96,ff_pins,{1:'ARM_CLK',2:'RUN_NOT',6:'CUT_CLEAR_N',7:'CTRL_3V3',5:'CUT_PERMIT',3:None,8:'CTRL_3V3',4:'GND'})
add('LVC1G08_DBV','U6','SN74LVC1G08DBVR','Package_TO_SOT_SMD:SOT-23-5',650.24,274.32,and_pins,{1:'RUN_BUF',2:'CUT_PERMIT',4:'DRV_RUN_GATED',5:'CTRL_3V3',3:'GND'})
add('LVC1G04_DBV','U7','SN74LVC1G04DBVR','Package_TO_SOT_SMD:SOT-23-5',774.7,274.32,inv_pins,{1:None,2:'RUN_BUF',4:'RUN_NOT',5:'CTRL_3V3',3:'GND'})
add('TLV803E_DBZ','U8','TLV803EA29DBZR','Package_TO_SOT_SMD:SOT-23',650.24,360.68,sup_pins,{3:'CTRL_3V3',2:'CUT_FAULT_N',1:'GND'})
add('TLV803E_DBZ','U9','TLV803EA29DBZR','Package_TO_SOT_SMD:SOT-23',774.7,360.68,sup_pins,{3:'DRV_AVDD',2:'CUT_FAULT_N',1:'GND'})
add('LVC1G17_DBV','U10','SN74LVC1G17DBVR','Package_TO_SOT_SMD:SOT-23-5',650.24,447.04,single_sh,{1:None,2:'DRV_RUN_REQ',4:'RUN_BUF',5:'CTRL_3V3',3:'GND'})
# Reuse existing passive definitions; local positive y pin maps to screen -y.
pass_pins=[(1,'~','passive','L',5.08),(2,'~','passive','L',-5.08)]
def passive(ref,kind,val,x,y,n1,n2):
 fp=('Resistor_SMD:R_0603_1608Metric' if kind=='R' else 'Capacitor_SMD:C_0603_1608Metric')
 items.append(f'(symbol (lib_id "Project:{kind}") (at {x} {y} 0) (unit 1) (in_bom yes) (on_board yes) (dnp no) (uuid "{uid(ref)}") '+prop('Reference',ref,x,y-12.7)+prop('Value',val,x,y-10.16)+prop('Footprint',fp,x,y,True)+f'(instances (project "{NAME}" (path "/{ROOT}" (reference "{ref}") (unit 1)))))')
 components.append(dict(ref=ref,value=val,footprint=fp));connections[ref]={'1':n1,'2':n2}
 for n,yp,net in [(1,5.08,n1),(2,-5.08,n2)]:
  py=y-yp;items.append(wire(x,py,x+7.62,py,ref+str(n)+'w'));items.append(label(net,x+7.62,py,ref+str(n)+'l'))
passive('R16','R','14.0k 1%',881.38,101.6,'DRV_AVDD','WIN_HI')
passive('R17','R','10.7k 1%',957.58,101.6,'WIN_HI','WIN_LO')
passive('R18','R','14.0k 1%',1033.78,101.6,'WIN_LO','GND')
passive('R19','R','10k 1%',881.38,172.72,'CTRL_3V3','CUT_FAULT_N')
passive('R20','R','100k 1%',957.58,172.72,'DRV_RUN_REQ','GND')
passive('R21','R','1k 1%',1033.78,172.72,'ARM_RAW','ARM_RC')
passive('R22','R','100k 1%',881.38,243.84,'ARM_RC','GND')
passive('C19','C','100n 10V X7R',957.58,243.84,'ARM_RC','GND')
passive('C20','C','1n 10V C0G',1033.78,243.84,'WIN_HI','GND')
passive('C21','C','1n 10V C0G',881.38,314.96,'WIN_LO','GND')
# Each IC gets local bypass. U2/3/9 use AVDD, others use CTRL_3V3.
for i,ref in enumerate(['U2','U3','U4','U5','U6','U7','U8','U9','U10']):
 x=881.38+(i%3)*76.2;y=386.08+(i//3)*71.12
 passive('C'+str(22+i),'C','100n 10V X7R',x,y,'DRV_AVDD' if ref in ['U2','U3','U9'] else 'CTRL_3V3','GND')
# Bench-only ARM dry contact header. Cannot be wired to MCU.
conn=library('ARM_2',[(1,'ARM_SUPPLY','passive','L',2.54),(2,'ARM_CONTACT','passive','L',-2.54)])
add('ARM_2','J5','BENCH_ARM_DRY_CONTACT','Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical',774.7,447.04,conn,{1:'CTRL_3V3',2:'ARM_RAW'})
for x,y,msg in [(600,36,'A85 CANDIDATE - SCH ONLY / UNROUTED / NO MOTOR PERMISSION'),(600,43,'Six windows use existing ADC_IA/B/C RC. Shared DRV CSA/gain faults remain uncovered.'),(600,515,'Bench ARM dry contact: MCU must never clock this latch. RUN high during ARM stores Q=0.'),(600,522,'CUT_FAULT_N wired-low includes both rail supervisors. Fault withdrawal never re-arms.'),(600,529,'R5 input is DRV_RUN_GATED. J4 remains RAW RUN request; no gate bypass.'),(600,536,'No actual current or delay qualification. No fabrication. A83 firmware remains default-off.')]:
 items.append(f'(text "{msg}" (at {x} {y} 0) (effects (font (size 1.2 1.2)) (justify left)) (uuid "{uid(msg)}"))')
a=s.index('(lib_symbols ');z=end(s,a);s=s[:z-1]+''.join(libs)+s[z-1:]
z=len(s.rstrip())-1;s=s[:z]+''.join(items)+s[z:]
(P/(NAME+'.kicad_sch')).write_text(s)
lib=(SRC/'Project.kicad_sym').read_text();z=len(lib.rstrip())-1
(P/'Project.kicad_sym').write_text(lib[:z]+''.join(x.replace('Project:','',1) for x in libs)+lib[z:])
shutil.copyfile(SRC/'sym-lib-table',P/'sym-lib-table');shutil.copyfile(SRC/'fp-lib-table',P/'fp-lib-table')
shutil.copytree(SRC/'MotorStage.pretty',P/'MotorStage.pretty',dirs_exist_ok=True)
(P/'connection-contract.json').write_text(json.dumps(connections,indent=2)+'\n')
(P/'added-components.json').write_text(json.dumps(components,indent=2)+'\n')
shutil.copyfile(SRC/'drill_motor_evt_a4_bus_divider.kicad_pro',P/(NAME+'.kicad_pro'))
print(NAME,len(components),'added components')
