#!/usr/bin/env python3
"""Produce plain diff -u from complete transcripts, keeping raw files untouched.
Normalize only run-specific metadata: side labels, paths, PIDs/namespaces,
ports, timestamps, UUIDs, runtime-record hashes, lock inode IDs, log fibers.
All outcomes, errors, modes, lock hashes, wait codes and state transitions stay.
"""
import pathlib,re,subprocess,json
root=pathlib.Path('/tmp/lead-28256-r1/evidence-capture')
out=root/'normalized';out.mkdir(exist_ok=True)
manifest={}
def normalize(s):
 s=re.sub(r'/home/ubuntu/wt/pr-28256(?:-base)?','<CHECKOUT>',s)
 s=re.sub(r'/tmp/lead-28256-r1/evidence-capture/\d{8}T\d{6}\.\d+Z-F\d+-\w+','<RUN>',s)
 s=s.replace('/tmp/lead-28256-r1/evidence-capture','<DRIVER_DIR>')
 s=s.replace('/home/ubuntu/.local/share/vite-plus/js_runtime/node/26.12.1/bin/node','<NODE>')
 s=re.sub(r'ROW (F\d+) SIDE (before|after)',r'ROW \1 SIDE <SIDE>',s)
 s=re.sub(r'pid:\[\d+\]','pid:[<PIDNS>]',s)
 s=re.sub(r'\bpidns=pid:\[[^]]+\]','pidns=pid:[<PIDNS>]',s)
 s=re.sub(r'(?i)(\b(?:probePid|pid)["\']?\s*[:=]\s*)\d+',r'\1<PID>',s)
 s=re.sub(r'(?i)(\bpid\s+)\d+',r'\1<PID>',s)
 s=re.sub(r'\(node:\d+\)','(node:<PID>)',s)
 s=re.sub(r'("port"\s*:\s*)\d+',r'\1<PORT>',s)
 s=re.sub(r'(--port\s+)\d+',r'\1<PORT>',s)
 s=re.sub(r'(127\.0\.0\.1:)\d+',r'\1<PORT>',s)
 s=re.sub(r'\b\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|\+00:00)', '<TIMESTAMP>',s)
 s=re.sub(r'\[\d\d:\d\d:\d\d\.\d+\]','[<TIME>]',s)
 s=re.sub(r'\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b','<UUID>',s)
 s=re.sub(r'(R path=\S+ sha256=)[0-9a-f]{64}',r'\1<R-SHA256>',s)
 s=re.sub(r'("inode"\s*:\s*)\d+',r'\1<INODE>',s)
 s=re.sub(r'(inode=)\d+',r'\1<INODE>',s)
 s=re.sub(r'\(#\d+\)','(#<FIBER>)',s)
 return s
for row in ['F06','F07','F08','F09','F10','F11']:
 manifest[row]={}
 for side in ['before','after']:
  files=sorted(root.glob(f'20*-{row}-{side}/{row}-{side}.txt'))
  valid=[f for f in files if 'ROW COMPLETE '+row in f.read_text()]
  if not valid:raise RuntimeError('no complete capture '+row+' '+side)
  raw=valid[-1];target=out/f'{row}-{side}.normalized.txt';target.write_text(normalize(raw.read_text()))
  manifest[row][side]={'raw':str(raw),'normalized':str(target),'run':str(raw.parent)}
 diff=root/f'{row}.diff'
 argv=['diff','-u','--label',f'{row}-before.normalized.txt','--label',f'{row}-after.normalized.txt',manifest[row]['before']['normalized'],manifest[row]['after']['normalized']]
 p=subprocess.run(argv,capture_output=True,text=True)
 if p.returncode not in [0,1]:raise RuntimeError(p.stderr)
 diff.write_text(p.stdout)
 manifest[row]['diff']=str(diff)
(root/'manifest.json').write_text(json.dumps(manifest,indent=2)+'\n')
print(json.dumps({row:{k:v if isinstance(v,str) else v['raw'] for k,v in r.items()} for row,r in manifest.items()},indent=2))
