#!/usr/bin/env python3
"""Bank batch-127: landscape and geography lexical field.

Rows are hand-authored from Patz's spatial/location sections and the corrected
dictionary. The script validates and banks the rows as under_review; it does not
generate sentence content.
"""

from __future__ import annotations

import datetime as dt
import json
import re
import sqlite3
import sys

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-127"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")

DENY = {
    "nyawa", "jinbi", "jibin", "walba", "julmbu", "wanan", "ngampula",
    "yalama", "ngana-jinji", "jubuny", "wural", "bigan", "jiwuny",
    "warngkul", "wanaba", "jarrkunji", "munjal", "murku", "buni",
    "kunya", "milkan", "kulur-jambul", "janyji", "buburr", "jarrmba",
    "jinji", "daga", "wanka", "yumbul", "kuku-yumbul", "jubi",
    "yilbay", "jarrakay", "janka-yukil", "jukarr", "kunkul",
    "bubu-jandu", "bubu-warral", "jani-warra", "dungkarr", "jimba",
    "bujal", "wanyjarr", "ngudan", "jimirr", "jangan", "ganggal",
    "bukul", "jalany", "nyunun", "yinkar", "jiba-ngulkurr", "jiba-wawu",
    "nyawa-kari", "jinbi-kari", "buri", "bibiny", "jubaji", "nyalbu",
    "kuka", "kambar", "dalku", "milbin", "kindu",
}


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


