#!/usr/bin/env python3
"""Native UI evidence driver. No app source or draft-storage writes."""
import pathlib,subprocess,time,sys,json,re,datetime,os
from PIL import Image,ImageChops,ImageDraw
ROOT=pathlib.Path('/tmp/lead-26800-r1/evidence-capture-1')
class Driver:
 def __init__(self,side,row,theme):
  self.side=side; self.row=row; self.theme=theme; self.port='8082' if side=='base' else '8081'
  self.run=ROOT/(datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%SZ')+'-'+side+'-'+row+'-'+theme)
  self.run.mkdir(); self.start=None;self.events=[]
 def ad(self,*args):
  t=time.time(); p=subprocess.run(['python3',str(ROOT/'ad.py'),*map(str,args)],text=True,capture_output=True,env={**os.environ,'EVIDENCE_METRO_PORT':self.port})
  self.events.append({'time':t,'t':None if self.start is None else t-self.start,'args':list(map(str,args)),'code':p.returncode,'stdout':p.stdout,'stderr':p.stderr})
  (self.run/'steps.json').write_text(json.dumps(self.events,indent=2))
  print(' '.join(map(str,args))+' => '+p.stdout[:150].replace('\n',' '),flush=True)
  if p.returncode: raise RuntimeError(p.stderr+p.stdout)
  return p.stdout
 def tap(self,label,role='button',last=False):
  snap=self.ad('snapshot','-i'); matches=re.findall(r'^(@\S+) \['+re.escape(role)+r'\] "'+re.escape(label)+r'"(?: |$)',snap,re.M)
  if not matches: raise RuntimeError('No '+role+' '+label+'\n'+snap)
  self.ad('press',matches[-1 if last else 0],'--settle')
 def wait(self,text): self.ad('wait',f'text="{text}"','15000')
 def save_state(self,label):
  p=subprocess.run(['python3',str(ROOT/'cdp.py'),self.port,str(ROOT/'read-drafts.js'),str(self.run/(label+'-drafts.json'))],capture_output=True,text=True)
  if p.returncode: raise RuntimeError(p.stderr)
  v=json.loads(p.stdout)
  if v.get('result',{}).get('exceptionDetails'): raise RuntimeError(p.stdout)
  return v['result']['result']['value']
 def still(self,label):
  for attempt in range(6):
   a=self.run/f'{label}-settle-{attempt}-a.png';b=self.run/f'{label}-settle-{attempt}-b.png'
   self.ad('screenshot',a);time.sleep(.5); self.ad('screenshot',b)
   ia=Image.open(a).convert('RGB');ib=Image.open(b).convert('RGB')
   # Status bar contains the host clock/battery; keep it in raw captures, mask for stability/diffs.
   if ImageChops.difference(ia.crop((0,64,1080,2424)),ib.crop((0,64,1080,2424))).getbbox() is None:
    ia.save(self.run/(label+'.png'))
    self.events.append({'time':time.time(),'t':None if self.start is None else time.time()-self.start,'keyframe':label})
    (self.run/'steps.json').write_text(json.dumps(self.events,indent=2));return
  raise RuntimeError('Not pixel-stable '+label)
 def record(self):
  self.start=time.time();self.ad('record','start',self.run/'recording.mp4','--quality','high','--hide-touches')
 def stop(self): self.ad('record','stop')
 def project(self,name):
  self.tap(name+', '+str(ROOT/'workspaces'/name));self.wait('What should we build')
 def remove(self,name):
  p=subprocess.run(['python3',str(ROOT/'seed.py'),self.side,'remove',name],text=True,capture_output=True)
  if p.returncode: raise RuntimeError(p.stderr)
  self.events.append({'time':time.time(),'t':time.time()-self.start if self.start else None,'projectRemove':name,'output':p.stdout})
 def begin(self):self.record();self.still('source');self.save_state('source')
 def f1(self):
  self.begin();self.ad('back','--settle');self.remove('beta');self.tap('26785 keep this draft');self.wait('Choose project');self.still('picker');self.project('alpha')
  self.wait('26785 keep this draft' if self.side=='head' else 'Ask anything');self.still('destination');self.save_state('destination')
  self.ad('back','--settle');self.still('unsent');self.tap('26785 keep this draft')
  if self.side=='base': self.wait('Choose project');self.project('alpha');self.wait('Ask anything')
  else:self.wait('26785 keep this draft')
  self.still('reopened');self.save_state('reopened');self.stop()
 def f2(self):
  self.begin();self.ad('back','--settle');self.remove('beta');self.tap('26785 keep this draft');self.wait('Choose project');self.still('picker');self.save_state('before-cancel')
  self.ad('back','--settle');self.still('cancelled-unsent');self.save_state('after-cancel');self.tap('26785 untouched second draft');self.wait('26785 untouched second draft');self.still('healthy');self.ad('back','--settle')
  self.tap('26785 keep this draft');self.wait('Choose project');self.project('alpha');self.wait('26785 keep this draft');self.still('destination');self.save_state('destination');self.stop()
 def f3(self):
  self.begin();self.ad('back','--settle');self.tap('26785 healthy draft');self.wait('26785 healthy draft');self.still('direct-open');self.tap('beta');self.wait('Choose project');self.still('picker');self.ad('back','--settle');self.wait('26785 healthy draft');self.still('cancelled');self.save_state('after-cancel');self.tap('beta');self.wait('Choose project');self.project('alpha');self.wait('26785 healthy draft');self.still('destination');self.save_state('destination');self.stop()
 def f4(self):
  self.begin();self.ad('open','com.t3tools.t3code.dev','t3code-dev://new/draft?draftId=new-task%3Aproof-26800-missing');self.wait('Choose project');self.still('picker');self.project('alpha');self.wait('Ask anything');self.still('destination');self.save_state('destination');self.stop()
 def rich_row(self):
  snap=self.ad('snapshot','-i');label=next(s for s in re.findall(r'^@\S+ \[button\] "([^"]+)"',snap,re.M) if s.startswith('26785 recovery field notes'));self.tap(label)
 def editor_pan(self,direction):
  self.ad('press','role=text-field','--settle');snap=self.ad('snapshot','-i')
  if 'Move cursor to the head' not in snap:
   self.ad('press','label="Open features menu"','--settle');self.ad('press','label="Text editing"','--settle')
  self.ad('press','label="Move cursor to the '+('head' if direction=='start' else 'end')+'"','--settle');self.ad('keyboard','dismiss')
 def f5(self):
  self.begin();self.editor_pan('start');self.still('source-start');self.editor_pan('end');self.still('source-end');self.tap('Open proof-26785-image.png');self.still('source-image');self.ad('back','--settle');self.ad('keyboard','dismiss');self.tap('Navigate up');self.wait('Unsent');self.remove('beta');self.rich_row();self.wait('Choose project');self.still('picker')
  title='alpha synthetic recovery project with an intentionally long display name for 26785';self.tap(title+', '+str(ROOT/'workspaces'/'alpha'));self.wait('What should we build');self.still('destination');self.save_state('destination');self.editor_pan('start');self.still('destination-start');self.editor_pan('end');self.still('destination-end');self.tap('Open proof-26785-image.png');self.still('destination-image');self.ad('back','--settle');self.stop()
if __name__=='__main__':
 side,row,theme=sys.argv[1:];d=Driver(side,row,theme)
 try:getattr(d,row.lower())()
 finally:print('RUN',d.run,flush=True)
