"""Build lossless native-pixel inspection sheets and complete public screenshot archives.

Analytical composites retain each original screenshot's pixels. They are QA evidence,
not generated customer imagery. Full-page references are archived but never substituted
for readable section/run/state inspection. The manifest binds all originals and aliases.
"""
from pathlib import Path
import argparse,json,hashlib,zipfile,shutil,datetime
from PIL import Image,ImageDraw,ImageFont
root=Path(__file__).resolve().parents[1]
a=argparse.ArgumentParser();a.add_argument('--round',choices=['pre','post','post-v2'],required=True);args=a.parse_args();round_=args.round
baseline=root/'evidence'/dict(pre='mobile-pre-complete',post='mobile-post-complete',**{'post-v2':'mobile-post-v2'})[round_]
interactive=root/'evidence'/f'interactive-{round_}'
output=root/'evidence'/f'inspection-{round_}';output.mkdir(exist_ok=True)
public=root/'site/review/screenshots'/round_;public.mkdir(parents=True,exist_ok=True)
records=[];references=[];groups={}
for p in sorted(baseline.rglob('capture.json')):
 d=json.loads(p.read_text())
 if d['status']!='PASS':continue
 group=d['item']['id']+'--'+str(d['viewport']['requested']['width'])
 files=[]
 for c in d['captures']:
  f=p.parent/c['filename'];files.append(f)
  row={'asset_id':d['item']['id'],'partition':d['item']['partition'],'source_kind':'baseline','url':d['url'],'viewport':d['viewport']['requested'],'captured_at':c.get('capturedAt',d['finishedAt']),'file':str(f.resolve()),'name':c['filename'],'group':group,**{k:v for k,v in c.items() if k not in ['filename','capturedAt']}}
  if c['type']=='full-page':references.append(row)
  else:
   row['sha256']=hashlib.sha256(f.read_bytes()).hexdigest();records.append(row)
 groups[group]=files+[p]
for manifest in [interactive/'capture-manifest.json',root/'evidence'/f'extra-state-{round_}'/'capture-manifest.json',root/'evidence'/f'comment-fixture-{round_}'/'capture-manifest.json',root/'evidence'/f'legacy-full-{round_}-0'/'capture-manifest.json',root/'evidence'/f'legacy-full-{round_}-1'/'capture-manifest.json']:
 if not manifest.exists():continue
 d=json.loads(manifest.read_text())
 for c in d['records']:
  f=Path(c['file']);group='interactive-'+c['id']+'--'+str(c['requestedViewport']['width']);groups.setdefault(group,[]).append(f)
  records.append({'asset_id':c['id'],'partition':c['partition'],'source_kind':'interactive','url':c['url'],'viewport':c['requestedViewport'],'captured_at':c['capturedAt'],'file':str(f.resolve()),'name':c['name'],'group':group,'type':c['kind'],'state':c['state'],'sha256':c['sha256'],'actual_scroll':c['basis'],'measured_section':c['measuredSection']})
 shutil.copy2(manifest,public/(manifest.parent.name+'-capture-manifest.json'))
archives=[]
for group,files in sorted(groups.items()):
 parts=[];part=[];size=0
 for f in dict.fromkeys(files):
  n=f.stat().st_size
  if n>24*1024**2:raise ValueError(f'Individual screenshot above safe archive size: {f}')
  if part and size+n>24*1024**2:parts.append(part);part=[];size=0
  part.append(f);size+=n
 if part:parts.append(part)
 for i,part in enumerate(parts,1):
  name=f'{group}--part-{i:02d}.zip';q=public/name
  with zipfile.ZipFile(q,'w',compression=zipfile.ZIP_DEFLATED,compresslevel=6) as z:
   for f in part:z.write(f,f.name)
  assert q.stat().st_size<25*1024**2
  entries={f.name for f in part}
  archives.append({'group':group,'path':str(q.relative_to(root/'site')),'entries':sorted(entries),'sha256':hashlib.sha256(q.read_bytes()).hexdigest(),'bytes':q.stat().st_size})
  for row in records+references:
   if row['group']==group and row['name'] in entries:row['archive_path']=str(q.relative_to(root/'site'));row['archive_entry']=row['name']
# Exact pixel duplicates need one actual inspection, with all contexts mapped as aliases.
unique={}
for row in records:unique.setdefault(row['sha256'],[]).append(row)
font=ImageFont.load_default(size=15)
sheets=[]
for partition in ['acquisition','practice','internal','legacy']:
 entries=[(h,rows) for h,rows in unique.items() if any(r['partition']==partition for r in rows)]
 for k in range(0,len(entries),4):
  batch=entries[k:k+4];opened=[Image.open(rows[0]['file']).convert('RGB') for h,rows in batch];header=72
  sheet=Image.new('RGB',(sum(im.width for im in opened),max(im.height for im in opened)+header),'#ffffff');draw=ImageDraw.Draw(sheet);x=0;tiles=[]
  for (h,rows),im in zip(batch,opened):
   row=rows[0];label=f"{row['asset_id']} | {row['viewport']['width']}px | {row.get('state',row['type'])}\n{row['name'][:40]}\n{h[:16]} | {len(rows)} exact-pixel context(s)"
   draw.multiline_text((x+4,4),label,font=font,fill='#111111',spacing=3);sheet.paste(im,(x,header));tiles.append({'sha256':h,'bbox':[x,header,x+im.width,header+im.height],'source_file':row['file'],'aliases':rows});x+=im.width
  folder=output/partition;folder.mkdir(exist_ok=True);q=folder/f'sheet-{k//4+1:04d}.png';sheet.save(q)
  sheets.append({'partition':partition,'file':str(q.resolve()),'pixels':list(sheet.size),'lossless_no_resize':True,'tiles':tiles})
result={'round':round_,'generated_at':datetime.datetime.now(datetime.timezone.utc).isoformat(),'records':records,'full_page_references':references,'unique_pixel_records':len(unique),'sheets':sheets,'archives':archives,'actual_images_inspected':False,'inspection_owner':'Independent Luna xhigh reviewers. Preparing sheets is not a verdict.'}
(output/'inspection-manifest.json').write_text(json.dumps(result,ensure_ascii=False,indent=2))
# Public mapping contains original evidence identity and archive entry without private paths.
def sanitize(row):return {k:v for k,v in row.items() if k not in ['file']}
public_result={k:v for k,v in result.items() if k not in ['records','full_page_references','sheets']};public_result['records']=[sanitize(r) for r in records];public_result['full_page_references']=[sanitize(r) for r in references]
(public/'capture-index.json').write_text(json.dumps(public_result,ensure_ascii=False,indent=2))
(public/'README.md').write_text(f'''# Complete {round_} screenshot evidence

{len(records)} actual readable section, overlapping-run and interaction captures are bound to their original filenames, capture basis, URL, viewport and SHA-256 in [capture-index.json](capture-index.json). {len(references)} full-page references are archived separately from readable inspection evidence. Every original capture is available in the page/viewport ZIP named in its record, at the recorded archive entry. Archives are below 25 MiB and retain original PNG bytes.

Lossless analytical sheets prepare pixel inspection and are not generated customer imagery. Their preparation and automated geometry checks do not establish visual acceptance. Independent Luna observations and reports are supplied separately after actual inspection.
''')
print(json.dumps({'round':round_,'readable_records':len(records),'full_references':len(references),'unique_pixels':len(unique),'sheets':len(sheets),'archives':len(archives)},indent=2))
