"""Draws the report's four SVG figures from the artifact's raw results. Standard library only.
Usage (from the artifact folder):  python3 -I figures/make_figures.py <output-dir>
"""
import json, math, sys
from pathlib import Path

HERE = Path(__file__).resolve().parent.parent
OUT = Path(sys.argv[1]) if len(sys.argv) > 1 else HERE / 'figures'
OUT.mkdir(parents=True, exist_ok=True)

INK, INK2, MUTED, GRID, SURF = '#1d1d1b', '#52514e', '#8a8984', '#e4e3df', '#ffffff'
BLUE, BLUE_L, ORANGE, GREY = '#2a78d6', '#9cc1ee', '#eb6834', '#b9b8b2'
FONT = "font-family=\"'Inter','Helvetica Neue',Arial,sans-serif\""


def wilson(k, n, z=1.959963984540054):
    p = k / n
    d = 1 + z * z / n
    c = (p + z * z / (2 * n)) / d
    h = z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / d
    return max(0.0, c - h), min(1.0, c + h)


def esc(s):
    return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')


def svg(w, h, body, title):
    return (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}" width="{w}" height="{h}" role="img" aria-label="{esc(title)}">'
            f'<title>{esc(title)}</title><rect width="{w}" height="{h}" fill="{SURF}"/>{body}</svg>\n')


def text(x, y, s, size=12, fill=INK, anchor='start', weight='400'):
    return f'<text x="{x}" y="{y}" {FONT} font-size="{size}" fill="{fill}" text-anchor="{anchor}" font-weight="{weight}">{esc(s)}</text>'


def hbar_chart(rows, title, subtitle, note, fname, xmax=1.0, ticks=(0, .25, .5, .75, 1), fmt=lambda v: f'{v:.0%}', label_w=230):
    W, top, rh = 640, 74, 30
    H = top + rh * len(rows) + 64
    x0, x1 = label_w, W - 70
    sx = lambda v: x0 + (x1 - x0) * v / xmax
    b = [text(16, 26, title, 15, INK, weight='600'), text(16, 46, subtitle, 12, INK2)]
    for t in ticks:
        b.append(f'<line x1="{sx(t):.1f}" y1="{top - 8}" x2="{sx(t):.1f}" y2="{top + rh * len(rows)}" stroke="{GRID}" stroke-width="1"/>')
        b.append(text(sx(t), top + rh * len(rows) + 16, fmt(t), 11, MUTED, 'middle'))
    for i, r in enumerate(rows):
        y = top + i * rh
        k, n = r['k'], r['n']
        p = k / n
        lo, hi = wilson(k, n)
        color = r.get('color', BLUE)
        b.append(text(label_w - 10, y + 16, r['label'], 12, INK, 'end', r.get('weight', '400')))
        w = max(0.0, sx(p) - x0)
        if w > 0:
            b.append(f'<rect x="{x0}" y="{y + 6}" width="{w:.1f}" height="14" rx="3" fill="{color}"/>')
        b.append(f'<line x1="{sx(lo):.1f}" y1="{y + 13}" x2="{sx(hi):.1f}" y2="{y + 13}" stroke="{INK}" stroke-width="1.5"/>')
        for e in (lo, hi):
            b.append(f'<line x1="{sx(e):.1f}" y1="{y + 8}" x2="{sx(e):.1f}" y2="{y + 18}" stroke="{INK}" stroke-width="1.5"/>')
        b.append(text(min(sx(hi), x1) + 8, y + 17, r.get('value', f'{k}/{n}'), 12, INK2))
    b.append(text(16, H - 14, note, 11, MUTED))
    (OUT / fname).write_text(svg(W, H, ''.join(b), title + '. ' + subtitle))


careeros = json.loads((HERE / 'careeros' / 'gate-study-results.json').read_text())
adv_total = careeros['e1']['adversarial']['total']
loo = careeros['e2_leaveOneOut']

# Figure 1 (hero): ablation ------------------------------------------------------------
rows = [{'label': 'All seven gates', 'k': careeros['e1']['adversarial']['blocked'], 'n': adv_total, 'weight': '600'}]
for g in ['numbers', 'attribution', 'privacy', 'links', 'citations', 'length', 'style']:
    rows.append({'label': f'Without the {g} gate', 'k': loo[g]['blocked'], 'n': adv_total, 'color': BLUE_L})
rows.append({'label': 'Gates before commit 70b84e6', 'k': careeros['e4_preM4']['currentHarness']['blocked'], 'n': adv_total, 'color': GREY})
rows.append({'label': 'No gates', 'k': careeros['e3_ungated']['adversarial']['blocked'], 'n': adv_total, 'color': GREY})
hbar_chart(rows, 'Adversarial drafts blocked before publication',
           f'{adv_total} hand-written bad drafts from the CareerOS eval suite; bars = share blocked, whiskers = Wilson 95% CI',
           'Deterministic gate code only: no language model was called. Source: artifact/careeros/gate-study-results.json',
           'fig-ablation.svg')

# Figure 3: paraphrase probe -------------------------------------------------------------
probes = careeros['e5_probes']
def kind_rows(kind, label):
    ps = [p for p in probes if p['kind'] == kind]
    return {'label': label, 'k': sum(p['blocked'] for p in ps), 'n': len(ps)}
