#!/usr/bin/env python3
"""catalogue.py -- regenerate a by-type, by-date index of a static site.

Reads three meta tags per page (no content-string parsing):

    <meta name="krons:type" content="research">
    <meta name="krons:date" content="2026-07-04">
    <meta name="krons:pin"  content="1">

Unstamped pages fall back to the DIR map (below) + file mtime, so nothing
goes missing. Renders <web-root>/archive/index.html from the sibling
Jinja2 template catalogue.html.j2 -- restyle the index without touching
this Python.

Run:  uv run --python 3.14 --with jinja2 catalogue.py

Public domain. See UNLICENSE. Fork it, change the config block, done.
"""
from __future__ import annotations
import re, html, datetime
from pathlib import Path
from jinja2 import Environment, FileSystemLoader, select_autoescape

# ---- config: the only block you edit for your own site ------------------
BRAND  = "krons.fiu.wtf"
NS     = "krons"                     # meta namespace: <meta name="krons:type" ...>
TYPES  = ["Products", "Guides", "Research", "Art", "Lore"]
ICON   = {"Products": "\U0001F4E6", "Guides": "\U0001F4D8", "Research": "\U0001F52C",
          "Art": "\U0001F3A8", "Lore": "\U0001F30C", "Other": "\U0001F4C4"}
OUTPUT = "archive/index.html"        # written relative to the web root
EXCL   = {"assets", "skills", "sessions", "examples", "test_app",
          "auto", "node_modules", "archive", "tools"}
DIR    = {  # optional dir -> type fallback for pages with no meta
    "arizuko": "Products", "rsx": "Products", "qubo-ising": "Products", "products": "Products",
    "golang": "Guides", "guides": "Guides", "orchestration-hub": "Guides",
    "ultra-low-latency": "Guides", "sealed-bid-guide": "Guides", "qwen": "Guides",
    "relocation-guide": "Guides", "subtitles": "Guides",
    "agents": "Research", "biotech-swe-guide": "Research", "formal-verification": "Research",
    "research": "Research", "bio-soup": "Research", "arizuko-research": "Research", "virtue": "Research",
    "arts": "Art", "poem": "Art", "tarot": "Art", "game": "Art", "lore": "Lore",
    "mayai": None, "happy": None,    # other groups' trees -> skip
}
# -------------------------------------------------------------------------

WEB_ROOT = Path(__file__).resolve().parents[2]   # tools/catalogue/ -> web root
HERE = Path(__file__).resolve().parent


def read(p: Path) -> str:
    try:
        return p.read_text(encoding="utf-8", errors="ignore")[:6000]
    except Exception:
        return ""


def title_of(text: str, p: Path) -> str:
    for rx in (r"<title>(.*?)</title>", r"<h1[^>]*>(.*?)</h1>"):
        m = re.search(rx, text, re.I | re.S)
        if m:
            return re.sub(r"<[^>]+>", "", re.sub(r"\s+", " ", html.unescape(m.group(1)))).strip()
    return p.parent.name or p.name


def meta_of(text: str) -> dict:
    d = {}
    for k in ("type", "date", "pin"):
        m = re.search(rf'<meta\s+name=["\']{NS}:{k}["\']\s+content=["\']([^"\']*)["\']', text, re.I)
        if m:
            d[k] = m.group(1).strip()
    return d


def collect() -> dict:
    buckets = {t: [] for t in TYPES + ["Other"]}
    for p in WEB_ROOT.rglob("*.html"):
        rel = p.relative_to(WEB_ROOT).as_posix()
        if rel == "index.html":
            continue
        if rel.split("/")[0] in EXCL:
            continue
        text = read(p)
        m = meta_of(text)
        typ = m.get("type") or DIR.get(rel.split("/")[0], "Other")
        if typ is None:          # explicitly skipped tree
            continue
        typ = typ.capitalize()
        if typ not in buckets:
            typ = "Other"
        date = m.get("date") or datetime.datetime.fromtimestamp(
            p.stat().st_mtime, datetime.UTC).strftime("%Y-%m-%d")
        url = "../" + rel
        if url.endswith("/index.html"):
            url = url[:-10]
        buckets[typ].append({"date": date, "url": url, "title": title_of(text, p),
                             "path": rel, "pin": m.get("pin") == "1", "stamped": bool(m.get("type"))})
    for t in buckets:
        buckets[t].sort(key=lambda r: r["date"], reverse=True)
    return buckets


def main() -> None:
    buckets = collect()
    order = [t for t in TYPES + ["Other"] if buckets[t]]
    total = sum(len(v) for v in buckets.values())
    stamped = sum(1 for v in buckets.values() for r in v if r["stamped"])
    env = Environment(loader=FileSystemLoader(str(HERE)),
                      autoescape=select_autoescape(["html", "j2"]))
    out = env.get_template("catalogue.html.j2").render(
        brand=BRAND, ns=NS, icon=ICON, order=order, buckets=buckets,
        total=total, stamped=stamped)
    dest = WEB_ROOT / OUTPUT
    dest.parent.mkdir(parents=True, exist_ok=True)
    dest.write_text(out, encoding="utf-8")
    print(f"catalogue: {total} pages, {stamped} stamped -> {OUTPUT}")


if __name__ == "__main__":
    main()
