import json,sys,subprocess
from pathlib import Path
from PIL import Image,ImageDraw,ImageFont
root=Path(sys.argv[1])
font='/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf'
def f(n):return ImageFont.truetype(font,n)
def render(rows,side,t):
 im=Image.new('RGB',(1280,720),'#111827');d=ImageDraw.Draw(im)
 visible=[r for r in rows if r['seconds']<=t]; env=rows[0]['data']; listing=next((r for r in reversed(visible) if r['event'] in ['watch subscribed','device.list','final outcome']),None)
 gateway=next((r['data'] for r in reversed(visible) if r['event']=='gateway transcript'),[])
 booted=listing and any(x['id']=='lease-proof' and x['booted'] for x in listing['data']['devices'])
 d.text((40,28),('BEFORE' if side=='before' else 'AFTER')+'  |  Captured server RPC transcript',font=f(27),fill='white')
 d.text((40,80),'Commit '+env['commit'][:10]+'   |   elapsed %02d:%02d'%(int(t//60),int(t%60)),font=f(22),fill='#a5b4fc')
 for i,line in enumerate(['Real T3 server + pinned agent-device CLI; local fake gateway', 'Gateway TTL cap: 5 minutes   |   Host maximum age: 60 minutes', 'Real wall clock; state queried over the app WebSocket RPC']):d.text((40,135+33*i),line,font=f(19),fill='#d1d5db')
 d.text((40,270),'Actual gateway renewal requests:',font=f(22),fill='white')
 renew=[e for e in gateway if e.get('route')=='renew']
 if not renew:d.text((60,315),'Awaiting first two-minute renewal...',font=f(21),fill='#9ca3af')
 for i,e in enumerate(renew[-4:]):
  sec=e['elapsedSeconds'];body=json.dumps(e['body'],separators=(',',':'))
  d.text((60,315+36*i),'%02d:%02d  POST /renew  %-20s HTTP %d'%(int(sec//60),int(sec%60),body,e['status']),font=f(22),fill='#86efac' if e['status']==200 else '#fca5a5')
 d.text((40,495),'Actual device.list response:',font=f(22),fill='white')
 if listing:
  d.text((60,540),'lease-proof: '+('booted=true, present' if booted else 'absent'),font=f(26),fill='#86efac' if booted else '#fca5a5')
  d.text((60,585),'Active sessions: '+str(len(listing['data']['sessions'])),font=f(22),fill='#d1d5db')
 d.text((40,668),'Transcript playback; no UI or device video is simulated here.',font=f(18),fill='#9ca3af')
 return im
for side in ['before','after']:
 rows=json.loads((root/f'{side}-runtime.json').read_text());end=next(r['seconds'] for r in rows if r['event']=='final outcome')
 render(rows,side,end).save(root/f'{side}.png')
 # A timestamped recording of the emitted command output, at its original speed.
 with (root/f'{side}.cast').open('w') as out:
  out.write(json.dumps({'version':2,'width':130,'height':36,'title':side+' real server RPC output'})+'\n')
  for row in rows:out.write(json.dumps([row['seconds'],'o',json.dumps(row)+'\r\n'])+'\n')
 # Human-readable playback of the recorded output, compressed 15x.
 p=subprocess.Popen(['ffmpeg','-loglevel','error','-y','-f','rawvideo','-pix_fmt','rgb24','-s','1280x720','-r','10','-i','-','-c:v','libx264','-preset','fast','-crf','0','-g','1','-pix_fmt','yuv420p',str(root/f'{side}.mp4')],stdin=subprocess.PIPE)
 for frame in range(int(end/15*10)+1):p.stdin.write(render(rows,side,min(end,frame*1.5)).tobytes())
 p.stdin.close();assert p.wait()==0
