#!/usr/bin/env python3
"""Build public evidence from the driver outputs; omit paths, hostnames and unrelated logs.
Usage: make-evidence.py SCRATCH. The app images redact only the environment-name line.
Runtime cards are explicitly labeled summaries of captured process/trace output.
"""
import pathlib,json,sys,subprocess,html,re,datetime
p=pathlib.Path(sys.argv[1]); public=p/'public';public.mkdir(exist_ok=True)
shas={'before':'b68aedfdb71534bfdfbe55771ef2e686742faa8e','after':'ff6eed3ff963dabaeb1d996fb7707328c42203b7'}
for side,folder in [('before','base-run-1'),('after','head-run-1')]:
    run=p/folder; events=json.loads((run/'events.json').read_text()); trace=json.loads((run/'lifecycle-raw.json').read_text())
    def get(name):return next(row for row in events if row['event']==name)
    done=get('electron exited'); lost=get('private Xvfb stopped'); pre=get('before display loss')
    rows=[f'{side.upper()} - {shas[side][:10]}','Observed runtime output (not app UI)','Linux x86_64 / Electron 44.10.1 / X11 / 1280x800','Fresh HOME, XDG config and T3 home; no seeded data.','',
    '1. Launch real bundled Electron; wait for backend ready.','2. Wait 15 seconds; capture two identical frames 500ms apart.','3. Record 5 seconds, then terminate the private Xvfb.','',
    f'Electron launcher exit status: {done["exitCode"]}',f'Main exited {done["seconds"]-pre["seconds"]:.3f}s after display termination began.',
    'GPU stderr: X connection error received.',
    f'Observed descendants before display loss: {len(pre["descendants"])}',
    f'Descendant states at main exit: {json.dumps(done["childStates"])}',
    'Desktop lifecycle trace names:']
    rows.extend('  '+r['name'] for r in trace)
    exits=[r for r in trace if r['name']=='desktop.lifecycle.exit']
    if exits:
        rows.extend(['Exit trace attributes: '+json.dumps(exits[0]['attributes']),
        'ERROR: desktop main process exiting before shutdown completed',
        'Caveat: callback exitCode=0 differs from launcher status=1.'])
    else: rows.extend(['No exit trace; no beforeQuit/windowAllClosed/shutdown trace.', 'No desktop log entry explaining the main-process exit.'])
    servertrace=[json.loads(line) for line in (run/'home/t3/userdata/logs/server.trace.ndjson').read_text().splitlines() if '"name":"server.shutdown"' in line]
    if servertrace:
        xmatch=re.search(r'\[\d+:1112/(\d{2})(\d{2})(\d{2}\.\d+):ERROR:ui/gfx/x/connection', (run/'electron.log').read_text())
        gpu=int(xmatch[1])*3600+int(xmatch[2])*60+float(xmatch[3])
        shutdown=int(servertrace[-1]['startTimeUnixNano'])/1e9 % 86400
        rows.extend([f'Backend server.shutdown began {shutdown-gpu:.3f}s after first X error.'])
    rows.extend(['','Scope: unpackaged Electron; AppImage unmount/SIGBUS not reproduced.', 'The application still exits after losing its display.'])
    # Use short process aliases to keep the public card readable; PIDs remain in transcript.
    text='\n'.join(rows)+'\n'; (public/f'{side}-transcript.txt').write_text(text+'\nDriver timeline:\n'+json.dumps(events,indent=2)+'\n\nLifecycle records:\n'+json.dumps(trace,indent=2))
    cardrows=[r for r in rows if not r.startswith('Descendant states')]
    cardrows.insert(13,'Descendants at main exit: '+', '.join(done['childStates'].values()))
    svg='<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800"><rect width="1280" height="800" fill="#111827"/>'
    for n,line in enumerate(cardrows):
        svg+=f'<text x="30" y="{42+n*27}" font-family="DejaVu Sans Mono" font-size="{23 if n==0 else 17}" fill="#e5e7eb">{html.escape(line)}</text>'
    svg+='</svg>'; (public/f'{side}-runtime.svg').write_text(svg)
    subprocess.run(['convert',str(public/f'{side}-runtime.svg'),str(public/f'{side}-runtime.png')],check=True)
    vf='drawbox=x=280:y=160:w=720:h=32:color=white:t=fill,drawtext=text=Environment label redacted:x=280:y=166:fontsize=16:fontcolor=gray'
    subprocess.run(['ffmpeg','-y','-i',str(run/'ready.png'),'-vf',vf,'-frames:v','1',str(public/f'{side}-app.png')],check=True,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
    # Keep the genuine screen recording and append an explicitly labeled runtime card.
    subprocess.run(['ffmpeg','-y','-i',str(run/'display-loss.mp4'),'-loop','1','-framerate','10','-t','9','-i',str(public/f'{side}-runtime.png'),'-filter_complex',f'[0:v]{vf},setsar=1[a];[1:v]setsar=1[b];[a][b]concat=n=2:v=1:a=0[v]','-map','[v]','-c:v','libx264','-preset','fast','-pix_fmt','yuv420p','-movflags','+faststart',str(public/f'{side}-display-loss.mp4')],check=True,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
