#!/usr/bin/env python3
"""Renders _tools/TRACKER.md (the kill-system checklist) into 59_tracker.html for the local index.
The coordinator runs it at every check-in after updating statuses:  python3 _tools/make_tracker.py"""
import html, os, re, time

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SRC = os.path.join(ROOT, '_tools', 'TRACKER.md')
OUT = os.path.join(ROOT, '59_tracker.html')

STATUS = {  # label, chip colour, text colour
    'done': ('DONE', '#7CFFA3', '#10301c'), 'review': ('REVIEW', '#3FEAF5', '#0f2a30'),
    'doing': ('DOING', '#FFD23F', '#3a2c00'), 'todo': ('TO DO', '#3A3458', '#FFF6E0'),
    'blocked': ('BLOCKED', '#FF4FA3', '#3a0a22'), 'waiting': ('WAITING', '#6f6890', '#FFF6E0'),
}


def parse(text):
    layers, cur = [], None
    for line in text.splitlines():
        if line.startswith('## '):
            cur = {'title': line[3:].strip(), 'rows': []}
            layers.append(cur)
        elif cur is not None and line.startswith('|') and not re.match(r'^\|\s*-', line) and not line.startswith('| id '):
            cells = [c.strip() for c in line.strip().strip('|').split('|')]
            if len(cells) >= 6:
                cur['rows'].append(dict(id=cells[0], task=cells[1], owner=cells[2], dep=cells[3], st=cells[4].lower(), note=cells[5]))
    return layers


def esc(s):
    s = html.escape(s)
    return re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', s)


