#!/usr/bin/env python3
"""Upload reviewed synthetic evidence, preserving ghdrop Markdown verbatim."""
from pathlib import Path
import subprocess,json,re,concurrent.futures,sys,hashlib
ROOT=Path('/tmp/issue-lead-33252-r2/evidence-capture')
MAN=json.loads((ROOT/'manifest.json').read_text())
SCRIPTS=sorted([p for p in ROOT.iterdir() if p.suffix in ('.py','.js','.mjs','.sh') and '.private.' not in p.name])
SCRIPTS.append(ROOT/'reproduce.md')
files=set(SCRIPTS)
if (ROOT/'validation.json').exists():files.add(ROOT/'validation.json')
if (ROOT/'cleanup-report.json').exists():files.add(ROOT/'cleanup-report.json')
for e in MAN:
 for key,value in e.items():
  if key.endswith('Source') or not isinstance(value,str):continue
  if '--after-only' in sys.argv and not key.startswith('after'):continue
  p=Path(value)
  if p.is_file():files.add(p)
# Contact sheets are independently reviewed extracts of each complete recording.
files.update((ROOT/'publish').glob(('after-*-contact.png' if '--after-only' in sys.argv else '*-contact.png')))
mapfile=ROOT/'upload-map.json'
uploads=json.loads(mapfile.read_text()) if mapfile.exists() else {}
hashfile=ROOT/'upload-hashes.json'
hashes=json.loads(hashfile.read_text()) if hashfile.exists() else {}
digests={str(p):hashlib.sha256(p.read_bytes()).hexdigest() for p in files}
pending=[p for p in sorted(files) if str(p) not in uploads or hashes.get(str(p))!=digests[str(p)]]
def send(batch):
 p=subprocess.run(['ghdrop','upload','-m',*map(str,batch)],capture_output=True,text=True,check=True)
 lines=[line for line in p.stdout.splitlines() if re.match(r'!?\[.+\]\(https://',line)]
 if len(lines)!=len(batch):raise RuntimeError(p.stdout+p.stderr)
 return batch,lines,p.stdout
batches=[pending[i:i+8] for i in range(0,len(pending),8)]
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
 for batch,lines,raw in pool.map(send,batches):
  with (ROOT/'ghdrop-assets-verbatim.md').open('a') as f:f.write(raw)
  uploads.update({str(p):line for p,line in zip(batch,lines)})
  mapfile.write_text(json.dumps(uploads,indent=2))
  hashes.update({str(p):digests[str(p)] for p in batch});hashfile.write_text(json.dumps(hashes,indent=2))
  print('Uploaded',len(uploads),'assets',flush=True)
