#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""render_stands.py — generate where-felhom-stands.html from where-felhom-stands.yaml.

Run by hand, deliberately not wired into CI:

    python3 scripts/check_stands.py && python3 scripts/render_stands.py

WHY THIS EXISTS. The first version of this page was a React bundle: one 154 KB artefact whose
content sat gzip+base64 inside a JS module map. It rendered, and that was all it could do — it
could not be edited, a diff of it showed nothing a person could read, and it could not be
regenerated when a status moved. So it began going stale the moment it was committed, which is the
one thing a picture of "where we stand" must not do.

This emits plain static HTML with inline CSS and NO JavaScript. It is bigger on disk than it needs
to be and that is the trade: the page is a build product of a file a person can read and diff.

The palette and layout deliberately match the 2026-08-09 snapshot — the operator should recognise
the page, not learn a new one.
"""
import html
import os
import re
import sys

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SRC = os.path.join(ROOT, "documentation", "architecture", "where-felhom-stands.yaml")
OUT = os.path.join(ROOT, "documentation", "architecture", "where-felhom-stands.html")

DOT = {"walked": "#34d399", "built": "#60a5fa", "partial": "#fbbf24", "missing": "#64748b"}
LEGEND = [
    ("walked", "Walked", "done end to end on real hardware, evidence on file"),
    ("built", "Built", "shipped and tested, the real path never walked"),
    ("partial", "Partial", "some walked, some not — the note says which"),
    ("missing", "Missing", "does not exist"),
]
STAGES = {1: "Getting a box", 2: "Making it theirs", 3: "Using it", 4: "Drives",
          5: "Backing up", 6: "When something goes wrong", 7: "Getting everything back"}


def load(path):
    claims, cur, sect = [], None, None
    meta = {}
    for raw in open(path, encoding="utf-8"):
        line = raw.rstrip("\n")
        if line.startswith("verified_on:"):
            meta["date"] = line.split(":", 1)[1].strip()
        if line.startswith("  - id:"):
            cur = {"id": line.split(":", 1)[1].strip(), "sources": [], "changed": None}
            claims.append(cur)
            sect = None
            continue
        if cur is None:
            continue
        if line.strip() == "sources:":
            sect = "sources"
            continue
        if line.strip() == "changed:":
            sect = "changed"
            cur["changed"] = {}
            continue
        if line.strip() == "verified:":
            sect = "verified"
            continue
        m = re.match(r'\s+- (capability-map|evidence|register): (.*)$', line)
        if sect == "sources" and m:
            cur["sources"].append((m.group(1), m.group(2).strip().strip('"')))
            continue
        m = re.match(r'\s+(\w+): (.*)$', line)
        if not m:
            continue
        k, v = m.group(1), m.group(2).strip().strip('"')
        if sect == "changed":
            cur["changed"][k] = v
        elif sect == "verified":
            cur.setdefault("v_" + k, v)
        else:
            cur.setdefault(k, v)
    return meta, claims


def esc(t):
    return html.escape(t, quote=False)


def dot(status):
    return ('<span style="width:8px;height:8px;border-radius:50%%;background:%s;'
            'flex:0 0 8px;margin-top:5px"></span>' % DOT.get(status, "#64748b"))


def claim_html(c):
    bits = ['<div style="display:flex;gap:8px;align-items:flex-start;margin-bottom:9px">', dot(c.get("status"))]
    body = ['<span style="font-size:12px;line-height:1.45;color:#b8c4d6;text-wrap:pretty">',
            esc(c.get("title", ""))]
    if c.get("changed"):
        ch = c["changed"]
        if ch.get("also_moved"):
            # Moved twice. Say so — a second move rendered as a first one hides the fact that a
            # status is unsettled, which is exactly what a reader needs to see.
            label = "moved twice: %s, then 2026-08-10 back to %s" % (
                esc(ch["also_moved"]), esc(c.get("status", "?")))
        else:
            label = "changed 2026-08-09, was %s" % esc(ch.get("from", "?"))
        body.append('<span style="display:inline-block;margin-left:6px;padding:1px 5px;border-radius:3px;'
                    'background:#3a2a12;color:#fbbf24;font-size:10px;white-space:nowrap">%s</span>' % label)
    if c.get("v_verdict") in ("needs-hardware", "contested"):
        col = "#a78bfa" if c["v_verdict"] == "contested" else "#7dd3fc"
        body.append('<span style="display:inline-block;margin-left:6px;padding:1px 5px;border-radius:3px;'
                    'background:#1b2740;color:%s;font-size:10px;white-space:nowrap">%s</span>'
                    % (col, esc(c["v_verdict"])))
    if c.get("note"):
        body.append('<span style="display:block;color:#7c8aa3;font-size:11px;margin-top:3px">%s</span>'
                    % esc(c["note"]))
    decay = c.get("decay") or (c.get("changed") or {}).get("decay")
    if decay:
        body.append('<span style="display:block;color:#c08a3e;font-size:11px;margin-top:2px">'
                    'proof decay: %s</span>' % esc(decay))
    if c.get("changed", {}) and c["changed"].get("reason"):
        body.append('<span style="display:block;color:#9a7b3a;font-size:11px;margin-top:2px">why it moved: %s</span>'
                    % esc(c["changed"]["reason"]))
    srcs = " · ".join("%s %s" % (k, esc(v if len(v) < 62 else v[:59] + "…")) for k, v in c["sources"])
    body.append('<span style="display:block;color:#4b5b76;font-size:10px;margin-top:3px;'
                'font-family:ui-monospace,SFMono-Regular,Menlo,monospace">%s</span>' % srcs)
    body.append("</span>")
    bits.append("".join(body))
    bits.append("</div>")
    return "".join(bits)


def panel(title, inner, sub=None):
    h = ['<div style="background:#121b2c;border:1px solid #1f2c44;border-radius:6px;padding:14px 13px">']
    h.append('<div style="font-size:14px;font-weight:500;padding-bottom:9px;margin-bottom:10px;'
             'border-bottom:1px solid #1f2c44">%s</div>' % title)
    if sub:
        h.append('<div style="color:#6b7a91;font-size:11px;margin:-6px 0 10px">%s</div>' % esc(sub))
    h.append(inner)
    h.append("</div>")
    return "".join(h)


def main():
    meta, claims = load(SRC)
    counts = {}
    for c in claims:
        counts[c.get("status")] = counts.get(c.get("status"), 0) + 1
    changed = [c for c in claims if c.get("changed")]

    o = []
    o.append("<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\">")
    o.append('<meta name="viewport" content="width=device-width, initial-scale=1">')
    o.append("<title>Where Felhom stands</title><style>")
    o.append("@page{size:A3 landscape;margin:8mm}"
             "*{margin:0;padding:0;box-sizing:border-box}"
             "body{background:#0b1220;color:#e6edf7;font-family:-apple-system,BlinkMacSystemFont,"
             "'Segoe UI',Roboto,sans-serif;-webkit-font-smoothing:antialiased;padding:26px 20px}"
             ".wrap{max-width:1600px;margin:0 auto}"
             ".grid7{display:grid;grid-template-columns:repeat(7,1fr);gap:12px;align-items:start}"
             ".grid4{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;align-items:start}"
             ".grid3{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;align-items:start}"
             "h1{font-size:22px;font-weight:600;letter-spacing:-.2px}"
             "a{color:#60a5fa}"
             "@media print{body{padding:0}.grid7{grid-template-columns:repeat(7,1fr)}}")
    o.append("</style></head><body><div class=\"wrap\">")

    o.append("<h1>Where Felhom stands</h1>")
    o.append('<div style="color:#8fa0b8;font-size:12.5px;margin:7px 0 4px;max-width:1100px;line-height:1.5">'
             'What we built, what happens when things go wrong, and what is still missing. '
             '<b style="color:#cbd7e8">Generated from '
             '<code>where-felhom-stands.yaml</code></b>, which cites the capability map, register row or '
             'evidence document behind every claim — and which is checked by '
             '<code>scripts/check_stands.py</code>.</div>')
    o.append('<div style="color:#6b7a91;font-size:11.5px;margin-bottom:16px">Verified %s against '
             'felhom-agent <code>28ba8593b8</code>, felhom-controller <code>c732fe1283</code>, hub '
             '<code>56f8aa611c</code>. <b style="color:#fbbf24">%d status(es) moved in that pass</b> — '
             'each is marked on the page.</div>' % (meta.get("date", "?"), len(changed)))

    leg = ['<div style="display:flex;gap:22px;flex-wrap:wrap;background:#121b2c;border:1px solid #1f2c44;'
           'border-radius:6px;padding:11px 14px;margin-bottom:18px">']
    for k, name, desc in LEGEND:
        leg.append('<div style="display:flex;gap:8px;align-items:flex-start">%s'
                   '<span style="font-size:11.5px"><b>%s</b> <span style="color:#7c8aa3">(%d)</span>'
                   '<span style="display:block;color:#6b7a91;font-size:10.5px">%s</span></span></div>'
                   % (dot(k), name, counts.get(k, 0), esc(desc)))
    leg.append('<div style="display:flex;gap:8px;align-items:flex-start"><span style="font-size:11.5px">'
               '<b style="color:#7dd3fc">needs-hardware</b><span style="display:block;color:#6b7a91;'
               'font-size:10.5px">only a running box could settle it</span></span></div>')
    leg.append("</div>")
    o.append("".join(leg))

    o.append('<div style="font-size:15px;font-weight:600;margin:0 0 9px">The journey</div>')
    o.append('<div style="color:#6b7a91;font-size:11.5px;margin-bottom:10px">Seven stages, left to '
             'right, as they happen to a person.</div>')
    o.append('<div class="grid7">')
    for n in range(1, 8):
        inner = "".join(claim_html(c) for c in claims
                        if c.get("band") == "journey" and c.get("stage") == str(n))
        o.append(panel('<span style="color:#4b5b76;font-family:ui-monospace,monospace">%d</span> &nbsp;%s'
                       % (n, esc(STAGES[n])), inner))
    o.append("</div>")

    fails = [c for c in claims if c.get("band") == "failures"]
    o.append('<div style="font-size:15px;font-weight:600;margin:22px 0 9px">When things go wrong</div>')
    o.append('<div style="color:#6b7a91;font-size:11.5px;margin-bottom:10px">%d situations, and what '
             'actually happens in each.</div>' % len(fails))
    o.append('<div class="grid3">')
    for c in fails:
        o.append(panel(esc(c.get("title", "").split(" — ")[0])[:74], claim_html(c)))
    o.append("</div>")

    o.append('<div style="margin-top:26px;padding-top:12px;border-top:1px solid #1f2c44;color:#4b5b76;'
             'font-size:10.5px;line-height:1.6">'
             'Generated by <code>scripts/render_stands.py</code> from <code>where-felhom-stands.yaml</code>. '
             'Do not hand-edit this file — edit the data and regenerate. '
             'The 2026-08-09 React bundle is kept as '
             '<code>where-felhom-stands-2026-08-09-snapshot.html</code> and is not maintained.'
             '</div>')
    o.append("</div></body></html>")

    open(OUT, "w", encoding="utf-8").write("\n".join(o))
    print("wrote %s (%d bytes) — %d claims, %d changed"
          % (os.path.relpath(OUT, ROOT), os.path.getsize(OUT), len(claims), len(changed)))
    print("  statuses: " + ", ".join("%s=%d" % kv for kv in sorted(counts.items())))
    return 0


if __name__ == "__main__":
    sys.exit(main())
