#!/usr/bin/env python3
"""Publish whole-surface synthetic evidence, with fixed, disclosed privacy masks."""
import pathlib,json,subprocess,sys
from PIL import Image,ImageChops,ImageDraw
B=pathlib.Path('/tmp/lead-32548-r1/evidence-capture-2')
O=B/'publish'; O.mkdir(exist_ok=True)
S=B/'review-sheets'; S.mkdir(exist_ok=True)
D=json.loads((B/'capture-retakes-private.json').read_text())
def run(args): return subprocess.run(args,check=True,capture_output=True)
def mask(row):
    if row in ['F01','F16']: return (915,550,1440,900)
    if row=='F02': return (915,195,1440,539)
    return None
stats=json.loads((O/'capture-metadata.json').read_text()) if (O/'capture-metadata.json').exists() else {}
selected=set(sys.argv[1:])
for key,r in D.items():
    if selected and key not in selected: continue
    row=key[:3]; side=key[3:]; src=pathlib.Path(r['prefix']); dst=O/f'{row}-{side}'
    rect=mask(row)
    for suffix in ['-a','-b']:
        im=Image.open(str(src)+suffix+'.png').convert('RGB')
        if rect: ImageDraw.Draw(im).rectangle(tuple(v*2 for v in rect),fill='#767676')
        im.save(str(dst)+suffix+'.png')
    a=Image.open(str(dst)+'-a.png'); b=Image.open(str(dst)+'-b.png')
    # Validate originals as well: privacy edits must not hide an unstable capture.
    oa=Image.open(str(src)+'-a.png'); ob=Image.open(str(src)+'-b.png')
    assert ImageChops.difference(oa.convert('RGB'),ob.convert('RGB')).getbbox() is None,key
    b.save(str(dst)+'.png')
    filt=[]
    if rect:
        x,y,x2,y2=rect
        filt.append(f'drawbox=x={x*2}:y={y*2}:w={(x2-x)*2+2}:h={(y2-y)*2+2}:color=0x767676:t=fill')
    args=['ffmpeg','-y','-hide_banner','-loglevel','error','-threads','2','-i',str(src)+'.mp4']
    if filt:args+=['-vf',','.join(filt)]
    # Preserve VFR timing; a constant-rate output would invent repeated frames.
    run(args+['-fps_mode','passthrough','-enc_time_base','1:30000','-c:v','libx264','-threads','2','-preset','fast','-crf','18','-an',str(dst)+'.mp4'])
    frames=json.loads(run(['ffprobe','-v','error','-show_frames','-select_streams','v:0','-show_entries','frame=best_effort_timestamp_time','-of','json',str(dst)+'.mp4']).stdout)['frames']
    original_frames=json.loads(run(['ffprobe','-v','error','-show_frames','-select_streams','v:0','-show_entries','frame=best_effort_timestamp_time','-of','json',str(src)+'.mp4']).stdout)['frames']
    assert len(original_frames)==len(frames),(key,'frame loss')
    assert [f['best_effort_timestamp_time'] for f in original_frames]==[f['best_effort_timestamp_time'] for f in frames],(key,'timing changed')
    proc=subprocess.Popen(['ffmpeg','-v','error','-threads','2','-i',str(dst)+'.mp4','-vf','scale=720:450','-fps_mode','passthrough','-f','rawvideo','-pix_fmt','rgb24','-'],stdout=subprocess.PIPE)
    count=0; sheets=[]; sheet=None
    while True:
        buf=proc.stdout.read(720*450*3)
        if not buf:break
        assert len(buf)==720*450*3
        if count%12==0:sheet=Image.new('RGB',(2160,4*474),'#333333')
        im=Image.frombytes('RGB',(720,450),buf)
        cell=count%12; x=(cell%3)*720;y=(cell//3)*474
        sheet.paste(im,(x,y+24))
        ImageDraw.Draw(sheet).text((x+4,y+4),f'{key} frame {count:03} t={frames[count]["best_effort_timestamp_time"]}s',fill='white')
        count+=1
        if count%12==0:
            f=S/f'{key}-{len(sheets):02}.png';sheet.save(f);sheets.append(str(f));sheet=None
    if sheet:
        f=S/f'{key}-{len(sheets):02}.png';sheet.save(f);sheets.append(str(f))
    assert proc.wait()==0
    stats[key]={'frames':count,'sheets':sheets,'steps':[{'at':s['at'],'name':s['name'],'args':s['args']} for s in r['steps']], 'settledOriginalAE':0,'maskCss':rect,'probe':{k:v for k,v in r['probe'].items() if k!='text'}}
    # Full text is omitted from public diagnostics to exclude attribution metadata.
    for linked in stats[key]['probe']['linked']:linked.pop('text',None)
    print(key,count,len(sheets),flush=True)
for row in ['F07','F02','F17','F16','F01','F29']:
    if row=='F29':
        r=D['F29after'];p=r['prefix'];a=Image.open(p+'-baseline-a.png').convert('RGB'); b=Image.open(p+'-baseline-b.png').convert('RGB')
        assert ImageChops.difference(a,b).getbbox() is None
        a.save(O/'F29-pre-wheel.png'); a.save(O/'F29-pre-wheel-a.png'); b.save(O/'F29-pre-wheel-b.png'); before=a;after=Image.open(O/'F29-after.png').convert('RGB')
    else:before=Image.open(O/f'{row}-before.png').convert('RGB');after=Image.open(O/f'{row}-after.png').convert('RGB')
    diff=ImageChops.difference(before,after)
    diff.point(lambda v:min(v*8,255)).save(O/f'{row}-Diff0-x8.png')
    channels=diff.split();maximum=ImageChops.lighter(ImageChops.lighter(channels[0],channels[1]),channels[2])
    maximum.point(lambda v:255 if v>16 else 0).save(O/f'{row}-DiffT16.png')
(O/'capture-metadata.json').write_text(json.dumps(stats,indent=2))
(B/'sheets.json').write_text(json.dumps(stats,indent=2))
