#!/usr/bin/env python3
"""Bank batch-240: Rule 198 visual-property perception consolidation."""

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-240"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-240-visual-property-perception-consolidation-after-rule197-process.md"

TARGET_HEADS = {
    "ngumbu": "black/dark",
    "bingaji": "light-coloured",
    "ngala-ngala": "red",
    "dayirr": "clean/clear",
}
SUBJECT_NOUNS = {
    "kulji": "stone",
    "juku": "tree",
    "ngurrban": "cloud",
    "mungka": "hair",
    "kambi": "clothing",
    "nganka": "flower",
    "karrambil": "king parrot",
    "bana": "water",
    "bubu": "ground",
    "bayan": "house",
}
PARTICIPANT_SUPPORT = {"ngayu", "yundu", "jana", "ngana", "yaluy", "yinyay", "nyaji-l"}
DENY_KUKU = {
    "waki", "kambar", "makirr", "makarra", "murbuy", "wuba", "mula-mula", "marun-marun", "jurrbil",
    "warral", "wanka", "jarrmba", "jinbal", "jibarr", "kunji", "dalku", "kubarr", "jarrakulba",
    "balbal", "ngungul-ngungul", "blue-bala", "brown-bala", "karrkay", "kulngkul", "kari",
    "ngumbu-kari", "bingaji-kari", "ngala-ngala-kari", "dayirr-kari", "baja", "jarra", "bungan",
    "bungal", "manya", "manda",
}
DENY_ENGLISH = (
    "green", "blue", "brown", "purple", "yellow", "white", "pale", "ochre", "clay", "paint", "shine",
    "shining", "glow", "glowing", "burn", "burnt", "smoke", "storm", "rain", "weather", "country",
    "own", "belong", "feather", "fly", "eat", "food", "tree-use", "timber", "cut", "make", "become",
    "very", "more", "less", "not", "without", "hear", "heard", "listen", "listening",
)


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.87")


PAIRS = [
    ("stone", "kulji", "ngumbu", "black", "Rule 103 colour table: Kulji ngumbu; object NP support from Ngayu bayan yalbay nyajin."),
    ("tree", "juku", "ngumbu", "black", "Dictionary juku tree/wood; colour adjective ngumbu; object NP in perception frame."),
    ("cloud", "ngurrban", "ngumbu", "dark", "Dictionary ngurrban cloud; ngumbu dark/black; no weather-event predicate."),
    ("hair", "mungka", "bingaji", "light-coloured", "Rule 103 colour table: Mungka bingaji; adjective sense only."),
    ("clothing", "kambi", "bingaji", "light-coloured", "Dictionary kambi clothing sense; bingaji adjective only; flying-fox homograph closed."),
    ("flower", "nganka", "ngala-ngala", "red", "Rule 103 colour table: Nganka ngala-ngala; dictionary nganka flower."),
    ("king parrot", "karrambil", "ngala-ngala", "red", "Dictionary karrambil king parrot; ngala-ngala red; no bird-event semantics."),
    ("water", "bana", "dayirr", "clear", "Rule 103 property table: Bana dayirr; dayirr clear/clean only."),
    ("ground", "bubu", "dayirr", "clean", "Dictionary bubu ground/earth; dayirr clean/clear; country readings closed."),
    ("house", "bayan", "dayirr", "clean", "Rule 112/154 house-clean frames; object NP order supported by perception examples."),
]

ROWS = []
for i, (eng_n, kuku_n, adj, eng_adj, evidence) in enumerate(PAIRS, start=1):
    ROWS.append(row(i, f"I see the {eng_adj} {eng_n}.", f"Ngayu {kuku_n} {adj} nyajil.", f"ngayu {kuku_n} {adj} nyaji-l | I {eng_n.replace(' ', '.')} {eng_adj} see-NONPAST", f"r198_1sg_visual_object_{kuku_n}_{adj.replace('-', '_')}", ["ngayu", kuku_n, adj, "nyaji-l"], evidence))
for i, (eng_n, kuku_n, adj, eng_adj, evidence) in enumerate(PAIRS, start=11):
    ROWS.append(row(i, f"You see the {eng_adj} {eng_n}.", f"Yundu {kuku_n} {adj} nyajil.", f"yundu {kuku_n} {adj} nyaji-l | you {eng_n.replace(' ', '.')} {eng_adj} see-NONPAST", f"r198_2sg_visual_object_{kuku_n}_{adj.replace('-', '_')}", ["yundu", kuku_n, adj, "nyaji-l"], evidence))
for i, (eng_n, kuku_n, adj, eng_adj, evidence) in enumerate(PAIRS, start=21):
    ROWS.append(row(i, f"They saw the {eng_adj} {eng_n}.", f"Jana {kuku_n} {adj} nyajin.", f"jana {kuku_n} {adj} nyaji-ny | they {eng_n.replace(' ', '.')} {eng_adj} see-PAST", f"r198_3pl_past_visual_object_{kuku_n}_{adj.replace('-', '_')}", ["jana", kuku_n, adj, "nyaji-l"], evidence))