rows = [kind_rows('number', 'Invented quantities'), kind_rows('link', 'Unsupplied links'),
        kind_rows('attribution', 'Over-claimed ownership'), kind_rows('qualitative', 'Invented features, superlatives')]
ben = [p for p in probes if p['kind'] == 'benign']
rows.append({'label': 'Benign controls (should pass)', 'k': sum(p['blocked'] for p in ben), 'n': len(ben), 'color': ORANGE, 'value': f"{sum(p['blocked'] for p in ben)}/{len(ben)} blocked"})
hbar_chart(rows, 'Rephrased violations the gates were not written against',
           '24 exploratory probes written after the gates (not held out); share blocked, Wilson 95% CI',
           'Same deterministic gates as the ablation figure. Source: artifact/careeros/gate-study-results.json (e5_probes)',
           'fig-probes.svg')

# Figure 4: provenance sample -------------------------------------------------------------
summ = json.loads((HERE / 'adelante' / 'results' / 'sample-summary.json').read_text())
ann = json.loads((HERE / 'adelante' / 'annotations-200.json').read_text())['positives']
union = set(ann['M1_high_stakes_claim']) | set(ann['M2_category_mismatch_strict']) | set(ann['M3_familiar_saying_strict']) | set(ann['M4_incoherent']) | set(ann['M5_unsupported_quantity'])
n = summ['n']
a = summ['annotated']
rows = [
    {'label': 'Any editorial issue (strict union)', 'k': len(union), 'n': n, 'weight': '600'},
    {'label': 'Health, money or job claim', 'k': a['M1_high_stakes_claim']['k'], 'n': n, 'color': BLUE_L},
    {'label': 'Doctrinal claim (faith editor)', 'k': a['M1b_doctrinal_claim']['k'], 'n': n, 'color': GREY},
    {'label': 'Familiar saying, labelled original', 'k': a['M3_familiar_saying_strict']['k'], 'n': n, 'color': BLUE_L},
    {'label': 'Incoherent wording', 'k': a['M4_incoherent']['k'], 'n': n, 'color': BLUE_L},
    {'label': 'Off-topic for its category', 'k': a['M2_category_mismatch_strict']['k'], 'n': n, 'color': BLUE_L},
    {'label': 'Unsupported quantity', 'k': a['M5_unsupported_quantity']['k'], 'n': n, 'color': BLUE_L},
]
hbar_chart(rows, 'What a random sample of the generated corpus contains',
           f'Seeded uniform sample, {n} of {summ["population"]:,} records; one annotator; Wilson 95% CI',
           'Every sampled record (200/200) carried reviewed/approved flags with no review date. Source: artifact/adelante/results/',
           'fig-provenance-sample.svg', xmax=0.15, ticks=(0, .05, .10, .15))

# Figure 2: pipeline ----------------------------------------------------------------------
W, H = 640, 530
b = [text(16, 26, 'Where the gates sit in the CareerOS drafting pipeline', 15, INK, weight='600'),
     text(16, 46, 'Untrusted text flows down; only the deterministic gates and a person decide what is publishable', 12, INK2)]
steps = [
    ('GitHub event and its evidence', 'release notes, PR text: treated as untrusted data', GREY),
    ('Relevance rules', 'deterministic: skip bots, docs, drafts, pre-releases', GREY),
    ('Claim extraction (provider)', 'every claim read from notes stays "inferred"', BLUE_L),
    ('Drafting (provider)', 'one variant per channel, citing claim IDs', BLUE_L),
    ('Seven gates (pure code)', 'numbers, attribution, citations, privacy, links, length, style', BLUE),
    ('Human review and approval', 'approval re-runs the gates in the same transaction', ORANGE),
    ('Publish to a channel', 'portfolio, README, LinkedIn kit, résumé, feed', GREY),
]
y = 66
for i, (t, s, c) in enumerate(steps):
    fill = c if c in (BLUE,) else SURF
    tc = SURF if c == BLUE else INK
    sc = SURF if c == BLUE else INK2
    b.append(f'<rect x="120" y="{y}" width="400" height="44" rx="8" fill="{fill}" stroke="{c}" stroke-width="2"/>')
    b.append(text(320, y + 19, t, 13, tc, 'middle', '600'))
    b.append(text(320, y + 35, s, 11, sc, 'middle'))
    if i < len(steps) - 1:
        b.append(f'<line x1="320" y1="{y + 44}" x2="320" y2="{y + 58}" stroke="{INK2}" stroke-width="1.5"/>')
        b.append(f'<path d="M314 {y + 53} L320 {y + 60} L326 {y + 53}" fill="none" stroke="{INK2}" stroke-width="1.5"/>')
    y += 60
b.append(text(16, H - 14, 'Blue outline = language-model step (templates in the deterministic provider). Filled blue = the gates studied here.', 11, MUTED))
(OUT / 'fig-pipeline.svg').write_text(svg(W, H, ''.join(b), 'CareerOS pipeline: evidence, relevance, extraction, drafting, gates, human approval, publish'))
print('wrote', sorted(p.name for p in OUT.glob('*.svg')))