ROWS = [
    (1, "This is my country.",
     "Yanya ngayku bubu.",
     "yanya ngayku bubu | this my country",
     "nominal_country_identity", 3, js(["yanya", "ngayku", "bubu"]),
     "Dictionary bubu 'ground; earth; a person's country'; Patz l.641 country/descent-group bubu.", "0.88"),
    (2, "That country is big.",
     "Yinya bubu yalbay.",
     "yinya bubu yalbay | that country big",
     "nominal_np_adj", 3, js(["yinya", "bubu", "yalbay"]),
     "Patz noun/adjective nominal clauses; bubu 'country, ground, earth, space' (l.1491).", "0.86"),
    (3, "Some places are just the same.",
     "Kanbal bubu yala-rrku.",
     "kanbal bubu yala-rrku | some place same-EMPH4",
     "attested_nominal_place", 4, js(["kanbal", "bubu", "yala"]),
     "Patz ex.206: Kanbal bubu yala-rrku 'Some places are just the same'.", "0.90"),
    (4, "The ground was really slippery.",
     "Bubu julbarr-bi.",
     "bubu julbarr-bi | ground slippery-EMPH3",
     "attested_np_adj_emph", 4, js(["bubu", "julbarr"]),
     "Patz text 3 line 6: bubu julbarr-bi 'the ground was really slippery'.", "0.90"),
    (5, "I sweep the ground for a camp.",
     "Ngayu bubu kida-l bayan-ka.",
     "ngayu bubu kida-l bayan-ka | I ground sweep-NONPAST camp-DAT",
     "transitive_nonpast_dative_purpose", 4, js(["ngayu", "bubu", "kidal", "bayan"]),
     "Patz ex.253/449: bubu kida-l for sweeping ground before setting up camp.", "0.88"),
    (6, "The child saw the slippery ground.",
     "Karrkay-angka bubu julbarr nyajin.",
     "karrkay-angka bubu julbarr nyaji-ny | child-ERG ground slippery see-PAST",
     "transitive_past_landscape_object", 4, js(["karrkay", "bubu", "julbarr", "nyajil"]),
     "Patz text 3: children saw bubu julbarr-bi; transitive A takes potent ERG.", "0.86"),
    (7, "The woman chases the dog from the camp.",
     "Jalbu-ngku kaya kanga-l bayan-muny.",
     "jalbu-ngku kaya kanga-l bayan-muny | woman-ERG dog chase-NONPAST camp-ABL",
     "ablative_source", 4, js(["jalbu", "kaya", "kangal", "bayan"]),
     "Patz ex.281: woman chases dog from house/camp; neutral ablative -muny.", "0.90"),
    (8, "The children came to that place.",
     "Karrkay-karrkay yinya bubu-ngu kadan.",
     "karrkay-karrkay yinya bubu-ngu kada-ny | child-REDUP that place-LOC come-PAST",
     "locative_goal_past", 4, js(["karrkay", "yinya", "bubu", "kaday"]),
     "Patz text l.7173: yinyay-mba-bi kada-ny bubu-ngu 'came to that very place'.", "0.86"),
    (9, "The old woman stayed at the camp.",
     "Mukul-jalbu bayan-ba bundan.",
     "mukul-jalbu bayan-ba bunda-ny | old-woman camp-LOC sit/stay-PAST",
     "locative_posture_past", 4, js(["mukul-jalbu", "bayan", "bundal"]),
     "Patz locative clause pattern: bayan-ba with bunda-y/bundanda-y for sitting or staying.", "0.86"),
    (10, "The camp is on the mountain.",
     "Bayan manjal-ba.",
     "bayan manjal-ba | camp mountain-LOC",
     "nominal_locative_predicate", 3, js(["bayan", "manjal"]),
     "Patz ex.504 has manjal-ba 'on the mountain'; nominal locative predicate pattern ex.297-299.", "0.88"),
    (11, "The child stands on the mountain.",
     "Karrkay manjal-ba janay.",
     "karrkay manjal-ba jana-y | child mountain-LOC stand-NONPAST",
     "locative_posture_nonpast", 4, js(["karrkay", "manjal", "janay"]),
     "Patz ex.504: manjal-ba; posture verbs with locative settings.", "0.86"),
    (12, "The mountain is big.",
     "Manjal yalbay.",
     "manjal yalbay | mountain big",
     "nominal_np_adj", 3, js(["manjal", "yalbay"]),
     "Dictionary manjal 'mountain'; standard adjective predicate without copula.", "0.84"),
    (13, "The mountain peak is big.",
     "Jalkaraburr yalbay.",
     "jalkaraburr yalbay | mountain.peak big",
     "nominal_np_adj", 3, js(["jalkaraburr", "yalbay"]),
     "Dictionary jalkaraburr 'high reaches of the mountains; mountain peak'.", "0.84"),
    (14, "The high bank is up there.",
     "Jalkar wangkarwangkar.",
     "jalkar wangkarwangkar | high.bank up.there",
     "nominal_location_word", 3, js(["jalkar", "wangkarwangkar"]),
     "Dictionary jalkar 'high bank'; Patz §3.3 wangkarwangkar 'up there, above'.", "0.85"),
    (15, "The valley is down there.",
     "Kangku badabada.",
     "kangku badabada | valley down.there",
     "nominal_location_word", 3, js(["kangku", "badabada"]),
     "Dictionary kangku 'valley, mountain pass'; Patz §3.3 badabada 'down there'.", "0.85"),
    (16, "The hillside is big.",
     "Jamba yalbay.",
     "jamba yalbay | hillside big",
     "nominal_np_adj", 3, js(["jamba", "yalbay"]),
     "Dictionary jamba 'hillside'.", "0.84"),
    (17, "They went on the hillside.",
     "Jana jamba-nga dungan.",
     "jana jamba-nga dunga-ny | they hillside-LOC go-PAST",
     "locative_motion_past", 4, js(["jana", "jamba", "dungay"]),
     "Dictionary jamba usage gives jamba-jamba-nga for along hillsides; V-final locative -nga.", "0.84"),
    (18, "The rainforest is big.",
     "Madja yalbay.",
     "madja yalbay | rainforest/scrub big",
     "nominal_np_adj", 3, js(["madja", "yalbay"]),
     "Dictionary madja 'scrub, jungle, rain forest'.", "0.84"),
    (19, "The man killed a wallaby in the scrub.",
     "Dingkar-angka kadar kunin madja-nga.",
     "dingkar-angka kadar kuni-ny madja-nga | man-ERG wallaby kill-PAST scrub-LOC",
     "transitive_past_locative", 4, js(["dingkar", "kadar", "kunil", "madja"]),
     "Patz ex.277: man killed wallaby in maja/madja-nga 'scrub-LOC'.", "0.88"),
    (20, "The spring is close.",
     "Jirka ngundu.",
     "jirka ngundu | spring close.by",
     "nominal_location_word", 3, js(["jirka", "ngundu"]),
     "Dictionary jirka 'spring'; Patz §3.3 ngundu 'close by'.", "0.84"),
    (21, "The hole is in the ground.",
     "Janku bubu-ngu.",
     "janku bubu-ngu | hole ground-LOC",
     "nominal_locative_predicate", 3, js(["janku", "bubu"]),
     "Dictionary janku 'hole in the ground'; bubu-ngu locative attested in Patz text.", "0.86"),
    (22, "The flat rock is good.",
     "Jalngarr ngulkurr.",
     "jalngarr ngulkurr | flat.rock good",
     "nominal_np_adj", 3, js(["jalngarr", "ngulkurr"]),
     "Dictionary jalngarr 'flat rock'.", "0.84"),
    (23, "The stone is on the ground.",
     "Kulji bubu-ngu.",
     "kulji bubu-ngu | stone ground-LOC",
     "nominal_locative_predicate", 3, js(["kulji", "bubu"]),
     "Dictionary kulji 'stone/pebbles'; bubu-ngu locative from Patz text.", "0.85"),
    (24, "The sand is wet.",
     "Jukar duna.",
     "jukar duna | sand wet",
     "nominal_np_adj", 3, js(["jukar", "duna"]),
     "Dictionary jukar 'sand'; duna 'wet'.", "0.84"),
    (25, "The dog lies on the sand.",
     "Kaya jukar-a wunay.",
     "kaya jukar-a wuna-y | dog sand-LOC lie-NONPAST",
     "locative_posture_nonpast", 4, js(["kaya", "jukar", "wunay"]),
     "Rhotic locative guard: single-r jukar takes -a; posture verb wuna-y.", "0.86"),
    (26, "Water is good.",
     "Bana ngulkurr.",
     "bana ngulkurr | water good",
     "nominal_np_adj", 3, js(["bana", "ngulkurr"]),
     "Dictionary bana 'water'; nominal adjective predicate.", "0.84"),
    (27, "This is water for drinking.",
     "Yanya bana nuka-nka.",
     "yanya bana nuka-nka | this water drink-PURP",
     "attested_purposive_nominal", 4, js(["yanya", "bana", "nukal"]),
     "Patz ex.429a: Yanya bana nuka-nka 'This is drinking water'.", "0.90"),
    (28, "The girls bathe in the water.",
     "Maral-maral bana-nga janji-l.",
     "maral-maral bana-nga janji-l | girl-REDUP water-LOC bathe-NONPAST",
     "attested_locative_activity", 4, js(["maral", "bana", "janjil"]),
     "Patz ex.275: Maral-maral bana-nga janji-l.", "0.90"),
    (29, "The fish swam in the water.",
     "Kuyu bana-nga janjin.",
     "kuyu bana-nga janji-ny | fish water-LOC swim-PAST",
     "locative_activity_past", 4, js(["kuyu", "bana", "janjil"]),
     "Patz text l.6770: kuyu-lu bana-nga janji-ny 'fish was swimming in the water'.", "0.88"),
    (30, "The river is close.",
     "Wawubaja ngundu.",
     "wawubaja ngundu | river close.by",
     "nominal_location_word", 3, js(["wawubaja", "ngundu"]),
     "Dictionary wawubaja 'river'; Patz §3.3 ngundu 'close by'.", "0.84"),
    (31, "They swam through the river.",
     "Jana wawubaja-burr marijin.",
     "jana wawubaja-burr mariji-ny | they river-PERL swim-PAST",
     "attested_perlative_motion", 5, js(["jana", "wawubaja", "mariji-y"]),
     "Patz ex.291: Jana wawubaja-burr mariji-ny.", "0.90"),
    (32, "Women and children went to the river to get mussels.",
     "Jalbu-jalbu karrkay-karrkay jana wawubaja-nga dungan mukirr mani-nka.",
     "jalbu-jalbu karrkay-karrkay jana wawubaja-nga dunga-ny mukirr mani-nka | woman-REDUP child-REDUP they river-LOC go-PAST mussel get-PURP",
     "attested_participant_intro_motion_purpose", 5, js(["jalbu", "karrkay", "jana", "wawubaja", "dungay", "mukirr", "manil"]),
     "Patz ex.621: women/children went to river to get mussels.", "0.90"),
    (33, "The young man went to the river to spear fish.",
     "Warru dungan wawubaja-nga kuyu dama-nka.",
     "warru dunga-ny wawubaja-nga kuyu dama-nka | young.man go-PAST river-LOC fish spear-PURP",
     "attested_motion_purpose", 5, js(["warru", "dungay", "wawubaja", "kuyu", "damal"]),
     "Patz ex.406: Warru dunga-ny wawubaja-nga, kuyu dama-nka.", "0.90"),
    (34, "Two horses are running to the river.",
     "Yarraman jambul wawubaja-nga wanarri.",
     "yarraman jambul wawubaja-nga wanarri-y | horse two river-LOC run.REDUP-NONPAST",
     "attested_motion_to_river", 5, js(["yarraman", "jambul", "wawubaja", "warri"]),
     "Patz ex.67b: two horses running to the river.", "0.90"),
    (35, "He went down to the creek.",
     "Nyulu bada yilki-nga dungan.",
     "nyulu bada yilki-nga dunga-ny | he down creek-LOC go-PAST",
     "attested_directional_locative_motion", 5, js(["nyulu", "bada", "yilki", "dungay"]),
     "Patz text line 89/91: bada yilki-nga dunga-ny.", "0.90"),
    (36, "He went from the camp down to the creek.",
     "Bayan-muny nyulu bada yilki-nga dungan.",
     "bayan-muny nyulu bada yilki-nga dunga-ny | camp-ABL he down creek-LOC go-PAST",
     "attested_ablative_directional_motion", 5, js(["bayan", "nyulu", "bada", "yilki", "dungay"]),
     "Patz text line 90: Bayan-muny nyulu bada yilki-nga dunga-ny.", "0.90"),
    (37, "Do not go into deep water; that is deep water.",
     "Kari-ku dunga-y dajali-mun-bu, yinya dajali.",
     "kari-ku dunga-y dajali-mun-bu, yinya dajali | NEG-EMPH go-IMP deep.water-mun-LOC, that deep.water",
     "attested_deep_water_prohibitive", 5, js(["kari", "dungay", "dajali", "yinya"]),
     "Patz text line 32: don't go into dajali-mun-bu; yinya dajali.", "0.90"),
    (38, "Come close into the shallow water.",
     "Ngundu kada-y yalkan-ba!",
     "ngundu kada-y yalkan-ba | close come-IMP shallow.water-LOC",
     "attested_shallow_water_imperative", 5, js(["ngundu", "kaday", "yalkan"]),
     "Patz text line 33: Ngundu kada-y yalkan-ba.", "0.90"),
    (39, "That fish is from the sea.",
     "Yinya kuyu jalun-muny.",
     "yinya kuyu jalun-muny | that fish sea-ABL",
     "attested_ablative_nominal", 4, js(["yinya", "kuyu", "jalun"]),
     "Patz ex.299: Yinya kuyu jalun-muny.", "0.90"),
    (40, "The sea is big.",
     "Jalun yalbay.",
     "jalun yalbay | sea big",
     "nominal_np_adj", 3, js(["jalun", "yalbay"]),
     "Dictionary jalun 'sea, large body of water'.", "0.84"),
    (41, "The reef is in the sea.",
     "Dulngku jalun-bu.",
     "dulngku jalun-bu | reef sea-LOC",
     "nominal_locative_predicate", 4, js(["dulngku", "jalun"]),
     "Dictionary dulngku 'reef'; Patz §3.3 notes jalun-bu 'sea-LOC'.", "0.86"),
    (42, "The high tide is big.",
     "Balar yalbay.",
     "balar yalbay | high.tide big",
     "nominal_np_adj", 3, js(["balar", "yalbay"]),
     "Dictionary balar 'high tide'.", "0.84"),
    (43, "The low tide is at the reef.",
     "Ngajal dulngku-ngu.",
     "ngajal dulngku-ngu | low.tide reef-LOC",
     "nominal_locative_predicate", 4, js(["ngajal", "dulngku"]),
     "Dictionary ngajal 'low tide', dulngku 'reef'; u-final locative -ngu.", "0.84"),
    (44, "The tide is strong.",
     "Yira junkurr.",
     "yira junkurr | tide strong",
     "nominal_np_adj", 3, js(["yira", "junkurr"]),
     "Dictionary yira 'tide'; junkurr 'strong'.", "0.84"),
    (45, "The child stands nearby.",
     "Karrkay ngundu janay.",
     "karrkay ngundu jana-y | child close.by stand-NONPAST",
     "attested_location_word_posture", 4, js(["karrkay", "ngundu", "janay"]),
     "Patz ex.107/279: ngundu jana-y 'stand close by'.", "0.88"),
    (46, "He came from the other side of the river.",
     "Nyulu kadan ngubar-muny.",
     "nyulu kada-ny ngubar-muny | he come-PAST other.side.of.river-ABL",
     "attested_location_word_ablative", 5, js(["nyulu", "kaday", "ngubar"]),
     "Patz ex.108: Nyulu kada-ny ngubar-muny.", "0.90"),
    (47, "People are calling out from down there.",
     "Bama yirrka-n-yirrka-y badabada-mundu.",
     "bama yirrka-n-yirrka-y badabada-mundu | person call-REDUP-NONPAST down.there-ABESS",
     "attested_location_word_abessive", 5, js(["bama", "yirrkay", "badabada"]),
     "Patz ex.110: Bama yirrka-n-yirrka-y badabada-mundu.", "0.90"),
    (48, "He is behind the camp.",
     "Nyulu kudamundu bayan-ba.",
     "nyulu kudamundu bayan-ba | he behind camp-LOC",
     "location_word_with_locative_np", 4, js(["nyulu", "kudamundu", "bayan"]),
     "Dictionary kudamundu example: Nyulu kudamundu bayan-ba.", "0.88"),
    (49, "The old woman followed from behind.",
     "Wulbuman-angka nyunguny wukurrin kudamundu.",
     "wulbuman-angka nyunguny wukurri-ny kudamundu | old.woman-ERG him.ACC follow-PAST behind",
     "attested_location_word_following", 5, js(["wulbuman", "nyunguny", "wukurril", "kudamundu"]),
     "Patz ex.111: old woman followed him behind/from behind.", "0.90"),
    (50, "The two looked up that way.",
     "Bula yarra wangkar nyajin.",
     "bula yarra wangkar nyaji-ny | they.two that.way up see-PAST",
     "attested_directional_perception", 5, js(["bula", "yarra", "wangkar", "nyajil"]),
     "Patz text line 14: Bula yarra wangkar nyaji-ny.", "0.90"),
]


