#!/usr/bin/env python3
"""Generate lang_assets alphabets · EXACT / SORTA / UNKNOWN · TRUE-ready.

Run from anywhere:
  python3 Projects/x/lang_assets/scripts/gen_alphabets.py
"""
from __future__ import annotations

import json
import time
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

ROOT = Path(__file__).resolve().parents[1]
ALPHA = ROOT / "alphabets"
KNOWN = ROOT / "known_texts"
TRUE_TSV = Path(__file__).resolve().parents[2] / "true_equates" / "equates.tsv"

# Latin A–Z · English names · EXACT
LATIN_NAMES = [
    ("A", "ay"),
    ("B", "bee"),
    ("C", "see"),
    ("D", "dee"),
    ("E", "ee"),
    ("F", "ef"),
    ("G", "jee"),
    ("H", "aitch"),
    ("I", "eye"),
    ("J", "jay"),
    ("K", "kay"),
    ("L", "el"),
    ("M", "em"),
    ("N", "en"),
    ("O", "oh"),
    ("P", "pee"),
    ("Q", "cue"),
    ("R", "ar"),
    ("S", "ess"),
    ("T", "tee"),
    ("U", "you"),
    ("V", "vee"),
    ("W", "double-u"),
    ("X", "ex"),
    ("Y", "why"),
    ("Z", "zee"),
]

