#!/usr/bin/env python3
"""Produce the browsable index and PR-ready evidence from uploaded artifacts."""
import datetime as dt
import html
import json
import re
from pathlib import Path

ROOT = Path(__file__).parent
RUN = ROOT / 'run-2026-10-30T0757'
receipts = (ROOT / 'uploads.md').read_text()
receipts += (ROOT / 'index-script-upload.md').read_text()
urls = dict(re.findall(r'!?\[([^]]+)\]\((https://[^)]+)\)', receipts))
results = json.loads((RUN / 'results.json').read_text())
records = {(r['side'], r['row']): r for r in results}
before_sha = '1cf14a498fa27712a305097012c6f8f920a6da33'
after_sha = '8f66a27e8d63292f6bdaf1e3a546debaa93797f5'
commit = 'https://git.cafe/pingdotgg/t3code/commit/'
bref = f'[{before_sha[:10]}]({commit}{before_sha})'
aref = f'[{after_sha[:10]}]({commit}{after_sha})'
environment = ('Chromium 156.0.0.0, Linux x86_64, headless T3 preview browser on fs-39; '
               '1440 × 900 CSS px, dark, DPR 2, no device emulation, 100% font scaling and browser zoom. '
               'Both contexts have the preview browser’s clipboard-write permission grant; iframe sandbox and '
               'Permissions Policy remain enabled. Whole-surface PNGs are automatically downsampled by the tool '
               'to 1280 × 800; MP4s are 2880 × 1800. Top-page Date/Date.now are fixed to '
               '2026-10-30T08:00:00Z after seeding; the fixture contains no clock. Server and turn timing remain real. '
               'Reduced-motion emulation is unavailable in this preview tool.')
scope = ('3/4 matrix rows are captured in their specified mode: R1/R2 in the real app, R3 as the requested '
         'panel-state harness. R4 also has a supplementary component-harness pair, so all four iframe contexts '
         'have before/after recordings. The real redeemed-guest row remains UNVERIFIED: T3 Connect is unavailable.')
incidental = ('Incidental difference beyond clipboard behavior: the independently generated real turns show '
              '“Worked for 19s” before and “Worked for 35s” after. This is fixture timing, not a change made by the fix.')
changes = [
    'The page output changes from a Permissions Policy NotAllowedError to copied in every captured context.',
    'Pasting yields clipboard-sentinel-27402 before and exactly echo hello-27402 after. No paste is sent.',
    'The longer inline error makes the real inline frame 92px tall before versus 80px after, moving done below it by 12 CSS px. R2’s closed-dialog final frame inherits that R1 inline state.',
    'In the R4 harness the same 92px-versus-80px content-height difference moves the neutral paste label and textarea by 12 CSS px, including rounded-border antialiasing pixels.',
    'The visible real-turn duration differs: 19s before versus 35s after in R1/R2-final/R3. It is obscured by the dialog backdrop in R2-dialog and absent from the R4 harness.',
    'During recordings the pointer, focus ring, caret, and dialog opening/closing reflect the scripted interactions; exact wall-clock pacing varies slightly. Settled stills blur the paste target and move the pointer to matching blank space.',
]
rows = [
    ('R1', 'Owner inline — REAL APP', 'R1-final', 'Real Codex html_render turn through each revision’s isolated server; identical published HTML.'),
    ('R2', 'Owner full-screen at 100% — REAL APP', 'R2-dialog', 'Same real asset, opened through the actual Open full screen button; a fresh dialog document starts idle.'),
    ('R3', 'Flagged attachment panel — HARNESS', 'R3-final', 'Existing panel store seeded with the real attachment, exact 7547-byte size, and htmlRender:true. Filename visibly labels HARNESS R3. No production producer of this flag is established.'),
    ('R4', 'GuestHtmlRender — COMPONENT HARNESS ONLY', 'R4-final', 'Actual GuestHtmlRender from each revision, mounted with its real iframe attributes and a measured 80px reference. The unavailable guest URL is replaced by that revision’s real owner asset URL. No guest invite, redemption, session, timeline, HTTPS route, or guest-response CSP is verified.'),
]

def link(name, label=None):
    return f'[{label or name}]({urls[name]})'

def image(name, label):
    return f'![{label}]({urls[name]})'

def relative(record, contains):
    start = dt.datetime.fromisoformat(record['startedAt'].replace('Z', '+00:00'))
    event = next((entry for entry in record['timeline'] if contains in entry['step']), None)
    if event is None:
        return '—'
    at = dt.datetime.fromisoformat(event['at'].replace('Z', '+00:00'))
    return f'{(at-start).total_seconds():.2f}s'

