import sys,re,math,heapq,uuid,json
from pathlib import Path
R=Path(__file__).resolve().parent;B=R.parent;sys.path.insert(0,str(B/'parity_metadata_closure_A22'));from prepare_candidates import forms,run
from align_and_verify import footprint_rows
P=R/'controller/drill_control_evt_a3_pf0_sleep.kicad_pcb';s=P.read_text();orig=(B/'drill_interface_A31/controller_candidate/drill_control_evt_a2_paired.kicad_pcb').read_text()
oldids={re.search(r'\(uuid "([^\"]+)"',x)[1] for _,_,x in forms(orig,'segment')}
for tag in ['segment','via']:
 for st,en,x in reversed(list(forms(s,tag))):
  if '/DRV_SLEEP_N' not in x:continue
  u=re.search(r'\(uuid "([^\"]+)"',x)[1]
  if tag=='via' or u not in oldids or u in {'43dd196c-b31a-480b-a906-d7a749bcd76a','61a728a0-2b3e-455c-8e91-eae8c7a2a650'}:s=s[:st]+s[en:]
# Pure geometry raster; native refill/DRC remains final authority.
step=.025;x0=70;y0=35;nx=1800;ny=1400;obs=[set(),set()];viaobs=[set(),set()]
def distseg(x,y,a,b):
 dx=b[0]-a[0];dy=b[1]-a[1];t=max(0,min(1,((x-a[0])*dx+(y-a[1])*dy)/(dx*dx+dy*dy))) if dx*dx+dy*dy else 0
 return math.hypot(x-a[0]-t*dx,y-a[1]-t*dy)
def raster(bounds,layers,predicate,margin,target):
 lo,hi,bo,to=bounds
 for i in range(max(0,math.floor((lo-margin-x0)/step)),min(nx,math.ceil((hi+margin-x0)/step))+1):
  for j in range(max(0,math.floor((bo-margin-y0)/step)),min(ny,math.ceil((to+margin-y0)/step))+1):
   if predicate(x0+i*step,y0+j*step,margin):
    for l in layers:target[l].add((i,j))
def add(bounds,layers,pred):
 raster(bounds,layers,pred,.295,obs);raster(bounds,layers,pred,.42,viaobs)
for _,_,z in forms(s,'segment'):
 if '/DRV_SLEEP_N' in z:continue
 a=tuple(map(float,re.search(r'\(start ([\d.-]+) ([\d.-]+)',z).groups()));b=tuple(map(float,re.search(r'\(end ([\d.-]+) ([\d.-]+)',z).groups()));w=float(re.search(r'\(width ([\d.-]+)',z)[1]);l=0 if '(layer "F.Cu")' in z else 1
 # margin includes our radius and clearance; obstacle half-width explicit.
 add((min(a[0],b[0])-w/2,max(a[0],b[0])+w/2,min(a[1],b[1])-w/2,max(a[1],b[1])+w/2),[l],lambda x,y,m,a=a,b=b,w=w:distseg(x,y,a,b)<=m+w/2)
for _,_,z in forms(s,'via'):
 xy=tuple(map(float,re.search(r'\(at ([\d.-]+) ([\d.-]+)',z).groups()));r=float(re.search(r'\(size ([\d.-]+)',z)[1])/2
 add((xy[0]-r,xy[0]+r,xy[1]-r,xy[1]+r),[0,1],lambda x,y,m,xy=xy,r=r:math.hypot(x-xy[0],y-xy[1])<=r+m)
for _,_,f,ref,_ in footprint_rows(s):
 m=re.search(r'\(at ([\d.-]+) ([\d.-]+)(?: ([\d.-]+))?\)',f);fx,fy=float(m[1]),float(m[2]);fa=math.radians(float(m[3] or 0));ca,sa=math.cos(fa),math.sin(fa)
 for _,_,p in forms(f,'pad'):
  if '/DRV_SLEEP_N' in p:continue
  pm=re.search(r'\(at ([\d.-]+) ([\d.-]+)(?: ([\d.-]+))?\)',p)
  px,py=float(pm[1]),float(pm[2]);cx=fx+px*ca+py*sa;cy=fy-px*sa+py*ca;w,h=map(float,re.search(r'\(size ([\d.-]+) ([\d.-]+)',p).groups());angle=math.radians(float(pm[3] or 0));co,si=math.cos(angle),math.sin(angle)
  ls=[0,1] if '"*.Cu"' in p else [0] if '"F.Cu"' in p else [1]
  # Rectangle-distance, conservative for rounded corners.
  def pred(x,y,m,cx=cx,cy=cy,w=w,h=h,co=co,si=si):
   xx=(x-cx)*co-(y-cy)*si;yy=(x-cx)*si+(y-cy)*co
   return math.hypot(max(abs(xx)-w/2,0),max(abs(yy)-h/2,0))<=m
  rad=math.hypot(w,h)/2;add((cx-rad,cx+rad,cy-rad,cy+rad),ls,pred)