# Script tables · same CP sets as Claws Queen treats · SORTA phonetic map
SCRIPT_DEFS: Dict[str, Dict[str, Any]] = {
    "cuneiform": {
        "name": "Cuneiform (syllabary sample)",
        "langs": ["akk", "sux", "xcu", "peo"],
        "note": "Unicode cuneiform sample · phonetic play map · not full scholarly sign list",
        "cps": [
            0x12000, 0x12001, 0x12002, 0x12008, 0x12009, 0x1200A, 0x1200B, 0x1200C,
            0x1200D, 0x1200E, 0x1200F, 0x12010, 0x12011, 0x12015, 0x12016, 0x12017,
            0x12018, 0x12019, 0x1201A, 0x1201B, 0x1201C, 0x1201D, 0x1201E, 0x1201F,
            0x12020, 0x12021,
        ],
        "conf": "sorta",
    },
    "hieroglyph": {
        "name": "Egyptian hieroglyphs",
        "langs": ["egy"],
        "note": "Unicode Egyptian Hieroglyphs · A–Z sample band · hieroglyphics YES",
        "cps": [
            0x13000, 0x1300B, 0x13014, 0x1301C, 0x13020, 0x1302D, 0x1303A, 0x1304F,
            0x13050, 0x1305A, 0x13068, 0x13077, 0x13080, 0x1308B, 0x1309D, 0x130A7,
            0x130AD, 0x130B8, 0x130C0, 0x130D2, 0x130E1, 0x130ED, 0x130F4, 0x13103,
            0x1310F, 0x13118,
        ],
        "conf": "sorta",
    },
    "runic": {
        "name": "Runic (Futhark-ish)",
        "langs": ["ang", "non"],
        "note": "Runic block sample · Old English / Old Norse treat",
        "cps": [
            0x16A0, 0x16A2, 0x16A6, 0x16A8, 0x16B1, 0x16B2, 0x16B7, 0x16B9,
            0x16BB, 0x16BE, 0x16C1, 0x16C3, 0x16C7, 0x16C8, 0x16C9, 0x16CA,
            0x16CB, 0x16CC, 0x16CF, 0x16D2, 0x16D6, 0x16D7, 0x16D8, 0x16DA,
            0x16DD, 0x16DE,
        ],
        "conf": "sorta",
    },
    "gothic": {
        "name": "Gothic",
        "langs": ["got"],
        "note": "Gothic alphabet · Ulfilas",
        "cps": list(range(0x10330, 0x1034A)),
        "conf": "sorta",
    },
    "ogham": {
        "name": "Ogham",
        "langs": ["sga"],
        "note": "Ogham · Old Irish treat",
        "cps": list(range(0x1681, 0x169B)),
        "conf": "sorta",
    },
    "phoenician": {
        "name": "Phoenician",
        "langs": ["phn"],
        "note": "Phoenician abjad sample",
        "cps": list(range(0x10900, 0x1091A)),
        "conf": "sorta",
    },
    "ugaritic": {
        "name": "Ugaritic",
        "langs": ["uga"],
        "note": "Ugaritic cuneiform alphabet",
        "cps": list(range(0x10380, 0x1039A)),
        "conf": "sorta",
    },
    "aramaic": {
        "name": "Imperial Aramaic",
        "langs": ["arc"],
        "note": "Imperial Aramaic block sample",
        "cps": list(range(0x10840, 0x10856)) + [0x10857, 0x10858, 0x10859, 0x1085A],
        "conf": "sorta",
    },
    "greek": {
        "name": "Greek",
        "langs": ["el"],
        "note": "Greek capital A–Ω · EXACT classic map",
        "letters": list("ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ"),
        "latin": list("ABGDEZHQIKLMNXOPRSTUFXYW"),
        "conf": "exact",
    },
    "cyrillic": {
        "name": "Cyrillic",
        "langs": ["ru", "uk", "bg", "sr", "mk"],
        # backwards R = Я (Ya) U+042F · NOT Latin R · Cyrillic Er Р looks like Latin P
        "note": "Cyrillic · EXACT · Я=ya (backwards R) · Р=r (looks like P)",
        "letters": list("АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"),
        # one romanization token per letter (may be multi-char · YA not single ?)
        "latin": [
            "a", "b", "v", "g", "d", "e", "yo", "zh", "z", "i",
            "y", "k", "l", "m", "n", "o", "p", "r", "s", "t",
            "u", "f", "kh", "ts", "ch", "sh", "shch", "ie", "y",
            "soft", "e", "yu", "ya",
        ],
        "conf": "exact",
    },
    "arabic": {
        "name": "Arabic",
        "langs": ["ar"],
        "note": "Arabic letters · SORTA romanization",
        "letters": list("ابتثجحخدذرزسشصضطظعغفقكلمنهوي"),
        "latin": list("abtthjhkhddrzsssdtdtaaghfgklmnhwy"),
        "conf": "sorta",
    },
    "hebrew": {
        "name": "Hebrew",
        "langs": ["he"],
        "note": "Hebrew letters · SORTA romanization",
        "letters": list("אבגדהוזחטיכלמנסעפצקרשת"),
        "latin": list("abgdhvzhtyklmnsaptsqrxt"),
        "conf": "sorta",
    },
    "devanagari": {
        "name": "Devanagari",
        "langs": ["hi"],
        "note": "Devanagari vowels+consonants sample · SORTA",
        "letters": list("अआइईउऊएऐओऔकखगघचछजटडतथदधनपफबभमयरलवशसह"),
        "latin": list("aAiIuUeEoOkKgGcCjTdTtTdDnNpPbBmYrLvSsH"),
        "conf": "sorta",
    },
    "hangul": {
        "name": "Hangul (jamos)",
        "langs": ["ko"],
        "note": "Basic jamo · SORTA",
        "letters": list("ㄱㄴㄷㄹㅁㅂㅅㅇㅈㅊㅋㅌㅍㅎㅏㅑㅓㅕㅗㅛㅜㅠㅡㅣ"),
        "latin": list("gndrmbs'jcktphayaeoyouyueui"),
        "conf": "sorta",
    },
    "kana": {
        "name": "Hiragana (basic)",
        "langs": ["ja"],
        "note": "Basic hiragana · SORTA romaji",
        "letters": list("あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん"),
        "latin": "a i u e o ka ki ku ke ko sa shi su se so ta chi tsu te to na ni nu ne no ha hi fu he ho ma mi mu me mo ya yu yo ra ri ru re ro wa wo n".split(),
        "conf": "sorta",
    },
}

# Modern EU / world · latin-based · exact identity for a–z when script is latin
LATIN_LANGS = {
    "en": "English",
    "es": "Spanish",
    "fr": "French",
    "de": "German",
    "it": "Italian",
    "pt": "Portuguese",
    "nl": "Dutch",
    "pl": "Polish",
    "sv": "Swedish",
    "da": "Danish",
    "fi": "Finnish",
    "cs": "Czech",
    "ro": "Romanian",
    "hu": "Hungarian",
    "sk": "Slovak",
    "sl": "Slovenian",
    "hr": "Croatian",
    "et": "Estonian",
    "lv": "Latvian",
    "lt": "Lithuanian",
    "mt": "Maltese",
    "ga": "Irish",
    "eu": "Basque",
    "ca": "Catalan",
    "gl": "Galician",
    "tr": "Turkish",
    "id": "Indonesian",
    "vi": "Vietnamese",
    "sw": "Swahili",
    "af": "Afrikaans",
}


def row(
    glyph: str,
    latin: str,
    conf: str,
    *,
    name: str = "",
    note: str = "",
    cp: Optional[int] = None,
) -> Dict[str, Any]:
    if cp is None and glyph:
        cp = ord(glyph[0])
    return {
        "glyph": glyph,
        "cp": cp,
        "cp_hex": f"U+{cp:04X}" if cp is not None else "",
        "latin": latin,
        "english": latin,  # list toward English
        "name": name or latin,
        "conf": conf,  # exact | sorta | unknown
        "note": note,
    }