def main():
    text = open(SRC, encoding='utf-8').read()
    layers = parse(text)
    rows = [r for L in layers for r in L['rows']]
    total = len(rows)
    count = {k: sum(1 for r in rows if r['st'] == k) for k in STATUS}
    pct = 100 * count['done'] / max(1, total)
    gate = next((r for r in rows if r['id'] == 'K4.0'), None)
    sec = []
    for L in layers:
        rs = L['rows']
        d = sum(1 for r in rs if r['st'] == 'done')
        cards = []
        for r in rs:
            lab, bg, fg = STATUS.get(r['st'], (r['st'].upper(), '#3A3458', '#FFF6E0'))
            cards.append(f'''<div class="card st-{html.escape(r['st'])}">
  <div class="top"><span class="id">{esc(r['id'])}</span><span class="chip" style="background:{bg};color:{fg}">{lab}</span></div>
  <div class="task">{esc(r['task'])}</div>
  <div class="meta"><span class="own">{esc(r['owner'])}</span>{'' if r['dep'] in ('-', '') else f'<span class="dep">needs {esc(r["dep"])}</span>'}</div>
  {'' if not r['note'] else f'<div class="note">{esc(r["note"])}</div>'}
</div>''')
        sec.append(f'''<section><h2>{esc(L['title'])} <small>{d} / {len(rs)} done</small></h2>
<div class="lbar"><i style="width:{100 * d / max(1, len(rs)):.1f}%"></i></div>
<div class="grid">{''.join(cards)}</div></section>''')
    updated = time.strftime('%d %b %H:%M')
    page = f'''<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Kill System Tracker</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Lilita+One&family=Rubik:wght@400;500;600;700;800;900&display=swap">
<style>
:root {{ --ink:#1D1B24; --panel:#1E1B2B; --panel-2:#2A2640; --panel-3:#3A3458; --cream:#FFF6E0; --cream-2:rgba(255,246,224,.78); --cream-3:rgba(255,246,224,.55);
  --yellow:#FFD23F; --pink:#FF4FA3; --green:#7CFFA3; --cyan:#3FEAF5;
  --f-display:"Lilita One","Arial Rounded MT Bold",system-ui,sans-serif; --f-body:"Rubik",system-ui,-apple-system,sans-serif; --hard:3px 6px 0 var(--ink); }}
* {{ box-sizing:border-box; }} html,body {{ overflow-x:hidden; }}
body {{ margin:0; min-height:100vh; color:var(--cream); font:500 15px/1.45 var(--f-body);
  background: radial-gradient(60% 50% at 50% -8%, rgba(255,246,224,.18), transparent 70%), linear-gradient(180deg,#3A2D73,#231B49 38%,#140F2A 75%,#0F0C20); }}
.wrap {{ max-width:1320px; margin:0 auto; padding:24px 16px 60px; }}
a {{ color:var(--yellow); }}
h1 {{ font:400 clamp(30px,6vw,54px)/1 var(--f-display); text-align:center; margin:8px 0 6px; -webkit-text-stroke:6px var(--ink); paint-order:stroke fill; text-shadow:0 5px 0 var(--ink); }}
.sub {{ text-align:center; color:var(--cream-2); margin:0 auto 18px; max-width:820px; }}
.back {{ display:inline-block; margin-bottom:10px; font-weight:800; }}
.sum {{ display:flex; flex-wrap:wrap; gap:10px; align-items:center; justify-content:center; background:var(--panel); border:3px solid var(--ink); border-radius:18px; box-shadow:var(--hard); padding:14px 16px; margin:0 auto 18px; }}
.sum b {{ font:400 26px/1 var(--f-display); }}
.bar,.lbar {{ height:12px; flex:1 1 220px; min-width:160px; background:#151126; border:2px solid var(--ink); border-radius:99px; overflow:hidden; }}
.bar i,.lbar i {{ display:block; height:100%; background:linear-gradient(90deg,#7CFFA3,#3FEAF5); }}
.lbar {{ margin:4px 0 12px; }}
.pill {{ font-weight:800; font-size:12px; padding:4px 10px; border-radius:99px; border:2px solid var(--ink); }}
.gate {{ background:linear-gradient(90deg,rgba(255,79,163,.25),rgba(255,210,63,.18)); border:3px solid var(--ink); border-radius:16px; padding:12px 16px; margin:0 0 20px; box-shadow:var(--hard); font-weight:700; }}
section {{ margin:22px 0; }}
h2 {{ font:400 22px/1.15 var(--f-display); margin:0 0 4px; }} h2 small {{ font:700 13px/1 var(--f-body); color:var(--cream-3); margin-left:8px; }}
.grid {{ display:grid; grid-template-columns:repeat(auto-fill,minmax(290px,1fr)); gap:12px; }}
.card {{ background:var(--panel); border:3px solid var(--ink); border-radius:16px; padding:12px 14px; box-shadow:var(--hard); }}
.card.st-done {{ opacity:.78; }} .card.st-doing {{ outline:2px solid var(--yellow); }} .card.st-blocked {{ outline:2px solid var(--pink); }}
.top {{ display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; }}
.id {{ font:400 18px/1 var(--f-display); color:var(--yellow); }}
.chip {{ font-weight:800; font-size:11px; letter-spacing:.06em; padding:4px 9px; border-radius:99px; border:2px solid var(--ink); }}
.task {{ font-weight:600; }} .task b {{ color:var(--yellow); }}
.meta {{ display:flex; flex-wrap:wrap; gap:6px; margin-top:8px; font-size:12px; font-weight:700; }}
.own {{ background:var(--panel-3); padding:3px 8px; border-radius:99px; }} .dep {{ background:#151126; color:var(--cream-2); padding:3px 8px; border-radius:99px; }}
.note {{ margin-top:6px; font-size:12.5px; color:var(--cream-2); }}
.rule {{ text-align:center; color:var(--cream-3); font-size:13px; margin-top:24px; }}
</style></head><body><div class="wrap">
<a class="back" href="36_index.html">‹ back to the index</a>
<h1>THE KILL SYSTEM TRACKER</h1>
<p class="sub">Everything agreed on 24 Oct, built in dependency order: the least-dependent work first, in parallel. Quality never goes down. Updated {updated}.</p>
<div class="sum"><b>{count['done']} / {total} done</b><div class="bar"><i style="width:{pct:.1f}%"></i></div>
{''.join(f'<span class="pill" style="background:{STATUS[k][1]};color:{STATUS[k][2]}">{count[k]} {STATUS[k][0]}</span>' for k in ('doing', 'review', 'todo', 'blocked', 'waiting'))}</div>
{f'<div class="gate">GATE · {esc(gate["task"])}: no kill code goes into the game until every page is done and you approve them.</div>' if gate else ''}
{''.join(sec)}
<p class="rule">Source: _tools/TRACKER.md · rules: _tools/58k_BRIEF.md · plan: _tools/58k_PLAN.md</p>
</div></body></html>'''
    open(OUT, 'w', encoding='utf-8').write(page)
    print(f'59_tracker.html: {count["done"]}/{total} done, {count["doing"]} doing')


if __name__ == '__main__':
    main()