def scan() -> list[str]:
    issues: list[str] = []
    if len(ROWS) != 50:
        issues.append(f"expected 50 rows, got {len(ROWS)}")
    seqs = [r[0] for r in ROWS]
    if seqs != list(range(1, 51)):
        issues.append(f"seqs must be 1..50, got {seqs[:5]}...{seqs[-5:]}")
    seen_ku: set[str] = set()
    for row in ROWS:
        seq, english, kuku, analysis, frame, tier, words_used, evidence, confidence = row
        label = f"{LABEL}.{seq:02d}"
        if not english[:1].isupper() or english.rstrip("'\"").rstrip()[-1:] not in ".!?":
            issues.append(f"{label}: English must be capitalized and final-punctuated")
        if " | " not in analysis:
            issues.append(f"{label}: analysis missing gloss separator")
        if not kuku[:1].isupper():
            issues.append(f"{label}: Kuku surface must start uppercase")
        try:
            json.loads(words_used)
        except Exception as exc:
            issues.append(f"{label}: words_used is not JSON: {exc}")
        low = f" {kuku.lower()} {analysis.lower()} "
        for word in DENY:
            if re.search(rf"(?<![a-z]){re.escape(word)}(?![a-z])", low):
                issues.append(f"{label}: denied phantom/wrong-sense token {word!r}")
        if kuku.lower() in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface within batch")
        seen_ku.add(kuku.lower())
    return issues


