#!/usr/bin/env python3
"""Bank batch-241: Rule 199 tree-reference lexical expansion."""

from __future__ import annotations

import datetime as dt
import json
import re
import sqlite3

from process_ledger import ensure_schema, record_event

DB = "/mnt/donto-data/donto-resources/research/translation-training/kuku-yalanji-runpod-2026-06-30/synthetic/claude-synthetic-v1-2026-07-02/synthetic.db"
LABEL = "batch-241"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-241-tree-reference-lexical-expansion-after-rule198-process.md"

TARGET_HEADS = {
    "jinjajalka": "jinjajalka tree",
    "kurunkal": "kurunkal mangrove",
    "madu": "madu sea pine",
    "marngkil": "marngkil ti tree",
    "marrabal": "marrabal ti tree",
    "milbarakal": "milbarakal tree",
    "mungari": "mungari kaori pine",
    "wurmbariji": "wurmbariji scrub tree",
    "wuymbariji": "wuymbariji scrub tree",
    "kurrun": "kurrun tree",
}
SUPPORT = {"ngayu", "yundu", "jana", "ngana", "yaluy", "yinyay", "nyaji-l"}
DENY_KUKU = {
    "mayi", "nuka", "nukan", "waju", "wajun", "warrmba", "warrmban", "mani", "manin", "daji", "dajin",
    "dinda", "dindan", "duda", "dudan", "wukurri", "wukurrin", "ngara", "ngaran", "kuni", "kunin",
    "yaka", "yakan", "balka", "balkan", "kida", "kidan", "dunga", "dungan", "wandi", "wandin",
    "baya", "kubu", "kurrma", "bubu", "bayan", "bana", "jalun", "jikan", "nganka", "diburr",
    "wunba", "dukul", "wawu", "buyku", "bijan", "marril", "mirrbangku", "miyilabu", "ngadil", "nyumbil",
    "wanakan", "wankar", "yurrunji",
}
DENY_ENGLISH = (
    "fruit", "fruits", "seed", "seeds", "edible", "eat", "eats", "ate", "food", "medicine", "medicinal",
    "poison", "poisonous", "sap", "timber", "wood", "bark", "oar", "oars", "woomera", "spear", "burn",
    "burning", "cut", "cutting", "gather", "gathering", "grow", "growing", "ecology", "place", "place-name",
    "story", "country", "own", "ownership", "necklace", "leech", "blind", "blindness", "leaf", "leaves",
    "flower", "flowers", "honey", "grub", "bait", "use", "used", "make", "making", "material", "source",
)


def js(values: list[str]) -> str:
    return json.dumps(values, ensure_ascii=False)


def row(seq: int, english: str, kuku: str, analysis: str, frame: str, words: list[str], evidence: str) -> tuple:
    return (seq, english, kuku, analysis, frame, 3, js(words), js([]), evidence, "0.86")


PAIRS = [
    ("jinjajalka tree", "jinjajalka", "jinjajalka.tree", "Dictionary: jinjajalka, noun, species of tree; no use prose."),
    ("kurunkal mangrove", "kurunkal", "kurunkal.mangrove", "Dictionary: kurunkal, noun, species of mangrove; seed prose closed."),
    ("madu sea pine", "madu", "madu.sea.pine", "Dictionary: madu, noun, sea pine tree."),
    ("marngkil ti tree", "marngkil", "marngkil.ti.tree", "Dictionary: marngkil, noun, species of ti tree."),
    ("marrabal ti tree", "marrabal", "marrabal.ti.tree", "Dictionary: marrabal, noun, species of ti tree."),
    ("milbarakal tree", "milbarakal", "milbarakal.tree", "Dictionary: milbarakal, noun, species of tree."),
    ("mungari kaori pine", "mungari", "mungari.kaori.pine", "Dictionary: mungari, noun, kaori/kauri pine tree."),
    ("wurmbariji scrub tree", "wurmbariji", "wurmbariji.scrub.tree", "Dictionary: wurmbariji, noun, species of tree found in scrub."),
    ("wuymbariji scrub tree", "wuymbariji", "wuymbariji.scrub.tree", "Dictionary: wuymbariji, noun, scrub tree."),
    ("kurrun tree", "kurrun", "kurrun.tree", "Dictionary: kurrun, noun, tree species with English name unknown."),
]

