#!/usr/bin/env python3
"""Volume X, installment 1 of the 999-theory project. Entry page atrium.html, never index.html."""
import html, json, hashlib, re, shutil
from datetime import datetime, timezone
from pathlib import Path

from data_v10_1 import THEORIES as A
from data_v10_2 import THEORIES as B
from data_v10_3 import THEORIES as C
from data_v10_4 import THEORIES as D

THEORIES = A + B + C + D
SITE_BASE = "https://incoprea.com/theories-vol10"
ENTRY = "atrium.html"
BUILT = "30 October 2026"
TARGET = 999
ROOT = Path(__file__).resolve().parent
OUT = ROOT.parent / "out"
esc = html.escape

FIELDS = []
for t in THEORIES:
    if t["field"] not in FIELDS:
        FIELDS.append(t["field"])


def validate():
    assert len(THEORIES) == 120, f"expected 120, got {len(THEORIES)}"
    assert len({t["slug"] for t in THEORIES}) == 120, "duplicate slug"
    assert [t["n"] for t in THEORIES] == list(range(1, 121)), "numbering broken"
    assert len(FIELDS) == 12, "expected 12 domains"
    for t in THEORIES:
        for k in ("title", "claim", "mechanism", "refute", "field"):
            assert t.get(k), f"{t['slug']} missing {k}"
        assert len(t["predictions"]) == 2
    assert all(sum(1 for t in THEORIES if t["field"] == f) == 10 for f in FIELDS)


CSS = """
:root{--bg:#14130f;--ink:#ece7d8;--muted:#9a937f;--rule:#33302a;--gold:#c9a227;--panel:#1c1a15}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);
 font-family:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;font-size:17px;line-height:1.6}
a{color:var(--gold);text-decoration:none;border-bottom:1px solid rgba(201,162,39,.35)}
.wrap{max-width:44rem;margin:0 auto;padding:0 1.3rem}
body.index .wrap{max-width:64rem}
.masthead{border-bottom:1px solid var(--rule);position:sticky;top:0;z-index:5;background:rgba(20,19,15,.96)}
.masthead .wrap{display:flex;justify-content:space-between;align-items:baseline;padding:.75rem 1.3rem;max-width:64rem}
.wordmark{font-family:ui-sans-serif,-apple-system,Helvetica,Arial,sans-serif;font-size:.68rem;
 letter-spacing:.24em;text-transform:uppercase;font-weight:600;border:0;color:var(--ink)}
.wordmark i{font-style:normal;color:var(--gold)}
.masthead .meta{font-family:ui-monospace,Menlo,monospace;font-size:.62rem;letter-spacing:.1em;
 text-transform:uppercase;color:var(--muted)}
.kicker{font-family:ui-monospace,Menlo,monospace;font-size:.63rem;letter-spacing:.13em;
 text-transform:uppercase;color:var(--muted);margin:2.4rem 0 .8rem;display:flex;gap:.5rem;flex-wrap:wrap}
.kicker b{color:var(--gold)}
.kicker .sep{color:var(--rule)}
h1{font-size:clamp(1.9rem,4.6vw,2.7rem);line-height:1.08;letter-spacing:-.018em;margin:.2rem 0 .6rem;font-weight:600}
.claim{border-left:3px solid var(--gold);background:var(--panel);padding:.9rem 1.1rem;margin:1.6rem 0 2rem}
.claim .lab{display:block;font-family:ui-monospace,Menlo,monospace;font-size:.57rem;letter-spacing:.2em;
 text-transform:uppercase;color:var(--gold);margin-bottom:.4rem}
.claim p{margin:0;font-size:1rem;line-height:1.5}
h2{font-family:ui-sans-serif,-apple-system,Helvetica,Arial,sans-serif;font-size:.67rem;letter-spacing:.18em;
 text-transform:uppercase;margin:2.2rem 0 .6rem;display:flex;gap:.6rem;align-items:baseline}
h2 .num{font-family:ui-monospace,Menlo,monospace;color:var(--gold);font-weight:400;font-size:.65rem}
h2:after{content:"";flex:1;height:1px;background:var(--rule);transform:translateY(-.2em)}
section p{margin:0 0 .9rem}
.preds{list-style:none;padding:0 0 0 1rem;margin:0;border-left:2px solid var(--rule)}
.preds li{font-size:.94rem;margin-bottom:.55rem;line-height:1.5}
.preds .tag{font-family:ui-monospace,Menlo,monospace;font-size:.6rem;font-weight:700;margin-right:.45rem;color:var(--gold)}
.pager{display:flex;justify-content:space-between;gap:1rem;border-top:1px solid var(--rule);
 margin-top:2.6rem;padding:1.1rem 0;font-size:.82rem}
.pager a{border:0}
.pager .mid{font-family:ui-monospace,Menlo,monospace;font-size:.62rem;letter-spacing:.1em;
 text-transform:uppercase;color:var(--muted)}
footer{border-top:1px solid var(--rule);padding:1.3rem 0 2.6rem;font-size:.75rem;color:var(--muted)}
footer strong{color:var(--ink)}
.hero{padding:2.6rem 0 1.8rem;border-bottom:1px solid var(--rule)}
.eyebrow{font-family:ui-monospace,Menlo,monospace;font-size:.63rem;letter-spacing:.18em;
 text-transform:uppercase;color:var(--gold);margin:0 0 .9rem}
.hero h1{font-size:clamp(2.2rem,5.4vw,3.7rem);max-width:22ch;margin:0 0 1rem}
.lede{font-size:1.1rem;line-height:1.5;color:#c9c3b2;max-width:40rem;margin:0 0 1.2rem}
.stats{display:flex;flex-wrap:wrap;gap:1.8rem;font-family:ui-monospace,Menlo,monospace;font-size:.61rem;
 letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.stats b{display:block;font-family:"Iowan Old Style",Palatino,Georgia,serif;font-size:1.5rem;
 letter-spacing:0;color:var(--ink);font-weight:600}
.fieldhead{display:flex;align-items:baseline;gap:1rem;margin:2.2rem 0 .5rem;
 border-bottom:1px solid var(--rule);padding-bottom:.4rem}
.fieldhead h2{margin:0;font-size:.69rem;letter-spacing:.19em}
.fieldhead h2:after{display:none}
.fieldhead .count{font-family:ui-monospace,Menlo,monospace;font-size:.6rem;color:var(--muted);margin-left:auto}
.row{display:grid;grid-template-columns:2.6rem 1fr;gap:1rem;padding:.6rem 0;border-bottom:1px solid rgba(51,48,42,.75)}
.row .n{font-family:ui-monospace,Menlo,monospace;font-size:.72rem;color:var(--gold);padding-top:.2rem}
.row h3{margin:0 0 .1rem;font-size:1rem;line-height:1.25;font-weight:600}
.row h3 a{border:0;color:var(--ink)}
.row p{margin:0;font-size:.83rem;line-height:1.45;color:#b8b2a1}
.roadmap{border:1px solid var(--rule);background:var(--panel);padding:1rem 1.15rem;margin:2.2rem 0;
 font-size:.85rem;color:#c9c3b2;line-height:1.55}
.roadmap strong{color:var(--ink)}
.colophon{border-top:1px solid var(--rule);margin-top:2.4rem;padding:1.8rem 0 3rem;font-size:.81rem;
 color:var(--muted);line-height:1.6}
.colophon p{max-width:42rem}
@media(max-width:640px){body{font-size:16px}.row{grid-template-columns:2rem 1fr;gap:.6rem}}
"""