def main() -> int:
    issues = scan()
    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")
    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 85 draft: landscape and geography lexical field", "operator-guide-2026-07-07", "under_review", NOW),
        )
        batch_id = cur.lastrowid

    db.executemany(
        """
        INSERT INTO sentences(
            batch_id, seq, english, kuku, analysis, frame, tier, words_used,
            loans_used, evidence, status, confidence, rights_status, author,
            created_at, updated_at
        ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
        """,
        [
            (
                batch_id, seq, english, kuku, analysis, frame, tier, words_used,
                "[]", evidence, "under_review", confidence,
                "project_approved_synthetic_pending_elder_verification",
                "claude-main", NOW, NOW,
            )
            for seq, english, kuku, analysis, frame, tier, words_used, evidence, confidence in ROWS
        ],
    )
    db.commit()
    count = db.execute("SELECT COUNT(*) FROM sentences WHERE batch_id=?", (batch_id,)).fetchone()[0]
    avg_words = db.execute(
        "SELECT AVG(LENGTH(kuku)-LENGTH(REPLACE(kuku,' ',''))+1.0) FROM sentences WHERE batch_id=?",
        (batch_id,),
    ).fetchone()[0]
    print(f"banked {LABEL}: {count} rows under_review; avg Kuku words/row={avg_words:.2f}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
