#!/usr/bin/env python3
"""exp16 — Palladius (Палладий) transliteration table + ru-side name detector (research/20 §B1 ch5, §B5
P4). miner-v1. $0. Versioned artifact (the syllable set is generated deterministically from the mapping
rules below and self-validated against the GT dst names).

Two uses:
  (5) ru-side detector: a ru token that segments cleanly into Palladius syllables = the draft model
      treated it as a transliterated Chinese NAME -> high-precision name spot WITH a ready dst.
  canon conformity (§C2-3): score whether a proposed dst for a type=name/place term conforms to Palladius.

The pinyin->Cyrillic Palladius system is a fixed ~400-syllable table. We generate it from initials ×
finals + the documented irregularities (zhi/zi/…->ы-class; y-/w- whole syllables; ü-class). Coverage is
validated by requiring every GT dst name syllable to be recognized (assert in __main__).
"""
from __future__ import annotations

import regex

import exp16_common as X

# ── initials (声母) → Palladius ────────────────────────────────────────────────────────────────────
INITIALS = {
    "b": "б", "p": "п", "m": "м", "f": "ф", "d": "д", "t": "т", "n": "н", "l": "л",
    "g": "г", "k": "к", "h": "х", "j": "цз", "q": "ц", "x": "с",
    "zh": "чж", "ch": "ч", "sh": "ш", "r": "ж", "z": "цз", "c": "ц", "s": "с",
    "": "",   # zero initial (y/w handled as whole syllables below)
}

# ── finals (韵母) → Palladius (base forms; some initial-conditioned variants applied in build) ──────
FINALS = {
    "a": "а", "o": "о", "e": "э", "ai": "ай", "ei": "эй", "ao": "ао", "ou": "оу",
    "an": "ань", "en": "энь", "ang": "ан", "eng": "эн", "er": "эр", "ong": "ун",
    "i": "и", "ia": "я", "ie": "е", "iao": "яо", "iu": "ю", "ian": "янь",
    "in": "инь", "iang": "ян", "ing": "ин", "iong": "юн",
    "u": "у", "ua": "уа", "uo": "о", "uai": "уай", "ui": "уй", "uan": "уань",
    "un": "унь", "uang": "уан", "ueng": "ун",
    "v": "юй", "ve": "юэ", "van": "юань", "vn": "юнь",   # ü written as v
}

# whole zero-initial syllables (y-/w-)
Y_W = {
    "yi": "и", "ya": "я", "ye": "е", "yao": "яо", "you": "ю", "yan": "янь", "yin": "инь",
    "yang": "ян", "ying": "ин", "yong": "юн", "yu": "юй", "yue": "юэ", "yuan": "юань", "yun": "юнь",
    "wu": "у", "wa": "ва", "wo": "во", "wai": "вай", "wei": "вэй", "wan": "вань", "wen": "вэнь",
    "wang": "ван", "weng": "вэн",
}
# retroflex/sibilant + i => -ы/-и class (zhi chi shi ri zi ci si)
SPECIAL_I = {
    "zhi": "чжи", "chi": "чи", "shi": "ши", "ri": "жи", "zi": "цзы", "ci": "цы", "si": "сы",
}
# initials that take j/q/x with ü finals written as u (ju->цзюй etc.)
_JQX = {"j", "q", "x"}


def _final_after(initial: str, final: str, cyr_ini: str, cyr_fin: str) -> str:
    """Apply the few initial-conditioned Palladius adjustments."""
    # e after most initials -> э, but after certain -> е is not standard; keep э.
    # 'o' after b/p/m/f -> о (бо/по/мо/фо); 'uo' after them n/a.
    # 'ie'->е, 'ei'->эй are fine. Keep base mapping.
    return cyr_ini + cyr_fin