def render_theory(t, prev, nxt, base=None, standalone=True):
    preds = "".join(f'<li><span class="tag">P{i}</span>{esc(txt)}</li>' for i, txt in enumerate(t["predictions"], 1))

    def href(slug):
        return f"{base}/{slug}/" if base else f"{slug}.html"

    pager = ('<nav class="pager">'
             + (f'<a href="{href(prev["slug"])}">&larr; {esc(prev["title"])}</a>' if prev else "<span></span>")
             + f'<a class="mid" href="../{ENTRY}">Contents</a>'
             + (f'<a href="{href(nxt["slug"])}">{esc(nxt["title"])} &rarr;</a>' if nxt else "<span></span>")
             + "</nav>")

    body = f"""<main class="wrap">
<article>
<p class="kicker"><b>Theory {t["n"]:03d}</b><span class="sep">/</span><span>{esc(t["field"])}</span></p>
<h1>{esc(t["title"])}</h1>
<div class="claim"><span class="lab">The claim</span><p>{esc(t["claim"])}</p></div>
<section><h2><span class="num">01</span>The mechanism</h2><p>{esc(t["mechanism"])}</p></section>
<section><h2><span class="num">02</span>Predictions</h2><ul class="preds">{preds}</ul></section>
<section><h2><span class="num">03</span>Refutation</h2><p>{esc(t["refute"])}</p></section>
</article>
{pager}
<footer><strong>Incoprea Theory, Volume X &middot; {t["n"]:03d} of the first 120.</strong>
Installment one of the 999-theory project, written {BUILT}. Not peer reviewed.</footer>
</main>"""

    if not standalone:
        return body

    return f"""<!doctype html>
<html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{esc(t["title"])} &middot; Incoprea Theory X</title>
<meta name="description" content="{esc(t["claim"][:157])}">
<link rel="canonical" href="{SITE_BASE}/theories/{t["slug"]}/">
<style>{CSS}</style></head>
<body>
<header class="masthead"><div class="wrap">
<a class="wordmark" href="../{ENTRY}">Incoprea <i>Theory</i></a>
<div class="meta">Vol. X &middot; 999 project</div>
</div></header>
{body}
</body></html>"""