def steps(row):
    steps = [('Idle visible', 'idle page observed'), ('Click page Copy', 'click page Copy'), ('Status observed', ' observe '), ('Ctrl+V into unsent target', 'paste actual clipboard'), ('Final settled still', row+'-final settled')]
    if row == 'R2':
        steps.insert(0, ('Open actual full-screen dialog', 'open actual full-screen'))
        steps.insert(4, ('Close actual dialog', 'close actual dialog'))
    return [(label, relative(records['before', row], match), relative(records['after', row], match)) for label, match in steps]

def markdown_pair(pair):
    return ('| Before | After | Diff0 — zero threshold | DiffT — >16/255 |\n'
            '| --- | --- | --- | --- |\n'
            f'| {image("before-"+pair+".png", "Before — "+pair)}<br>{bref} | '
            f'{image("after-"+pair+".png", "After — "+pair)}<br>{aref} | '
            f'{image(pair+"-diff0.png", "Diff0 — "+pair)}<br>{bref} → {aref} | '
            f'{image(pair+"-diff16.png", "DiffT — "+pair)}<br>{bref} → {aref} |\n')

md = ['## Evidence', '', incidental, '', scope, '', f'Before {bref}; after {aref}. Issue [#27402](https://git.cafe/pingdotgg/t3code/issues/27402).', '', environment, '',
      'Both artifacts are byte-identical: 7547 bytes, SHA-256 `f2bc7f93e2818d1f1910ac1fad06530376b97473bc0c72c8023688ac5c4afbcc`. Both were published by a real Codex GPT-6-Astra / Medium turn with the same prompt. Fresh homes were created at each revision’s migration level, with T3CODE_DEV_SEED=0; no live database was copied.', '',
      'Before every accepted still: fonts loaded; 500ms resource quiet window; two screenshots at least 500ms apart were pixel-identical. All accepted captures, video inspection strips, and diffs were opened and inspected. Diff0 amplifies every nonzero maximum RGB-channel delta by 255; DiffT marks maximum RGB-channel delta >16 on a dimmed before image. No masks, crops, or alignment transforms are used.', '',
      '### What else changed', '', *['- '+change for change in changes], '']
sections = []
for row, title, pair, detail in rows:
    md += [f'### {row} — {title} — 1440 × 900, dark', '', detail, '', environment, '',
           'Steps below are tool action timestamps relative to the recording start; video encoding can shift the frame boundary slightly. Clipboard is reset to `clipboard-sentinel-27402` before each recording.', '',
           '| Action | Before | After |', '| --- | --- | --- |', *[f'| {label} | {before} | {after} |' for label, before, after in steps(row)], '',
           '| Before recording | After recording |', '| --- | --- |', f'| {link("before-"+row+".mp4")}<br>{bref} | {link("after-"+row+".mp4")}<br>{aref} |', '', markdown_pair(pair), '', link(pair+'-sheet.png', row+' sheet'), '']
    if row == 'R2':
        md += ['After closing the dialog, the recording pastes into the unsent composer. This additional final pair records that result:', '', markdown_pair('R2-final'), '', link('R2-final-sheet.png', 'R2 final paste sheet'), '']
    action_rows = ''.join(f'<tr><td>{html.escape(label)}</td><td>{before}</td><td>{after}</td></tr>' for label, before, after in steps(row))
    cells = ''.join(f'<figure><figcaption>{side.title()} — {(before_sha if side=="before" else after_sha)[:10]}</figcaption><video controls preload="metadata" src="{urls[side+"-"+row+".mp4"]}"></video></figure>' for side in ['before', 'after'])
    sections.append(f'<section id="{row}"><h2>{html.escape(row+" — "+title)}</h2><p>{html.escape(detail)}</p><p class="environment">{html.escape(environment)}</p><p>Clipboard reset to <code>clipboard-sentinel-27402</code>. Same click → status → Ctrl+V interaction. Times are seconds after the tool’s recording start.</p><table><thead><tr><th>Action</th><th>Before</th><th>After</th></tr></thead><tbody>{action_rows}</tbody></table><div class="pair">{cells}</div><p><a href="{urls[pair+"-sheet.png"]}">Open full-size {row} sheet</a></p><img alt="{html.escape(row)} Before, After, Diff0, DiffT" src="{urls[pair+"-sheet.png"]}">'+(f'<p>R2 final paste: <a href="{urls["R2-final-sheet.png"]}">additional sheet</a></p>' if row=='R2' else '')+'</section>')