def pt(xy):return (round((xy[0]-x0)/step),round((xy[1]-y0)/step),0)
a=(84.0125,52.425);b=(95.5,57.2988);start=pt(a);goal=pt(b);print('blocked start/goal',start[:2] in obs[0],goal[:2] in obs[0],flush=True)
q=[(0,0,start)];cost={start:0};prev={};found=None
while q:
 _,c,p=heapq.heappop(q)
 if c!=cost[p]:continue
 if p==goal:found=p;break
 i,j,l=p
 for di,dj in [(1,0),(-1,0),(0,1),(0,-1),(1,1),(-1,1),(1,-1),(-1,-1),(0,0)]:
  if di==dj==0:
   if (i,j) in viaobs[0] or (i,j) in viaobs[1]:continue
   v=(i,j,1-l);inc=45
  else:
   ii,jj=i+di,j+dj
   if not 0<=ii<=nx or not 0<=jj<=ny or (ii,jj) in obs[l]:continue
   if di and dj and ((i+di,j) in obs[l] or (i,j+dj) in obs[l]):continue
   v=(ii,jj,l);inc=math.hypot(di,dj)
  cc=c+inc
  if cc>=cost.get(v,1e30):continue
  cost[v]=cc;prev[v]=p;hh=math.hypot(v[0]-goal[0],v[1]-goal[1])+(10 if v[2]!=goal[2] else 0);heapq.heappush(q,(cc+hh,cc,v))
print('visited',len(cost),'found',bool(found),flush=True)
print('layers', [sum(p[2]==l for p in cost) for l in [0,1]],flush=True)
assert found,'No route; retain current A31 release candidate'
path=[found]
while path[-1]!=start:path.append(prev[path[-1]])
path=path[::-1];simple=[path[0]]
for i in range(1,len(path)-1):
 d1=tuple(path[i][k]-path[i-1][k] for k in range(3));d2=tuple(path[i+1][k]-path[i][k] for k in range(3))
 if d1!=d2:simple.append(path[i])
simple.append(path[-1]);coords=[(round(x0+i*step,5),round(y0+j*step,5),l) for i,j,l in simple];coords=[(*a,0)]+coords+[(*b,0)]
adds=[]
def uid(k):return str(uuid.uuid5(uuid.NAMESPACE_URL,'A32routed:'+k))
for k,(u,v) in enumerate(zip(coords,coords[1:])):
 if u==v:continue
 if u[2]!=v[2]:adds.append('(via (at %s %s) (size 0.45) (drill 0.2) (layers "F.Cu" "B.Cu") (net "/DRV_SLEEP_N") (uuid "%s"))'%(*u[:2],uid('via'+str(k))))
 else:adds.append('(segment (start %s %s) (end %s %s) (width 0.2) (layer "%s") (net "/DRV_SLEEP_N") (uuid "%s"))'%(*u[:2],*v[:2],'F.Cu' if u[2]==0 else 'B.Cu',uid('seg'+str(k))))
s=s.rstrip()[:-1]+'\n'+'\n'.join(adds)+'\n)\n';P.write_text(s);(R/'route_points.json').write_text(json.dumps(coords,indent=2)+'\n')
assert run(['pcb','drc','--refill-zones','--save-board','--schematic-parity','--format','json','--output',str(P.parent/'drc.json'),str(P)],P.parent/'drc.log')==0
d=json.loads((P.parent/'drc.json').read_text());print(json.dumps({'violations':len(d['violations']),'unconnected':len(d['unconnected_items']),'parity':len(d['schematic_parity'])}))