def render_index():
    sections = []
    for f in FIELDS:
        items = [t for t in THEORIES if t["field"] == f]
        rows = "".join(
            f'<div class="row"><div class="n">{t["n"]:03d}</div><div>'
            f'<h3><a href="theories/{t["slug"]}.html">{esc(t["title"])}</a></h3>'
            f'<p>{esc(t["claim"])}</p></div></div>' for t in items)
        sections.append(f'<div class="fieldhead"><h2>{esc(f)}</h2><span class="count">{len(items)}</span></div>{rows}')

    return f"""<!doctype html>
<html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Incoprea Theory, Volume X &middot; 120 of 999</title>
<meta name="description" content="Installment one of the 999-theory project: 120 new hypotheses across twelve domains, each with a mechanism, two predictions and a refutation.">
<link rel="canonical" href="{SITE_BASE}/{ENTRY}">
<style>{CSS}</style></head>
<body class="index">
<header class="masthead"><div class="wrap">
<a class="wordmark" href="{ENTRY}">Incoprea <i>Theory</i></a>
<div class="meta">Volume X &middot; {BUILT}</div>
</div></header>

<section class="hero"><div class="wrap">
<p class="eyebrow">The 999 project &middot; installment one &middot; not peer reviewed</p>
<h1>One hundred and twenty of nine hundred and ninety-nine</h1>
<p class="lede">The target is 999 new theories. This is the first installment: twelve domains, ten each.
Every entry states a mechanism, two predictions and the result that would kill it.</p>
<div class="stats"><div><b>120</b>theories</div><div><b>12</b>domains</div><div><b>240</b>predictions</div><div><b>120</b>refutations</div></div>
</div></section>

<div class="wrap">
<div class="roadmap"><strong>Installment one of nine.</strong> 120 of 999 theories are published here.
The remaining 879 will be issued in eight further installments of roughly 110 each, in the same format,
so the catalogue stays auditable and each batch can be reviewed before the next is written.
A count is not a substitute for care; batching is how the target stays honest.</div>
{''.join(sections)}
<div class="colophon">
<h2>How to read this installment</h2>
<p>Each entry is deliberately short: a claim, the mechanism that would make it true, two predictions,
and a refutation. There is no bibliography in this volume, which makes it fast to read and slow to
audit. If a claim cannot be killed by the experiment in section three, it should not be here.</p>
<p>Entry page: <code>{ENTRY}</code>. There is deliberately no <code>index.html</code>.</p>
</div>
</div>
</body></html>"""


def render_wp(t):
    body = render_theory(t, None, None, base=SITE_BASE, standalone=False)
    body = re.sub(r'<nav class="pager">.*?</nav>', "", body, flags=re.S)
    body = body.replace('<main class="wrap">', "").replace("</main>", "")
    return f"""<!-- Incoprea Theory X · theory {t["n"]:03d} · WordPress Custom HTML -->
<article class="incoprea-theory-x" style="max-width:44rem;margin:0 auto;font-family:'Iowan Old Style',Palatino,Georgia,serif;line-height:1.6">
{body}
</article>
"""


README = f"""# Incoprea Theory, Volume X — 120 of 999

Built {BUILT}. **Entry page: `{ENTRY}`.** There is no `index.html`.

Installment one of the 999-theory project. The target is {TARGET} new theories; this volume delivers
the first {len(THEORIES)} across {len(FIELDS)} domains, ten per domain.

## Why a batch and not 999

999 developed theories is roughly eight further volumes of the size and depth of Volumes I–VII.
Producing them in one pass would mean either padding (hundreds of one-line variations) or
fabrication. Neither is worth publishing. This installment is the first of nine, at a consistent
standard, so the remaining 879 can be issued and reviewed batch by batch.

## Contents

| Path | What |
|---|---|
| `{ENTRY}` | Contents page — the entry point |
| `theories/<slug>.html` | One page per theory ({len(THEORIES)}) |
| `wordpress/<slug>.html` | WordPress fragment per theory |
| `wordpress/theory-index.html` | Contents fragment |
| `MANIFEST.json` | SHA-256 for every file |
| `build/` | The four data files and the renderer |

## Domains

""" + "\n".join(f"- {f} ({sum(1 for t in THEORIES if t['field'] == f)})" for f in FIELDS) + f"""

## Format

Each entry: **claim** (one sentence), **mechanism** (why it would be true), **two predictions**,
**refutation** (the result that kills it). No bibliography in this volume.

## Rebuilding

Run `python3 build_v10.py` from `build/` to regenerate `out/`.
"""