script_names = ['seed-driver.mjs', 'seed-prompt.txt', 'fixture.html', 'capture-driver.mjs', 'open-panel-harness.js', 'guest-component-harness.js', 'generate-evidence.py', 'package-evidence.py', 'build-index.py', 'rebuild.md']
md += ['### Rebuild and raw records', '', *['- '+link(name) for name in script_names], '', link('results.json', 'Exact action timestamps and pasted results')+' · '+link('diff-metrics.json', 'Diff metrics')+' · '+link('matrix.md', 'Original fixture matrix')+' · '+link('raw-capture-history.zip', 'Raw capture history, probes, inspection frames and scripts'), '',
       '### Unverified', '', '- R4 real redeemed guest flow: T3 Connect unavailable on the VM (permanent environment limitation). The component harness does not close this gap.', '- R3 production entry path: no producer of htmlRender:true was established; the explicit panel-state harness is the matrix’s requested proof mode.', '',
       '### Cleanup', '', 'Both tabs and all four dev listeners were stopped. The baseline worktree created for this capture was removed. The after checkout remains clean at the requested head. No code, commit, PR, or public forge comment was created. The first baseline install succeeded; uploads succeeded without DNS retries. Pairing credentials and server homes are excluded from published files.', '',
       '### ghdrop output (verbatim)', '', receipts, '',
       'FRICTION:', '- infra | T3 Connect unavailable on the VM; real R4 invite/redemption/guest route cannot be exercised | a Connect-capable environment.', '- tool | Preview PNGs are capped at 1280px wide and reduced-motion emulation is unavailable | full-resolution PNG and reduced-motion controls; the report discloses both limits.', '- model | Initial same-URL iframe reset retained state; exploratory runs were superseded | force a document reload and assert idle before every accepted recording.', '',
       'VERDICT: Owner clipboard behavior verified before/after; flagged attachment and guest component harnesses pass after. Real guest flow remains unverified.']
(ROOT / 'evidence.md').write_text('\n'.join(md))
script_links = ''.join(f'<li><a href="{urls[name]}">{html.escape(name)}</a></li>' for name in script_names)
all_links = ''.join(f'<li><a href="{url}">{html.escape(name)}</a></li>' for name, url in urls.items())
page = '<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Clipboard evidence — issue 27402</title><style>body{margin:32px auto;max-width:1600px;padding:0 24px;font:16px/1.5 system-ui,sans-serif;background:#0b0b0b;color:#eee}a{color:#90b5ff}h1{font-size:30px}h2{margin-top:40px}section{border-top:1px solid #444;margin-top:40px;padding-top:8px}.notice{border-left:4px solid #eab308;padding-left:16px}.environment{color:#bbb;font-size:14px}table{border-collapse:collapse;margin:20px 0;width:100%}td,th{border:1px solid #444;padding:8px;text-align:left}.pair{display:grid;grid-template-columns:1fr 1fr;gap:20px}figure{margin:0}video,img{width:100%;height:auto}figcaption{margin-bottom:8px}code{background:#242424;padding:2px 4px}pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px}summary{cursor:pointer}@media(max-width:800px){.pair{grid-template-columns:1fr}}</style><h1>Clipboard evidence — <a href="https://git.cafe/pingdotgg/t3code/issues/27402">#27402</a></h1>'
page += f'<p class="notice">{html.escape(incidental)}</p><p class="notice">{html.escape(scope)}</p><p>Before <a href="{commit}{before_sha}">{before_sha[:10]}</a> → after <a href="{commit}{after_sha}">{after_sha[:10]}</a>.</p><p>{html.escape(environment)}</p><p>Identical real published HTML: 7547 bytes; SHA-256 <code>f2bc7f93e2818d1f1910ac1fad06530376b97473bc0c72c8023688ac5c4afbcc</code>. Fresh isolated state on each revision; no live database seed.</p><h2>What else changed</h2><ul>'+''.join('<li>'+html.escape(change)+'</li>' for change in changes)+'</ul>'
page += ''.join(sections)
page += '<h2>Rebuild scripts</h2><ul>'+script_links+'</ul><p>Accepted stills passed exact pixel equality over two captures at least 500ms apart after fonts and a 500ms resource quiet window. All stills, diffs and video inspection strips were opened and reviewed. Diff0: every nonzero maximum RGB delta amplified to white; DiffT: maximum RGB delta &gt;16/255 in magenta. No alignment or masks.</p><h2>Unverified</h2><p>Real R4 guest flow: T3 Connect is unavailable. R4 harness substitutes the real owner asset route; it is not proof of invitation, redemption, guest session, HTTPS, or guest-response CSP. R3 has no established production producer of the explicit flag.</p><h2>Files</h2><ul>'+all_links+'</ul><details><summary>ghdrop output (verbatim)</summary><pre>'+html.escape(receipts)+'</pre></details><p>Cleanup: tabs and dev listeners stopped; baseline worktree removed; after checkout clean and unchanged. No PR or public forge comment.</p></html>'
(ROOT / 'index.html').write_text(page)
print(f'generated index.html and evidence.md from {len(urls)} uploaded files')