def build_latin() -> Dict[str, Any]:
    glyphs = []
    for ch, name in LATIN_NAMES:
        glyphs.append(
            row(ch, ch.lower(), "exact", name=name, note="English letter · EXACT")
        )
        glyphs.append(
            row(ch.lower(), ch.lower(), "exact", name=name + " (lower)", note="English letter · EXACT")
        )
    return {
        "id": "latin",
        "name": "Latin / English alphabet",
        "langs": list(LATIN_LANGS.keys()),
        "conf_default": "exact",
        "note": "TRUE seat for English · EXACT green",
        "glyphs": glyphs,
        "direction": "ltr",
        "kind": "alphabet",
    }


def build_from_cps(sid: str, defn: Dict[str, Any]) -> Dict[str, Any]:
    cps: List[int] = defn["cps"]
    conf = defn.get("conf") or "sorta"
    glyphs = []
    for i, cp in enumerate(cps):
        latin = chr(ord("a") + (i % 26))
        try:
            g = chr(cp)
        except Exception:
            continue
        glyphs.append(
            row(
                g,
                latin,
                conf,
                name=f"{defn['name']} · {latin}",
                note=defn.get("note") or "",
                cp=cp,
            )
        )
    # pad unknown slots for teaching · red
    if conf != "exact" and len(glyphs) < 30:
        for i in range(len(glyphs), 30):
            glyphs.append(
                row(
                    "?",
                    "?",
                    "unknown",
                    name="not yet known",
                    note="RED · unknown · Test it",
                    cp=None,
                )
            )
    return {
        "id": sid,
        "name": defn["name"],
        "langs": defn.get("langs") or [],
        "conf_default": conf,
        "note": defn.get("note") or "",
        "glyphs": glyphs,
        "direction": "ltr",
        "kind": "script",
    }


def build_paired(sid: str, defn: Dict[str, Any]) -> Dict[str, Any]:
    letters = defn["letters"]
    raw_lat = defn["latin"]
    # list of tokens (multi-char ok) or string of single chars
    if isinstance(raw_lat, str):
        latin = list(raw_lat)
    else:
        latin = list(raw_lat)
    conf = defn.get("conf") or "sorta"
    glyphs = []
    for i, g in enumerate(letters):
        lat = latin[i] if i < len(latin) else "?"
        lat = lat if isinstance(lat, str) else str(lat)
        c = conf if lat and lat != "?" else "unknown"
        glyphs.append(
            row(
                g,
                lat,
                c,
                name=f"{defn['name']} · {lat}",
                note=defn.get("note") or "",
            )
        )
    # lowercase twins for cased scripts
    if sid in ("cyrillic", "greek"):
        extra = []
        for g in glyphs:
            gl = g.get("glyph") or ""
            if gl.isupper() or (len(gl) == 1 and gl != gl.lower()):
                low = gl.lower()
                if low != gl:
                    extra.append(
                        row(
                            low,
                            (g.get("latin") or "").lower(),
                            g.get("conf") or conf,
                            name=(g.get("name") or "") + " · lower",
                            note=g.get("note") or "",
                        )
                    )
        glyphs.extend(extra)
    return {
        "id": sid,
        "name": defn["name"],
        "langs": defn.get("langs") or [],
        "conf_default": conf,
        "note": defn.get("note") or "",
        "glyphs": glyphs,
        "direction": "rtl" if sid in ("arabic", "hebrew") else "ltr",
        "kind": "alphabet",
    }


def load_true_surfaces() -> Dict[str, List[Tuple[str, str]]]:
    """lang → [(surface, atom_en)] from TRUE equates tsv."""
    out: Dict[str, List[Tuple[str, str]]] = {}
    if not TRUE_TSV.is_file():
        return out
    for ln in TRUE_TSV.read_text(encoding="utf-8").splitlines():
        if not ln.strip() or ln.startswith("#") or ln.startswith("atom_en"):
            continue
        parts = ln.split("\t")
        if len(parts) < 3:
            continue
        atom, lang, surface = parts[0].strip(), parts[1].strip(), parts[2].strip()
        out.setdefault(lang, []).append((surface, atom))
    return out