for i, (eng_n, kuku_n, adj, eng_adj, evidence) in enumerate(PAIRS, start=31):
    ROWS.append(row(i, f"We see the {eng_adj} {eng_n} here.", f"Ngana {kuku_n} {adj} yaluy nyajil.", f"ngana {kuku_n} {adj} yaluy nyaji-l | we {eng_n.replace(' ', '.')} {eng_adj} here see-NONPAST", f"r198_1pl_here_visual_object_{kuku_n}_{adj.replace('-', '_')}", ["ngana", kuku_n, adj, "yaluy", "nyaji-l"], evidence))
for i, (eng_n, kuku_n, adj, eng_adj, evidence) in enumerate(PAIRS, start=41):
    ROWS.append(row(i, f"You see the {eng_adj} {eng_n} there.", f"Yundu {kuku_n} {adj} yinyay nyajil.", f"yundu {kuku_n} {adj} yinyay nyaji-l | you {eng_n.replace(' ', '.')} {eng_adj} there see-NONPAST", f"r198_2sg_there_visual_object_{kuku_n}_{adj.replace('-', '_')}", ["yundu", kuku_n, adj, "yinyay", "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_forbidden_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("r198_"):
            issues.append(f"{label}: Rule 198 frame must start with r198_")
        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 198")
        for denied in DENY_KUKU:
            if has_token(low, denied):
                issues.append(f"{label}: denied carry-forward 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 198 visual target head, found {targets}")
        for head in targets:
            if head not in words:
                issues.append(f"{label}: target head {head} missing from words_used")
            if has_forbidden_target_morphology(low, head):
                issues.append(f"{label}: target head {head} has forbidden morphology")
        subjects = [head for head in SUBJECT_NOUNS if has_token(low, head)]
        if len(subjects) != 1:
            issues.append(f"{label}: expected exactly one subject noun, found {subjects}")
        for subject in subjects:
            if subject not in words:
                issues.append(f"{label}: subject noun {subject} missing from words_used")
        if "nyaji-l" not in words:
            issues.append(f"{label}: nyaji-l support missing from words_used")
        for support in PARTICIPANT_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.startswith("ngayu ") or not low.endswith(" nyajil.") or has_token(low, "yaluy") or has_token(low, "yinyay")):
            issues.append(f"{label}: rows 1-10 require Ngayu NP ADJ nyajil with no local demonstrative")
        if 11 <= seq <= 20 and (not low.startswith("yundu ") or not low.endswith(" nyajil.") or has_token(low, "yaluy") or has_token(low, "yinyay")):
            issues.append(f"{label}: rows 11-20 require Yundu NP ADJ nyajil with no local demonstrative")
        if 21 <= seq <= 30 and (not low.startswith("jana ") or not low.endswith(" nyajin.") or has_token(low, "yaluy") or has_token(low, "yinyay")):
            issues.append(f"{label}: rows 21-30 require Jana NP ADJ nyajin with no local demonstrative")
        if 31 <= seq <= 40 and (not low.startswith("ngana ") or not has_token(low, "yaluy") or has_token(low, "yinyay") or not low.endswith(" nyajil.")):
            issues.append(f"{label}: rows 31-40 require Ngana NP ADJ yaluy nyajil")
        if 41 <= seq <= 50 and (not low.startswith("yundu ") or not has_token(low, "yinyay") or has_token(low, "yaluy") or not low.endswith(" nyajil.")):
            issues.append(f"{label}: rows 41-50 require Yundu NP ADJ yinyay nyajil")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied colour/polysemy/event 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 198 draft: visual-property perception construction consolidation", "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 198 source pass retained Rule 197 visual-property heads but moved them into established nyaji-l perception frames. Source support: nyaji-l see/hear/perceive with nonpast nyajil and past nyajin; bare pronoun A for ngayu, yundu, jana, and ngana; unmarked object NP with noun before adjective; yaluy and yinyay as local demonstrative support.",
                 command="operator handoff readback; Patz grammar nyaji-l examples; existing accepted perception rows; sqlite exact duplicate preflight",
                 artifact_path=PROCESS_LOG, created_at=NOW)
    record_event(db, batch_label=LABEL, phase="scope", event_type="construction-scope", actor="claude-main",
                 detail="Rule 198 is construction consolidation, not lexical expansion. It excludes negation, -kari privative forms, common-noun perceivers, new ERG decisions, hearing/listening English, colour-field expansion, case, degree, comparison, causative, inchoative, plural, compounds, productive reduplication, weather, ownership, material-use, bird-event, and body-possession semantics.",
                 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 visual-property perception surfaces returned no accepted/revised/under_review collisions.",
                 command="sqlite CTE exact lower(trim(kuku)) duplicate scan before banking", artifact_path="batch_scripts/bank_240.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 retained visual target and one object noun per row; nyaji-l present in every words_used list; no loans; no denied colour/polysemy/event tokens; no target morphology; and correct pronoun/tense/local-demonstrative frame distribution.",
                 command="python3 batch_scripts/bank_240.py pre-bank scan", artifact_path="batch_scripts/bank_240.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())
