from pathlib import Path
from PIL import Image,ImageDraw,ImageFont
import numpy as np,json
R=Path(__file__).resolve().parent;out=R/'captures';F=json.loads((R/'fixtures.json').read_text());stats=[]
font=ImageFont.truetype('/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf',16)
for row in F['fixtures']:
 id=row['id'];paths=[out/f'{id}-{s}.png' for s in ['before','after']]
 if not all(p.exists() for p in paths):continue
 a,b=[Image.open(p).convert('RGB') for p in paths]
 assert a.size==b.size,(id,a.size,b.size)
 # Compute in strips to cap memory at high phone DPR.
 d0=Image.new('RGB',a.size);dt=Image.new('RGB',a.size);counts=[0,0];bounds=[]
 for y in range(0,a.height,256):
  box=(0,y,a.width,min(y+256,a.height));aa=np.asarray(a.crop(box)).astype(np.int16);bb=np.asarray(b.crop(box)).astype(np.int16);delta=np.abs(aa-bb);m=delta.max(axis=2);zero=m>0;th=m>16
  counts[0]+=int(zero.sum());counts[1]+=int(th.sum())
  yy,xx=np.where(zero)
  if len(xx):bounds.append((int(xx.min()),int(yy.min()+y),int(xx.max()+1),int(yy.max()+y+1)))
  # Any nonzero delta becomes saturated magenta. This amplifies even a 1/255 delta.
  z=np.zeros((*m.shape,3),dtype=np.uint8);z[zero]=[255,0,255];t=np.zeros_like(z);t[th]=[255,0,255]
  d0.paste(Image.fromarray(z),box[:2]);dt.paste(Image.fromarray(t),box[:2])
 for name,img in [('diff0',d0),('diffT',dt)]:img.save(out/f'{id}-{name}.png')
 bbox=None if not bounds else [min(x[0] for x in bounds),min(x[1] for x in bounds),max(x[2] for x in bounds),max(x[3] for x in bounds)]
 ds=a.width/row['viewport']['width'];before=json.loads((out/f'{id}-before.json').read_text());after=json.loads((out/f'{id}-after.json').read_text());w=row['viewport']['width'];cw=w
 # Whole-surface overview includes every pixel of all four images, alongside readable section sheets.
 imgs=[a,b,d0,dt];labels=['Before '+F['base'],'After '+F['head'],'Diff0: max RGB delta > 0','DiffT: max RGB delta > 16/255']
 # Three readable strips at CSS scale (wide layouts capped at 900px per column).
 hint=before['observed']['hint']['rect'];lower=before['observed']['lower']['rect'];height=a.height/ds
 ranges=[('Hero',0,min(height,900)),('Git panel / keyboard hint',max(0,hint['y']-600),min(height,hint['y']+220)),('Lower CTA and actual footer',max(0,lower['y']-550),height)]
 total=64+sum(round((hi-lo)*cw/w)+40 for _,lo,hi in ranges);sheet=Image.new('RGB',(cw*2,total),'#eeeeee');draw=ImageDraw.Draw(sheet)
 for j,label in enumerate(labels[:2]):draw.text((j*cw+8,5),label.split(' ',1)[0],font=font,fill='black');draw.text((j*cw+8,28),label.split(' ',1)[-1][:24],font=font,fill='black')
 y=64
 for name,lo,hi in ranges:
  draw.text((8,y+4),id+' '+name+f' | CSS y {lo:.1f}–{hi:.1f}',font=font,fill='black');y+=40;h=round((hi-lo)*cw/w)
  for j,img in enumerate(imgs[:2]):sheet.paste(img.crop((0,round(lo*ds),img.width,round(hi*ds))).resize((cw,h)),(j*cw,y))
  y+=h
 sheet.save(out/f'{id}-sections.png')
 stats.append({'id':id,'size':a.size,'zeroPixels':counts[0],'thresholdPixels':counts[1],'bbox':bbox,'bboxCSS':None if bbox is None else [v/ds for v in bbox],'heroBefore':before['observed']['hero']['rect'],'heroAfter':after['observed']['hero']['rect'],'lowerBefore':before['observed']['lower']['rect'],'lowerAfter':after['observed']['lower']['rect']})
 print(id,a.size,counts,bbox,flush=True)
 for im in [a,b,d0,dt,sheet]:im.close()
(R/'diff-stats.json').write_text(json.dumps(stats,indent=2))