ROWS = []
for i, (eng, head, gloss, evidence) in enumerate(PAIRS, start=1):
    cap = head[:1].upper() + head[1:]
    ROWS.append(row(i, f"The {eng} is here.", f"{cap} yaluy.", f"{head} yaluy | {gloss} here", f"r199_tree_here_{head}", [head, "yaluy"], evidence))
for i, (eng, head, gloss, evidence) in enumerate(PAIRS, start=11):
    ROWS.append(row(i, f"I see the {eng}.", f"Ngayu {head} nyajil.", f"ngayu {head} nyaji-l | I {gloss} see-NONPAST", f"r199_1sg_tree_object_{head}", ["ngayu", head, "nyaji-l"], evidence))
for i, (eng, head, gloss, evidence) in enumerate(PAIRS, start=21):
    ROWS.append(row(i, f"You see the {eng} there.", f"Yundu {head} yinyay nyajil.", f"yundu {head} yinyay nyaji-l | you {gloss} there see-NONPAST", f"r199_2sg_tree_object_there_{head}", ["yundu", head, "yinyay", "nyaji-l"], evidence))
for i, (eng, head, gloss, evidence) in enumerate(PAIRS, start=31):
    ROWS.append(row(i, f"They saw the {eng} there.", f"Jana {head} yinyay nyajin.", f"jana {head} yinyay nyaji-ny | they {gloss} there see-PAST", f"r199_3pl_past_tree_object_there_{head}", ["jana", head, "yinyay", "nyaji-l"], evidence))
for i, (eng, head, gloss, evidence) in enumerate(PAIRS, start=41):
    ROWS.append(row(i, f"We see the {eng} here.", f"Ngana {head} yaluy nyajil.", f"ngana {head} yaluy nyaji-l | we {gloss} here see-NONPAST", f"r199_1pl_tree_object_here_{head}", ["ngana", head, "yaluy", "nyaji-l"], evidence))


def has_token(low: str, token: str) -> bool:
    return re.search(rf"(?<![a-z-]){re.escape(token)}(?![a-z-])", low) is not None


def has_target_morphology(low: str, head: str) -> bool:
    suffixes = "ngka|ngku|angka|nga|ngu|ba|mba|mbu|muny|darr|burr|bu|ka|nka|nku|ji|jirr|ku|mundu|amu|mu|iji|nda|ndu|anda|undu|unji|nji|kari|baka|baja|bungal|bungan|mal|man"
    return re.search(rf"(?<![a-z-]){re.escape(head)}-(?:{suffixes})\b", low) is not None


def english_has_denied(english_low: str, denied: str) -> bool:
    return denied in english_low if " " in denied else re.search(rf"\b{re.escape(denied)}\b", english_low) is not None