def build_syllables() -> dict[str, str]:
    """pinyin syllable -> Palladius. Generated; not exhaustive of tone/rare finals but covers names."""
    syl = {}
    syl.update(Y_W)
    syl.update(SPECIAL_I)
    for pi, ci in INITIALS.items():
        if pi == "":
            continue
        for pf, cf in FINALS.items():
            # ü finals (v) only valid after j/q/x/l/n; jqx write ü as plain u in pinyin
            if pf in ("v", "ve", "van", "vn"):
                if pi in _JQX:
                    py = pi + pf.replace("v", "u")     # ju/jue/juan/jun
                elif pi in ("l", "n"):
                    py = pi + pf.replace("v", "ü")     # lü/nü
                else:
                    continue
            else:
                py = pi + pf
            # skip the retroflex/sibilant + bare i (handled by SPECIAL_I)
            if pf == "i" and pi in ("zh", "ch", "sh", "r", "z", "c", "s"):
                continue
            syl[py] = _final_after(pi, pf, ci, cf)
    return syl


SYLLABLES = build_syllables()
# Cyrillic syllable inventory (values), longest-first for greedy segmentation
_CYR_SYL = sorted(set(SYLLABLES.values()), key=len, reverse=True)
_RE_CYR_WORD = regex.compile(r"^[\p{Cyrillic}]+$")


def is_palladius_token(token: str, min_syllables=1) -> bool:
    """True if `token` (a ru word) segments fully into Palladius syllables (greedy longest-match).
    Used as a high-precision 'this looks like a transliterated Chinese name' signal."""
    t = token.strip().lower().replace("ъ", "")
    if not t or not _RE_CYR_WORD.match(t):
        return False
    n_syl = 0
    i = 0
    while i < len(t):
        for s in _CYR_SYL:
            if s and t.startswith(s, i):
                i += len(s)
                n_syl += 1
                break
        else:
            return False
    return n_syl >= min_syllables


def palladius_conformant(dst: str) -> bool:
    """Canon conformity (§C2-3): every whitespace/hyphen-separated Cyrillic word of dst is a Palladius
    token. For multi-word names (Фан Юань) all parts must conform. Non-Cyrillic parts are ignored."""
    words = regex.findall(r"[\p{Cyrillic}]+", dst)
    cyr = [w for w in words if _RE_CYR_WORD.match(w)]
    if not cyr:
        return False
    return all(is_palladius_token(w, min_syllables=1) for w in cyr)


def ru_name_tokens(draft: str, min_syllables=2) -> list[str]:
    """Capitalized ru tokens in a draft that parse as Palladius (>=min_syllables) — model-flagged names."""
    out = []
    for tok in regex.findall(r"\b[\p{Lu}][\p{Cyrillic}]+", draft):
        if is_palladius_token(tok, min_syllables=min_syllables):
            out.append(tok)
    return out


if __name__ == "__main__":
    print(f"Palladius syllables generated: {len(SYLLABLES)} pinyin -> {len(set(SYLLABLES.values()))} cyr forms")
    # validate: every GT dst name/place syllable must be recognized
    gt = X.load_gt()
    name_dsts = [e.dst for e in gt if e.typ in ("name",) and e.dst]
    print("\n== GT name dst recognition ==")
    ok = bad = 0
    for e in gt:
        if e.typ not in ("name", "place"):
            continue
        # take the transliterated part(s) — for places skip the leading common noun (гора/деревня)
        words = regex.findall(r"[\p{Cyrillic}]+", e.dst)
        translit = [w for w in words if w.lower() not in ("гора", "деревня", "село", "селение", "стан", "клан", "род")]
        good = all(is_palladius_token(w) for w in translit) if translit else False
        ok += good
        bad += not good
        mark = "OK " if good else "MISS"
        print(f"  {mark} {e.src:<6} {e.dst:<26} translit={translit} "
              f"{[w for w in translit if not is_palladius_token(w)] if not good else ''}")
    print(f"\nrecognized {ok}/{ok+bad} GT name/place dst")
    # negatives: common Russian words should NOT be Palladius
    print("\n== negative control (common ru words, should be FALSE) ==")
    for w in ["человек", "который", "сказал", "деревня", "истинный", "камень", "мастер", "ранг"]:
        print(f"  {w:<12} palladius={is_palladius_token(w)}")