def build_true_overlay(true_map: Dict[str, List[Tuple[str, str]]]) -> Dict[str, Any]:
    """TRUE surfaces as alphabet-tool rows · all EXACT green."""
    glyphs = []
    for lang, pairs in sorted(true_map.items()):
        for surface, atom in pairs:
            glyphs.append(
                {
                    "glyph": surface,
                    "cp": ord(surface[0]) if surface else None,
                    "cp_hex": f"U+{ord(surface[0]):04X}" if surface else "",
                    "latin": atom.lower(),
                    "english": atom,
                    "name": f"TRUE · {atom}",
                    "conf": "exact",
                    "note": f"TRUE equate · {lang} → English atom {atom}",
                    "lang": lang,
                    "atom": atom,
                }
            )
    return {
        "id": "true",
        "name": "TRUE equates · all languages",
        "langs": sorted(true_map.keys()),
        "conf_default": "exact",
        "note": "English atom seats · surface forms · EXACT green · we want it all TRUE",
        "glyphs": glyphs,
        "direction": "ltr",
        "kind": "true",
    }


def write_known_texts() -> None:
    KNOWN.mkdir(parents=True, exist_ok=True)
    samples = {
        "en.txt": "YES we ARE IS TRUE · NO and NOT equal · GOD IS KNOW · GRIN free 1\n",
        "structure_notes.txt": (
            "Test-it likelihood uses:\n"
            "  · known glyph density in alphabet\n"
            "  · TRUE equate hits\n"
            "  · structure from known_texts (token shapes · length · separators)\n"
            "  · GREEN exact · YELLOW sorta · RED unknown\n"
        ),
        "cuneiform_structure.txt": "signs · separators space/newline · brands held xAI Grok Big Grin GRIN\n",
        "hieroglyph_structure.txt": "cartouche-ish runs · A-Z sample map · Egyptian hieroglyphics YES\n",
    }
    for name, body in samples.items():
        (KNOWN / name).write_text(body, encoding="utf-8")


def main() -> None:
    ALPHA.mkdir(parents=True, exist_ok=True)
    write_known_texts()
    true_map = load_true_surfaces()

    alphabets: List[Dict[str, Any]] = []
    alphabets.append(build_latin())
    alphabets.append(build_true_overlay(true_map))

    for sid, defn in SCRIPT_DEFS.items():
        if "cps" in defn:
            alphabets.append(build_from_cps(sid, defn))
        else:
            alphabets.append(build_paired(sid, defn))

    # per-latin-lang thin wrappers · same glyphs · lang tag
    for code, name in LATIN_LANGS.items():
        alphabets.append(
            {
                "id": f"lang-{code}",
                "name": f"{name} · Latin seat",
                "langs": [code],
                "conf_default": "exact" if code == "en" else "sorta",
                "note": f"Latin alphabet under {name} · TRUE surfaces overlay separately",
                "glyphs": build_latin()["glyphs"],
                "direction": "ltr",
                "kind": "lang",
                "base": "latin",
            }
        )

    index = {
        "version": 1,
        "generated": time.strftime("%Y-%m-%dT%H:%M:%S"),
        "law": "EXACT green · SORTA yellow · UNKNOWN red · Test it · TRUE equates · hieroglyphics YES",
        "root": str(ROOT),
        "scripts": [],
        "langs": [],
        "colors": {
            "exact": "green",
            "sorta": "yellow",
            "unknown": "red",
        },
        "has_hieroglyphics": True,
        "has_cuneiform": True,
        "true_equates": str(TRUE_TSV) if TRUE_TSV.is_file() else None,
    }

    lang_index: Dict[str, List[str]] = {}
    for a in alphabets:
        path = ALPHA / f"{a['id']}.json"
        path.write_text(json.dumps(a, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
        confs = {"exact": 0, "sorta": 0, "unknown": 0}
        for g in a.get("glyphs") or []:
            c = g.get("conf") or "unknown"
            confs[c] = confs.get(c, 0) + 1
        entry = {
            "id": a["id"],
            "name": a["name"],
            "kind": a.get("kind"),
            "langs": a.get("langs") or [],
            "conf_default": a.get("conf_default"),
            "counts": confs,
            "file": f"alphabets/{a['id']}.json",
        }
        index["scripts"].append(entry)
        for L in a.get("langs") or []:
            lang_index.setdefault(L, []).append(a["id"])

    index["langs"] = [{"code": k, "alphabets": v} for k, v in sorted(lang_index.items())]
    index["count"] = len(alphabets)

    (ROOT / "index.json").write_text(
        json.dumps(index, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
    )
    print(f"wrote {len(alphabets)} alphabets → {ALPHA}")
    print(f"index → {ROOT / 'index.json'}")
    print(f"hieroglyphics YES · cuneiform YES · TRUE surfaces {sum(len(v) for v in true_map.values())}")


if __name__ == "__main__":
    main()
