#!/usr/bin/env python3
"""
Extract a keyword-based context map from sources/context/string_theory.pdf.

Outputs:
- sources/context/string_theory_index.json
- docs/StringTheory_Index.md

This is a context bridge only. RU remains the engine.
"""

from __future__ import annotations
import os, json, argparse
from typing import List, Dict, Any
from PyPDF2 import PdfReader

KEY_GROUPS = {
  "Worldsheet and conformal field theory": ["worldsheet","conformal field theory","conformal","cft","virasoro","primary","chiral","representation"],
  "Operator product expansion": ["ope","operator product","vertex operator"],
  "Modular and torus": ["modular invariance","modular","torus","cylinder","partition function"],
  "Boundaries, D-branes, open strings": ["boundary","d-brane","open string","closed string","chan-paton","brst"],
  "Hilbert-space language": ["hilbert"],
}

def md_table(rows: List[List[str]], headers: List[str]) -> str:
    out=[]
    out.append("| " + " | ".join(headers) + " |")
    out.append("| " + " | ".join(["---"]*len(headers)) + " |")
    for r in rows:
        out.append("| " + " | ".join(r) + " |")
    return "\n".join(out)

def main()->int:
    ap=argparse.ArgumentParser()
    ap.add_argument("--root", default=".", help="Project root")
    args=ap.parse_args()
    root=os.path.abspath(args.root)

    pdf_path=os.path.join(root,"sources","context","string_theory.pdf")
    if not os.path.exists(pdf_path):
        print("Missing:", os.path.relpath(pdf_path, root))
        return 2

    reader=PdfReader(pdf_path)
    texts=[]
    for i in range(len(reader.pages)):
        try:
            t=reader.pages[i].extract_text() or ""
        except Exception:
            t=""
        texts.append(t)

    def pages_for(term: str) -> List[int]:
        tl=term.lower()
        pages=[]
        for i,t in enumerate(texts):
            if tl in t.lower():
                pages.append(i+1)
        return pages

    groups={}
    for group, terms in KEY_GROUPS.items():
        union=set()
        term_entries=[]
        for term in terms:
            pages=pages_for(term)
            term_entries.append({
              "term": term,
              "count_pages": len(pages),
              "first_pages": pages[:10],
              "pages": pages,
            })
            union |= set(pages)
        groups[group]={
          "terms": term_entries,
          "union_page_count": len(union),
          "union_first_pages": sorted(list(union))[:20],
        }

    out_json={
      "source_pdf":"sources/context/string_theory.pdf",
      "page_count": len(reader.pages),
      "generated_by":"tools/extract_string_theory_index.py",
      "groups": groups
    }

    json_out=os.path.join(root,"sources","context","string_theory_index.json")
    os.makedirs(os.path.dirname(json_out), exist_ok=True)
    with open(json_out,"w",encoding="utf-8") as f:
        json.dump(out_json,f,indent=2)

    md=[]
    md.append("# String Theory Context Map")
    md.append("")
    md.append("## Package center")
    md.append("")
    md.append("- RU is the universality engine.")
    md.append("- This PDF is used as a host-language context only. The engine depends on finite inputs (graph, nonnegative templates, costs).")
    md.append("")
    md.append("## Source")
    md.append("")
    md.append("- `sources/context/string_theory.pdf`")
    md.append(f"- Page count: {len(reader.pages)}")
    md.append("")
    md.append("## Where host-structure language appears in the PDF")
    md.append("")
    for group, data in groups.items():
        md.append(f"### {group}")
        md.append("")
        rows=[]
        for ent in data["terms"]:
            first_pages=", ".join(str(p) for p in ent["first_pages"])
            rows.append([ent["term"], str(ent["count_pages"]), first_pages])
        md.append(md_table(rows, ["Term","Pages with term","First pages"]))
        md.append("")
        union_pages=", ".join(str(p) for p in data["union_first_pages"])
        md.append(f"- Union pages for this group (first): {union_pages}")
        md.append("")
    md.append("## How this maps into RU inputs")
    md.append("")
    md.append("- Finite sector selection")
    md.append("  - Choose a finite label set of states or sectors appropriate to a regime.")
    md.append("- Nonnegative templates")
    md.append("  - In rational or topological subsectors, fusion or channel-composition coefficients form nonnegative matrices.")
    md.append("  - In open-string settings, Chan-Paton multiplicities provide another canonical source of nonnegative incidence data.")
    md.append("- Constraints as a graph")
    md.append("  - Boundary conditions, allowed channel schedules, and defect insertions can be encoded as a finite directed graph.")
    md.append("- Optional cost model")
    md.append("  - If an extremal regime is being certified, assign costs to edges and let canonical normalization define the tight core.")
    md.append("")
    md.append("## Outputs")
    md.append("")
    md.append("- `sources/context/string_theory_index.json`")
    md.append("- `docs/StringTheory_Index.md`")
    md.append("")

    md_out=os.path.join(root,"docs","StringTheory_Index.md")
    os.makedirs(os.path.dirname(md_out), exist_ok=True)
    with open(md_out,"w",encoding="utf-8") as f:
        f.write("\n".join(md) + "\n")

    print("Wrote:")
    print(" ", os.path.relpath(json_out, root))
    print(" ", os.path.relpath(md_out, root))
    return 0

if __name__=="__main__":
    raise SystemExit(main())