def scan_static() -> list[str]:
    issues: list[str] = []
    if len(ROWS) != 50:
        issues.append(f"{LABEL}: expected 50 rows, found {len(ROWS)}")
    seen_ku: set[str] = set()
    for expected, row_data in enumerate(ROWS, start=1):
        seq, english, kuku, analysis, frame, _tier, words_used, loans_used, evidence, confidence = row_data
        label = f"{LABEL}.{seq:02d}"
        low = kuku.lower()
        english_low = english.lower()
        words = json.loads(words_used)
        if seq != expected:
            issues.append(f"{label}: sequence mismatch; expected {expected}")
        if not english[:1].isupper() or english[-1:] not in ".!?":
            issues.append(f"{label}: English punctuation/capitalization failure")
        if " | " not in analysis:
            issues.append(f"{label}: analysis missing gloss separator")
        if not frame.startswith("r199_"):
            issues.append(f"{label}: Rule 199 frame must start with r199_")
        if low in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface within batch")
        seen_ku.add(low)
        if json.loads(loans_used):
            issues.append(f"{label}: no loans are admitted in Rule 199")
        for denied in DENY_KUKU:
            if has_token(low, denied):
                issues.append(f"{label}: denied use/homograph token {denied!r}")
        targets = [head for head in TARGET_HEADS if has_token(low, head)]
        if len(targets) != 1:
            issues.append(f"{label}: expected exactly one Rule 199 tree target, found {targets}")
        for head in targets:
            selected = TARGET_HEADS[head]
            if head not in words:
                issues.append(f"{label}: target head {head} missing from words_used")
            if selected not in english_low:
                issues.append(f"{label}: English must include selected sense {selected!r}")
            if selected.replace(" ", ".") not in analysis:
                issues.append(f"{label}: analysis must gloss {head} as {selected.replace(' ', '.')}")
            if has_target_morphology(low, head):
                issues.append(f"{label}: target head {head} has forbidden morphology")
        for support in SUPPORT:
            if support == "nyaji-l":
                continue
            if has_token(low, support) and support not in words:
                issues.append(f"{label}: support {support} missing from words_used")
        if 1 <= seq <= 10 and (not low.endswith(" yaluy.") or "nyaji-l" in words or has_token(low, "yinyay")):
            issues.append(f"{label}: rows 1-10 require TARGET yaluy only")
        if 11 <= seq <= 20 and (not low.startswith("ngayu ") or not low.endswith(" nyajil.") or has_token(low, "yaluy") or has_token(low, "yinyay") or "nyaji-l" not in words):
            issues.append(f"{label}: rows 11-20 require Ngayu TARGET nyajil")
        if 21 <= seq <= 30 and (not low.startswith("yundu ") or not has_token(low, "yinyay") or has_token(low, "yaluy") or not low.endswith(" nyajil.") or "nyaji-l" not in words):
            issues.append(f"{label}: rows 21-30 require Yundu TARGET yinyay nyajil")
        if 31 <= seq <= 40 and (not low.startswith("jana ") or not has_token(low, "yinyay") or has_token(low, "yaluy") or not low.endswith(" nyajin.") or "nyaji-l" not in words):
            issues.append(f"{label}: rows 31-40 require Jana TARGET yinyay nyajin")
        if 41 <= seq <= 50 and (not low.startswith("ngana ") or not has_token(low, "yaluy") or has_token(low, "yinyay") or not low.endswith(" nyajil.") or "nyaji-l" not in words):
            issues.append(f"{label}: rows 41-50 require Ngana TARGET yaluy nyajil")
        if "nyajin" in low and "see-PAST" not in analysis:
            issues.append(f"{label}: nyajin must be glossed as see-PAST")
        if "nyajil" in low and "see-NONPAST" not in analysis:
            issues.append(f"{label}: nyajil must be glossed as see-NONPAST")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied tree-use/source-prose field in English")
        if not evidence or not confidence:
            issues.append(f"{label}: missing evidence/confidence")
    return issues