if '--assets-only' in sys.argv:sys.exit(0)
B='c6ca6056309904f89b90b6055d797973be0a0939';A='21e22d030ca430ef680082630274b3b1c073d35c'
commit=lambda s:f'[{s[:10]}](https://git.cafe/pingdotgg/t3code/commit/{s})'
link=lambda path:uploads[str(path)]
names={'L':'Cold scan → populated Pi result','P':'One Pi repository, three sessions, selected','E':'Successful empty scan','M':'Mixed computers and global-loading mask','F':'Failed scan and Retry','C':'Cached refresh and error','T':'Truncated populated and empty results','G':'Groups, counts, selection, tooltips and overflow','A':'Default selection and controlled ages','D':'Permission denied, restored and revoked during import','I':'Import progress, completion and dismissal','O':'Completion outcomes and save failures','Z':'No setup environments','W':'Font, viewport and scroll stress'}
common=f'''Before {commit(B)}; after {commit(A)}. Web only. Headless Chrome 157.0.0.0 on Linux via agent-browser 0.41.2 and native CDP screenshots; no device emulation; DPR 1; default zoom; 16px root font, 100% scaling (W: 125%, 20px system-ui, resolving to Liberation Mono on this VM); reduced motion; frozen client clock 2026-11-09 09:40 UTC. Viewports are 1440×900 and 390×844 CSS px, each light/dark. Each environment JSON records the actual settings. Every still passed network-idle/fonts readiness and two pixel-identical frames 500ms apart. Diff0 marks every nonzero RGB-channel difference in full magenta; DiffT uses maximum channel difference >16/255. Absolute RGB×32 images are also linked.\n\nP/E use real isolated apps with three synthetic Pi sessions or empty isolated provider homes. L uses the real app and scanner with an explicitly instrumented transport gate: it delays the outgoing scan RPC until the scripted release; this makes the loader readable and does **not** measure scanner speed. M–W are explicitly labelled production-component harness captures with synthetic boundary values. No production data or session JSONL is uploaded.\n'''
scriptlines='\n'.join(link(p) for p in SCRIPTS)
rowpaths=[]
for row,name in names.items():
 text=f'## Evidence — {row}: {name}\n\n'+common+'\n'
 text+='### What else changed\n\n'
 text+='Context caveats: Escape remains open on both revisions; G keyboard-focus tooltip is unverified; W populated 390px/20px has existing horizontal overflow. See the index review notes.\n\nThe loader, description and successful-empty copy change. Where shorter text wraps onto fewer lines, the dialog height and vertically centered content move; the modal backdrop/shadow moves with it. No other source changes are part of this PR. Any additional observed difference is recorded in the index review notes.\n\n'
 for e in [x for x in MAN if x['row']==row]:
  label=f"{e['width']}×{900 if e['width']==1440 else 844} · {e['theme']}"
  text+=f'### {label}\n\n'
  if 'beforeVideo' in e:
   times={}
   for side in ('before','after'):
    p=e.get(side+'Timeline');times[side]=json.loads(Path(p).read_text()) if p else []
   text+='Same scripted interaction. Times mark action completion relative to sequence start, immediately after recording starts:\n\n| Step | Before time | After time |\n|---|---:|---:|\n'
   for i,t in enumerate(times['before']):
    other=times['after'][i] if i<len(times['after']) else {}
    action=t.get('action',t.get('event',''))
    bt=t.get('seconds',t.get('ms',0)/1000);at=other.get('seconds',other.get('ms',0)/1000)
    text+=f'| {action} | {bt:.2f}s | {at:.2f}s |\n'
   text+='\n| Before recording | After recording |\n|---|---|\n'
   text+=f"| {link(e['beforeVideo'])}<br>{commit(B)} | {link(e['afterVideo'])}<br>{commit(A)} |\n\n"
  if 'beforeVideo' in e:
   for side in ('before','after'):
    contact=Path(e[side+'Video']).with_name(Path(e[side+'Video']).stem+'-contact.png')
    if contact.exists():text+=side.capitalize()+' recording milestones: '+link(contact)+'\n\n'
  text+='| Before | After | Diff0 | DiffT |\n|---|---|---|---|\n'
  text+=f"| {link(e['before'])}<br>{commit(B)} | {link(e['after'])}<br>{commit(A)} | {link(e['Diff0'])}<br>{commit(B)} → {commit(A)} | {link(e['DiffT'])}<br>{commit(B)} → {commit(A)} |\n\n"
  text+=f"Comparison sheet: {link(e['sheet'])}\n\nAmplified absolute difference: {link(e['amplified32'])}\n\n"
  for side in ('before','after'):
   extras=[link(e[side+k]) for k in ('Environment','Timeline','Steps','Result') if side+k in e]
   if extras:text+=side.capitalize()+' metadata: '+', '.join(extras)+'\n\n'
 text+='### Reproduction scripts\n\n'+scriptlines+'\n\nAI-assisted; verified with the tests and tools listed above.\n'
 p=ROOT/f'evidence-row-{row}.md';p.write_text(text);rowpaths.append(p)
result=subprocess.run(['ghdrop','upload','-m',*map(str,rowpaths)],capture_output=True,text=True,check=True)
(ROOT/'ghdrop-sheets-verbatim.md').write_text(result.stdout)
sheetlinks=result.stdout.strip().splitlines()
notes=(ROOT/'review-notes.md').read_text() if (ROOT/'review-notes.md').exists() else 'Review pending.'
index='## Evidence\n\n'+common+'\n14/14 matrix rows captured, with four viewport/theme pairs each (56 before/after pairs).\n\n### Review notes and unverified coverage\n\n'+notes+'\n\n### Sheets\n\n'+'\n'.join(sheetlinks)+'\n\n### Reproduction scripts\n\n'+scriptlines+'\n\n'
for row,name in names.items():
 index+='\n### '+row+' — '+name+'\n\n'
 index+='Recordings, action timestamps, environment metadata and full comparison sheets: '+sheetlinks[list(names).index(row)]+'\n\n'
 index+='| Viewport · theme | Before | After | Diff0 | DiffT |\n|---|---|---|---|---|\n'
 index+=f'| Revision | {commit(B)} | {commit(A)} | {commit(B)} → {commit(A)} | {commit(B)} → {commit(A)} |\n'
 for e in [x for x in MAN if x['row']==row]:
  label=f"{e['width']}×{900 if e['width']==1440 else 844} · {e['theme']}"
  index+=f"| {label} | {link(e['before'])} | {link(e['after'])} | {link(e['Diff0'])} | {link(e['DiffT'])} |\n"
 index+='\n'
index+='\nAI-assisted; verified with the tests and tools listed above.\n'
if (ROOT/'validation.json').exists():index+='\nArtifact validation: '+link(ROOT/'validation.json')+'\n'
if (ROOT/'cleanup-report.json').exists():index+='\nCleanup report: '+link(ROOT/'cleanup-report.json')+'\n'
(ROOT/'evidence.md').write_text(index)
print('Evidence section length:',len(index),'characters',flush=True)
result=subprocess.run(['ghdrop','upload','-m',str(ROOT/'evidence.md')],capture_output=True,text=True,check=True)
(ROOT/'ghdrop-index-verbatim.md').write_text(result.stdout)
print(result.stdout,end='');print('\n'.join(sheetlinks))
