#!/usr/bin/env python3
"""Settled W2 stills. ACP stub harness; no live Antigravity backend.
Run only after the production server watchdog has completed.
Usage: still-driver.py before|after W2a|W2b URL
"""
import sys,pathlib,datetime,subprocess,json,time
from PIL import Image,ImageChops
side,row,url=sys.argv[1:]
root=pathlib.Path('/tmp/lead-33596-r2/capture-2')
run=root/(side+'-'+row+'-stills-'+datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%S%fZ'));run.mkdir()
cli='/tmp/lead-33596-r2/capture-1/browser-tools/node_modules/.bin/agent-browser'
base=[cli,'--session','capture2-166ccec3ec63-'+side]
def command(*parts):
 p=subprocess.run([*base,*parts],capture_output=True,text=True)
 if p.returncode: raise RuntimeError(p.stdout+p.stderr)
 return p.stdout
command('eval',(root/'driver.js').read_text())
anchor=int(command('eval',"Promise.all(['W2a','W2b'].map(row=>fixture.rpc('orchestration.getThreadProjection',{threadId:'thread:'+row+'-evidence'}))).then(ps=>Math.min(...ps.flatMap(p=>p.runs.map(r=>Date.parse(r.startedAt)))))").strip())
clock=(root/'frozen-clock.js').read_text().replace('__RUN_STARTED_AT_MS__',str(anchor))
(run/'clock.js').write_text(clock)
cdp=command('get','cdp-url').strip().strip(chr(34))
clockResult=subprocess.run(['node',str(root/'clock-cdp-driver.mjs'),cdp,str(run/'clock.js'),url],check=True,capture_output=True,text=True)
(run/'clock-result.json').write_text(clockResult.stdout)
command('set','viewport','1440','900','2');command('set','media','dark')

command('wait','--text','Usage limit reached' if side=='after' else 'Antigravity sent nothing for 15 minutes')
command('wait','--load','networkidle');command('eval','document.fonts.ready.then(()=>true)')
if command('eval',"Boolean(document.querySelector('button[aria-label=\"Dismiss error\"]'))").strip()=='true':
 command('focus','button[aria-label="Dismiss error"]');command('press','Enter')
 command('wait','--fn',"!document.querySelector('button[aria-label=\"Dismiss error\"]')")

command('eval',"document.activeElement?.blur(); [...document.querySelectorAll('*')].filter(e=>e.scrollHeight>e.clientHeight&&getComputedStyle(e).overflowY.match(/auto|scroll/)).forEach(e=>e.scrollTop=0); true")
# Repaint the whole viewport after transient notifications have left.
command('set','viewport','1441','900','2');command('set','viewport','1440','900','2')
command('wait','--load','networkidle');command('eval','document.fonts.ready.then(()=>true)')
command('eval',"(() => {const label=document.createElement('div'); label.id='capture-harness-label';label.textContent='ACP stub harness; no live Antigravity backend.';label.style.cssText='position:fixed;left:740px;top:15px;font:12px sans-serif;color:#c5c5c5;background:#000;z-index:2147483647;pointer-events:none';document.body.append(label);return true;})()")
first=run/'sample-1.png';second=run/'sample-2.png'
command('screenshot',str(first));time.sleep(.5);command('screenshot',str(second))
a,b=Image.open(first),Image.open(second)
if ImageChops.difference(a.convert('RGB'),b.convert('RGB')).getbbox():
 raise RuntimeError('Frames differ: retain and retry after settling')
b.save(run/(side+'-'+row+'-native.png'));b.resize((1440,900),Image.Resampling.LANCZOS).save(run/(side+'-'+row+'.png'))
metrics=command('eval',"JSON.stringify({ua:navigator.userAgent,viewport:[innerWidth,innerHeight],dpr:devicePixelRatio,scale:visualViewport.scale,font:getComputedStyle(document.documentElement).fontSize,theme:document.documentElement.className,fonts:document.fonts.status,date:new Date().toISOString(),text:document.body.innerText})")
(run/'environment.json').write_text(metrics)
(run/'verification.json').write_text(json.dumps({'runAnchorMs':anchor,'pixelIdentical':True,'separationMs':500,'nativePixels':b.size,'viewport':[1440,900],'theme':'dark','clock':datetime.datetime.fromtimestamp(((anchor+20*60*1000)//60000)*60,datetime.timezone.utc).isoformat()+' (browser only, derived from RPC run start; server unchanged)','label':'ACP stub harness; no live Antigravity backend.'},indent=2))
print(run)