def main() -> int:
    issues = scan_static()
    if issues:
        print("pre-bank scan failed:")
        for issue in issues:
            print(f"  - {issue}")
        return 1

    db = sqlite3.connect(DB)
    db.execute("PRAGMA foreign_keys = ON")
    db.execute("BEGIN")
    ensure_schema(db)
    existing = db.execute("SELECT id FROM batches WHERE label=?", (LABEL,)).fetchone()
    if existing:
        batch_id = existing[0]
        accepted = db.execute("SELECT COUNT(*) FROM sentences WHERE batch_id=? AND status IN ('accepted','revised')", (batch_id,)).fetchone()[0]
        if accepted:
            raise SystemExit(f"{LABEL} already has accepted/revised rows; refusing to overwrite")
        db.execute("DELETE FROM reviews WHERE sentence_id IN (SELECT id FROM sentences WHERE batch_id=?)", (batch_id,))
        db.execute("DELETE FROM sentences WHERE batch_id=?", (batch_id,))
        db.execute("UPDATE batches SET notes=?, created_at=? WHERE id=?", ("rebanked under_review", NOW, batch_id))
    else:
        cur = db.execute(
            "INSERT INTO batches(label, cheatsheet_version, guide_version, notes, created_at) VALUES(?,?,?,?,?)",
            (LABEL, "Rule 199 draft: tree-reference lexical expansion after Rule 198", "operator-guide-2026-07-08", "under_review", NOW),
        )
        batch_id = cur.lastrowid

    dupes: list[str] = []
    for seq, _english, kuku, *_rest in ROWS:
        hit = db.execute(
            """
            SELECT b.label, s.seq
            FROM sentences s JOIN batches b ON b.id=s.batch_id
            WHERE lower(trim(s.kuku))=lower(trim(?))
              AND s.status!='rejected'
              AND s.batch_id!=?
            """,
            (kuku, batch_id),
        ).fetchone()
        if hit:
            dupes.append(f"{LABEL}.{seq:02d} duplicates {hit[0]}.{hit[1]:02d}: {kuku}")
    if dupes:
        db.rollback()
        print("cross-corpus duplicate check failed:")
        for dupe in dupes:
            print(f"  - {dupe}")
        return 1

    record_event(db, batch_label=LABEL, phase="source", event_type="source-pass", actor="claude-main",
                 detail="Rule 199 source pass selected ten dictionary-backed, previously unused tree-reference nouns: jinjajalka, kurunkal, madu, marngkil, marrabal, milbarakal, mungari, wurmbariji, wuymbariji, and kurrun. Dictionary readback confirmed all are nouns in flora-tree/tree senses. Grammar/corpus support retained yaluy, yinyay, nyaji-l nonpast nyajil, past nyajin, and bare pronoun A forms.",
                 command="dictionary.yaml exact readback; sqlite corpus count and duplicate preflight; accepted perception-frame readback",
                 artifact_path=PROCESS_LOG, created_at=NOW)
    record_event(db, batch_label=LABEL, phase="scope", event_type="lexical-scope", actor="claude-main",
                 detail="Rule 199 is a tree-reference lexical expansion. It admits bare reference nouns only and excludes fruit, seeds, food, medicine, poison, sap, timber, bark, wood, oars, woomeras, spears, burning, cutting, gathering, growing/ecology, place names, story/country, ownership, material-source, and tree-use claims.",
                 artifact_path=PROCESS_LOG, created_at=NOW)
    record_event(db, batch_label=LABEL, phase="preflight", event_type="duplicate-preflight", actor="claude-main",
                 detail="Exact duplicate preflight over all 50 planned tree-reference surfaces returned no accepted/revised/under_review collisions.",
                 command="sqlite CTE exact lower(trim(kuku)) duplicate scan before banking", artifact_path="batch_scripts/bank_241.py", created_at=NOW)
    record_event(db, batch_label=LABEL, phase="preflight", event_type="static-guard-pass", actor="claude-main",
                 detail="Static guard passed: exactly 50 unique surfaces; exactly one tree target per row; all target heads bare; no loans; no denied tree-use/source-prose tokens; nyaji-l present only in perception rows; and correct yaluy/yinyay/pronoun/tense frame distribution.",
                 command="python3 batch_scripts/bank_241.py pre-bank scan", artifact_path="batch_scripts/bank_241.py", created_at=NOW)

    db.executemany(
        """
        INSERT INTO sentences(
          batch_id, seq, english, kuku, analysis, frame, tier, words_used,
          loans_used, evidence, status, confidence, author, created_at, updated_at
        )
        VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
        """,
        [(batch_id, seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence, "under_review", confidence, "claude", NOW, NOW)
         for seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence, confidence in ROWS],
    )
    db.commit()

    count = db.execute("SELECT COUNT(*) FROM sentences WHERE batch_id=?", (batch_id,)).fetchone()[0]
    under_review = db.execute("SELECT COUNT(*) FROM sentences WHERE batch_id=? AND status='under_review'", (batch_id,)).fetchone()[0]
    verified = db.execute("SELECT COUNT(*) FROM sentences WHERE status IN ('accepted','revised')").fetchone()[0]
    print(f"banked {LABEL}: rows={count}, under_review={under_review}, verified_before_close={verified}")
    return 0


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