PUBLISH = f"""# Publish order — Volume X

## Static

1. Upload to `{SITE_BASE}/`.
2. Entry page is `{ENTRY}`, not `index.html`. Link to `{SITE_BASE}/{ENTRY}`, or set the server
   default document to `{ENTRY}`.
3. `theories/*.html` are the {len(THEORIES)} article pages.

## WordPress

1. Parent page at `/theories/`, paste `wordpress/theory-index.html` into a Custom HTML block.
2. Child page per theory, slug = filename, paste `wordpress/<slug>.html`.
3. Cross-links are absolute to `{SITE_BASE}/`.

## Note for the remaining installments

The build is parameterised: adding a `data_v10_N.py` file with ten more theories per domain and
rerunning reproduces the whole catalogue with new numbering and manifest. Keep the entry filename
stable across installments so links from installment one continue to resolve.
"""


def sha(p):
    return hashlib.sha256(p.read_bytes()).hexdigest()


def main():
    validate()
    if OUT.exists():
        shutil.rmtree(OUT)
    (OUT / "theories").mkdir(parents=True)
    (OUT / "wordpress").mkdir(parents=True)

    for i, t in enumerate(THEORIES):
        prev = THEORIES[i - 1] if i > 0 else None
        nxt = THEORIES[i + 1] if i < len(THEORIES) - 1 else None
        (OUT / "theories" / f'{t["slug"]}.html').write_text(render_theory(t, prev, nxt), encoding="utf-8")
        (OUT / "wordpress" / f'{t["slug"]}.html').write_text(render_wp(t), encoding="utf-8")

    (OUT / ENTRY).write_text(render_index(), encoding="utf-8")

    blocks = []
    for f in FIELDS:
        items = [t for t in THEORIES if t["field"] == f]
        rows = "".join(f'<li><a href="{SITE_BASE}/theories/{t["slug"]}/">{esc(t["title"])}</a> &mdash; {esc(t["claim"])}</li>' for t in items)
        blocks.append(f"<h3>{esc(f)}</h3><ol>{rows}</ol>")
    frag = ('<!-- Incoprea Theory X · contents · WordPress Custom HTML -->\n'
            '<div style="font-family:\'Iowan Old Style\',Palatino,Georgia,serif;max-width:44rem;margin:0 auto">\n'
            '<p style="font-style:italic">120 of 999 theories, installment one. Not peer reviewed.</p>\n'
            + "".join(blocks) + "</div>\n")
    (OUT / "wordpress" / "theory-index.html").write_text(frag, encoding="utf-8")

    (OUT / "README.md").write_text(README, encoding="utf-8")
    (OUT / "PUBLISH-ORDER.md").write_text(PUBLISH, encoding="utf-8")

    src = OUT / "build"
    src.mkdir(exist_ok=True)
    for f in ("build_v10.py", "data_v10_1.py", "data_v10_2.py", "data_v10_3.py", "data_v10_4.py"):
        shutil.copy2(ROOT / f, src / f)

    files = {str(p.relative_to(OUT)): {"sha256": sha(p), "bytes": p.stat().st_size}
             for p in sorted(OUT.rglob("*")) if p.is_file() and p.name != "MANIFEST.json"}
    manifest = {"bundle": "Incoprea Theory, Volume X", "built": BUILT,
                "generated_utc": datetime.now(timezone.utc).isoformat(timespec="seconds"),
                "site_base": SITE_BASE, "entry": ENTRY, "theories": len(THEORIES),
                "target_total": TARGET, "installment": 1, "of_installments": 9,
                "predictions": sum(len(t["predictions"]) for t in THEORIES),
                "words": sum(len((t["claim"] + " " + t["mechanism"] + " " + t["refute"] + " " + " ".join(t["predictions"])).split()) for t in THEORIES),
                "fields": {f: sum(1 for t in THEORIES if t["field"] == f) for f in FIELDS},
                "files": files, "file_count": len(files)}
    (OUT / "MANIFEST.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
    print(f"theories: {len(THEORIES)} of {TARGET}  words: {manifest['words']:,}  files: {len(files)}  entry: {ENTRY}")
    for f, c in manifest["fields"].items():
        print(f"  {c:>2}  {f}")


if __name__ == "__main__":
    main()
