#!/usr/bin/env python3
"""Bank batch-145: physical properties and material qualities."""

from __future__ import annotations

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

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

DENY = {
    "nyawa", "jinbi", "jibin", "walba", "julmbu", "wanan", "ngampula",
    "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", "balbay-nja", "bunjurril", "buri",
    "ngara", "rrunyuji", "murri", "warmba", "dukal", "kalbali",
    "medicine", "hospital", "answer", "wubul", "niji", "kija",
    "warral", "jinbal", "jindal", "balbal", "buyun", "wungku",
    "green", "blue", "brown", "purple",
}


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


ROWS = [
    (1, "The stone is big.",
     "Kulji yalbay.",
     "kulji yalbay | stone big",
     "physical_property_size_big", 3, js(["kulji", "yalbay"]), js([]),
     "Dictionary yalbay = big; Patz has repeated kulji yalbay rock-big examples.", "0.88"),
    (2, "I have only a little food.",
     "Ngayu mayi buban wunay.",
     "ngayu mayi buban wuna-y | I food little lie-NONPAST",
     "physical_property_quantity_little", 4, js(["ngayu", "mayi", "buban", "wunay"]), js([]),
     "Dictionary buban = small amount/short time; modeled on ngayu money buban wunay.", "0.84"),
    (3, "The stick is long.",
     "Juku kalbay.",
     "juku kalbay | stick long",
     "physical_property_dimension_long", 3, js(["juku", "kalbay"]), js([]),
     "Dictionary kalbay = long; row avoids exact duplicate Kalka kalbay.", "0.86"),
    (4, "The beach is wide.",
     "Jukar yalmbar.",
     "jukar yalmbar | beach wide",
     "physical_property_dimension_wide", 3, js(["jukar", "yalmbar"]), js([]),
     "Dictionary yalmbar = wide; jukar is sand/beach/shore in living lexicon.", "0.83"),
    (5, "The road is narrow.",
     "Baral darra.",
     "baral darra | road narrow",
     "physical_property_dimension_narrow", 3, js(["baral", "darra"]), js([]),
     "Dictionary darra = narrow/too small; source example has baral darra bajaku.", "0.88"),
    (6, "The ground is flat.",
     "Bubu dubar.",
     "bubu dubar | ground flat/level",
     "physical_property_land_flat", 3, js(["bubu", "dubar"]), js([]),
     "Dictionary dubar = level/flat place, specifically of ground/place.", "0.86"),
    (7, "The fruit is low and within reach.",
     "Mayi kambal.",
     "mayi kambal | food/fruit low.within.reach",
     "physical_property_position_low", 4, js(["mayi", "kambal"]), js([]),
     "Dictionary kambal = low level/within reach; source example Mayi kambalmun mana.", "0.83"),
    (8, "That is deep water.",
     "Yinya dajali.",
     "yinya dajali | that deep.water",
     "physical_property_water_deep", 3, js(["yinya", "dajali"]), js([]),
     "Dictionary dajali = deep water; Patz appendix controls yinya dajali.", "0.90"),
    (9, "That is shallow water.",
     "Yinya yalkan.",
     "yinya yalkan | that shallow.water",
     "physical_property_water_shallow", 3, js(["yinya", "yalkan"]), js([]),
     "Dictionary yalkan = shallow water; Patz appendix controls yalkan-ba.", "0.86"),
    (10, "The stick is bent.",
     "Juku duku.",
     "juku duku | stick bent",
     "physical_property_shape_bent", 4, js(["juku", "duku"]), js([]),
     "Dictionary duku = bent; evidence example contains muku-duku hunchback/bent-back.", "0.79"),

    (11, "The bark is hard.",
     "Dumbul dandi.",
     "dumbul dandi | bark hard",
     "physical_property_texture_hard", 3, js(["dumbul", "dandi"]), js([]),
     "Dictionary dandi = hard/tough; dumbul is bark/crust/shell hard covering.", "0.86"),
    (12, "The sand is soft.",
     "Jukar kima.",
     "jukar kima | sand soft",
     "physical_property_texture_soft", 3, js(["jukar", "kima"]), js([]),
     "Dictionary kima = soft/weak/sick; Patz comparison uses jarra-kima for soft meat.", "0.84"),
    (13, "The turtle is heavy.",
     "Ngawiya kulngkul.",
     "ngawiya kulngkul | turtle heavy",
     "physical_property_weight_heavy", 3, js(["ngawiya", "kulngkul"]), js([]),
     "Dictionary kulngkul = heavy/pregnant; errata blocks niji as heavy.", "0.86"),
    (14, "The bark is wet.",
     "Dumbul duna.",
     "dumbul duna | bark wet",
     "physical_property_state_wet", 3, js(["dumbul", "duna"]), js([]),
     "Dictionary duna = wet; Patz body-part example also uses duna as wet predicate.", "0.85"),
    (15, "The grass is dry.",
     "Jikan marral.",
     "jikan marral | grass dry",
     "physical_property_state_dry", 3, js(["jikan", "marral"]), js([]),
     "Dictionary marral = dry; Patz appendix controls bubu marral-da.", "0.85"),
    (16, "The road is slippery.",
     "Baral julbarr.",
     "baral julbarr | road slippery",
     "physical_property_texture_slippery", 3, js(["baral", "julbarr"]), js([]),
     "Dictionary julbarr = slippery; Patz has wet roads/ground slippery.", "0.86"),
    (17, "The stone is smooth.",
     "Kulji dirka-dirka.",
     "kulji dirka-dirka | stone smooth",
     "physical_property_texture_smooth", 4, js(["kulji", "dirka-dirka"]), js([]),
     "Dictionary dirka-dirka = smooth, especially rock/surface contexts.", "0.84"),
    (18, "The bark is rough.",
     "Dubal murru-murru.",
     "dubal murru-murru | bark rough/lumpy",
     "physical_property_texture_rough", 4, js(["dubal", "murru-murru"]), js([]),
     "Patz NP-modifier example gives dubal murru-murru 'rough bark'.", "0.91"),
    (19, "The clothes are dirty.",
     "Kambi mumbu.",
     "kambi mumbu | clothes dirty",
     "physical_property_cleanliness_dirty", 3, js(["kambi", "mumbu"]), js([]),
     "Patz ex.169 has kambi mumbu-bunga-ny; dictionary adds mumbu from grammar source.", "0.86"),
    (20, "The water is clear.",
     "Bana dayirr.",
     "bana dayirr | water clear/clean",
     "physical_property_cleanliness_clear", 3, js(["bana", "dayirr"]), js([]),
     "Dictionary dayirr = clean/clear/bright; water-clear use keeps the sense bounded.", "0.85"),
    (21, "The fire is hot.",
     "Baya wumbul.",
     "baya wumbul | fire hot",
     "physical_property_temperature_hot", 3, js(["baya", "wumbul"]), js([]),
     "Dictionary wumbul = hot; Patz has wumbul-bunga-l and wumbul-ma-l rows.", "0.86"),
    (22, "The morning is cold.",
     "Wunkuwingku kiway-kiway.",
     "wunkuwingku kiway-kiway | morning cold-REDUP",
     "physical_property_temperature_cold", 4, js(["wunkuwingku", "kiway-kiway"]), js([]),
     "Dictionary kiway = cold and kiway-kiway = cold adjective; living docs block kija as cold.", "0.84"),
    (23, "The food is burnt.",
     "Mayi jirra.",
     "mayi jirra | food burnt",
     "physical_property_food_burnt", 3, js(["mayi", "jirra"]), js([]),
     "Dictionary jirra = burnt as food/crust; Patz ex.170 controls food overcooked/burnt.", "0.88"),
    (24, "The fruit is ripe.",
     "Mayi kulbul.",
     "mayi kulbul | food/fruit ripe",
     "physical_property_food_ripe", 3, js(["mayi", "kulbul"]), js([]),
     "Dictionary kulbul = ripe; Patz comparison has jarra-kulbul-bu 'rather ripe'.", "0.85"),
    (25, "The fish is fat.",
     "Kuyu wandul.",
     "kuyu wandul | fish fat",
     "physical_property_body_fat", 3, js(["kuyu", "wandul"]), js([]),
     "Dictionary wandul = fat; kuyu fish is a stable corpus noun.", "0.83"),

    (26, "The stone is black.",
     "Kulji ngumbu.",
     "kulji ngumbu | stone black/dark",
     "physical_property_colour_dark", 3, js(["kulji", "ngumbu"]), js([]),
     "Grammar colour inventory and dictionary: ngumbu = dark/black/charcoal.", "0.86"),
    (27, "The flower is red.",
     "Nganka ngala-ngala.",
     "nganka ngala-ngala | flower red-REDUP",
     "physical_property_colour_red", 3, js(["nganka", "ngala-ngala"]), js([]),
     "Grammar colour inventory and dictionary: ngala-ngala = red/reddish in Yalanji.", "0.87"),
    (28, "The hair is light-coloured.",
     "Mungka bingaji.",
     "mungka bingaji | hair light.coloured",
     "physical_property_colour_light", 4, js(["mungka", "bingaji"]), js([]),
     "Grammar colour inventory and dictionary: bingaji = light/white coloured; homonymous with white-haired person.", "0.84"),
    (29, "The clothes are very clean.",
     "Kambi dayirr bajaku.",
     "kambi dayirr bajaku | clothes clean very",
     "physical_property_cleanliness_clean_emphatic", 4, js(["kambi", "dayirr", "bajaku"]), js([]),
     "Dictionary dayirr usage: julurrinyamun kambi dayirr bajaku after washing clothes are very clean.", "0.90"),
    (30, "The food is good.",
     "Mayi ngulkurr.",
     "mayi ngulkurr | food good",
     "physical_property_value_good", 3, js(["mayi", "ngulkurr"]), js([]),
     "Patz ex.295 controls mayi ngulkurr as good food/food is good.", "0.88"),
    (31, "The food is no good.",
     "Mayi nganjay.",
     "mayi nganjay | food no.good",
     "physical_property_value_no_good", 3, js(["mayi", "nganjay"]), js([]),
     "Dictionary nganjay = no good/wilted/bad smell; usage Mayi nganjay kari nuka.", "0.86"),
    (32, "That food is distasteful.",
     "Yinya mayi nganja-burra.",
     "yinya mayi nganja-burra | that food bad.taste",
     "physical_property_taste_bad", 4, js(["yinya", "mayi", "nganja-burra"]), js([]),
     "Dictionary nganja-burra = bad taste/distasteful; demonstrative avoids duplicate Batch 139 surface.", "0.85"),
    (33, "The flower is beautiful.",
     "Nganka ngulkurrijin.",
     "nganka ngulkurrijin | flower beautiful",
     "physical_property_value_beautiful", 3, js(["nganka", "ngulkurrijin"]), js([]),
     "Dictionary ngulkurrijin = pretty/beautiful; usage Yinya nganka ngulkurrijin.", "0.88"),
    (34, "The child is skinny.",
     "Karrkay bala.",
     "karrkay bala | child skinny",
     "physical_property_body_skinny", 4, js(["karrkay", "bala"]), js([]),
     "Dictionary bala adjective = skinny/weak from sickness or not enough food; homonymous sugar glider fenced by context.", "0.80"),
    (35, "The man is strong.",
     "Dingkar yaykarr.",
     "dingkar yaykarr | man strong",
     "physical_property_body_strong", 3, js(["dingkar", "yaykarr"]), js([]),
     "Dictionary yaykarr = strong/hard/swift; errata prefers yaykarr over noun junkurr for strong adjective.", "0.84"),
    (36, "The knife is sharp.",
     "Naybu jalngkun.",
     "naybu jalngkun | knife sharp",
     "physical_property_edge_sharp", 3, js(["naybu", "jalngkun"]), js(["naybu"]),
     "Dictionary jalngkun = sharp; dudu entry contrasts Yinya naybu dudu, yundu jalngkun-bunga.", "0.88"),
    (37, "The spear is blunt.",
     "Kalka dudu.",
     "kalka dudu | spear blunt",
     "physical_property_edge_blunt", 3, js(["kalka", "dudu"]), js([]),
     "Patz ex.300 controls Nyungu kalka dudu; dictionary dudu = blunt edge/point.", "0.90"),
    (38, "The stick is new.",
     "Juku jirakal.",
     "juku jirakal | stick new",
     "physical_property_age_new", 3, js(["juku", "jirakal"]), js([]),
     "Dictionary jirakal = new; Patz NP example has kalka jirakal.", "0.84"),
    (39, "The clothes are too small.",
     "Kambi darra bajaku.",
     "kambi darra bajaku | clothes too.small very",
     "physical_property_size_too_small", 4, js(["kambi", "darra", "bajaku"]), js([]),
     "Dictionary darra usage Kambi ngaykunka darra bajaku 'the dress is too small for me'.", "0.88"),
    (40, "The axe is blunt.",
     "Warrbi dudu.",
     "warrbi dudu | axe blunt",
     "physical_property_edge_blunt_tool", 3, js(["warrbi", "dudu"]), js([]),
     "Dictionary dudu = blunt; warrbi is the established axe/tool noun in the corpus.", "0.83"),

    (41, "The wood became dry.",
     "Juku marralman.",
     "juku marral-ma-ny | wood dry-INCHO-PAST",
     "physical_property_inchoative_dry", 5, js(["juku", "marral"]), js([]),
     "Patz productive -ma-l inchoative from adjectives; dictionary marral = dry.", "0.83"),
    (42, "The water became hot.",
     "Bana wumbulman.",
     "bana wumbul-ma-ny | water hot-INCHO-PAST",
     "physical_property_inchoative_hot", 5, js(["bana", "wumbul"]), js([]),
     "Patz wumbul-ma-l warm/become hot pattern; dictionary wumbul = hot.", "0.84"),
    (43, "The water became cold.",
     "Bana kiwayman.",
     "bana kiway-ma-ny | water cold-INCHO-PAST",
     "physical_property_inchoative_cold", 5, js(["bana", "kiway"]), js([]),
     "Living docs license kiway-ma-l become cold; kija is blocked for cold.", "0.84"),
    (44, "The stone became hard.",
     "Kulji dandiman.",
     "kulji dandi-ma-ny | stone hard-INCHO-PAST",
     "physical_property_inchoative_hard", 5, js(["kulji", "dandi"]), js([]),
     "Patz inchoative table controls dandi-ma-l become hard.", "0.88"),
    (45, "The bark became soft.",
     "Dumbul kimaman.",
     "dumbul kima-ma-ny | bark soft-INCHO-PAST",
     "physical_property_inchoative_soft", 5, js(["dumbul", "kima"]), js([]),
     "Patz inchoative section cites kima-ma-l-kima-ma-l; dictionary kima = soft.", "0.84"),
    (46, "Mother made the clothes dirty.",
     "Ngamu-ngku kambi mumbu-bungan.",
     "ngamu-ngku kambi mumbu-bunga-ny | mother-ERG clothes dirty-CAUS-PAST",
     "physical_property_causative_dirty_clothes", 5, js(["ngamu", "kambi", "mumbu"]), js([]),
     "Patz ex.169a controls kambi mumbu-bunga-ny for making clothes dirty.", "0.90"),
    (47, "The child muddied the water.",
     "Karrkay-ngka bana mumbu-kangan.",
     "karrkay-ngka bana mumbu-kanga-ny | child-ERG water dirty-CAUS-PAST",
     "physical_property_causative_dirty_water", 5, js(["karrkay", "bana", "mumbu"]), js([]),
     "Patz ex.169b controls bana mumbu-kanga-ny for making water dirty by disturbance.", "0.91"),
    (48, "Grandmother softened the food.",
     "Babi-ngka mayi kima-bungan.",
     "babi-ngka mayi kima-bunga-ny | grandmother-ERG food soft-CAUS-PAST",
     "physical_property_causative_soften_food", 5, js(["babi", "mayi", "kima"]), js([]),
     "Patz state-causative section licenses kima-bunga-l make soft.", "0.84"),
    (49, "The old man hardened the stick.",
     "Mukul-dingkar-angka juku dandi-bungan.",
     "mukul-dingkar-angka juku dandi-bunga-ny | old-man-ERG stick hard-CAUS-PAST",
     "physical_property_causative_harden_stick", 5, js(["mukul-dingkar", "juku", "dandi"]), js([]),
     "Patz state-causative table controls dandi-bunga-l make hard.", "0.86"),
    (50, "The girl overcooked the food.",
     "Maral-angka mayi jirra-bungan.",
     "maral-angka mayi jirra-bunga-ny | girl-ERG food burnt/overdone-CAUS-PAST",
     "physical_property_causative_overcook_food", 5, js(["maral", "mayi", "jirra"]), js([]),
     "Patz ex.170a controls Maral-angka mayi jirra-bunga-ny.", "0.92"),
]


def scan_static() -> 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, loans_used, _evidence, _confidence = row
        label = f"{LABEL}.{seq:02d}"
        e_start = english.lstrip("'\"")
        e_end = english.rstrip("'\"").rstrip()
        if not e_start[:1].isupper() or e_end[-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")
        k_start = kuku.lstrip("'\"")
        if not k_start[:1].isupper():
            issues.append(f"{label}: Kuku surface must start uppercase or with a quote before uppercase")
        for field_name, payload in (("words_used", words_used), ("loans_used", loans_used)):
            try:
                json.loads(payload)
            except Exception as exc:
                issues.append(f"{label}: {field_name} 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/blocked 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_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 103 draft: physical properties and material qualities", "operator-guide-2026-07-07", "under_review", NOW),
        )
        batch_id = cur.lastrowid

    dupes = []
    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 103 source pass checked Patz adjective inventory, nominal/adjectival predicates, productive -ma-l "
            "inchoatives, state-causative -bunga-l/-kanga-l, water-depth examples dajali/yalkan, rough-bark dubal "
            "murru-murru, and dictionary entries for 38 physical-property lexemes."
        ),
        command="rg/sed over grammar.md, dictionary.yaml, living books, plus sqlite duplicate scan",
        artifact_path="process_logs/batch-145-physical-properties-process.md",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="scope",
        event_type="risk-controls",
        actor="claude-main",
        detail=(
            "Rejected or fenced off risky forms: no niji heavy, no kija cold, no invented green/blue/brown/purple, "
            "no warral colour/shape shortcut, no jinbal sharp, no jindal straight/right, no wungku sun row, no bare "
            "buyun general-quality row, and no generic deep/shallow adjective beyond dajali/yalkan water expressions."
        ),
        artifact_path="process_logs/batch-145-physical-properties-process.md",
        created_at=NOW,
    )
    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,
                loans_used, evidence, "under_review", confidence,
                "project_approved_synthetic_pending_elder_verification",
                "claude-main", NOW, NOW,
            )
            for seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence, confidence in ROWS
        ],
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="bank-success",
        actor="claude-main",
        detail="Banked 50 hand-authored Rule 103 physical-property/material-quality lexical rows under_review after static and duplicate checks.",
        command="python3 batch_scripts/bank_145.py",
        artifact_path="batch_scripts/bank_145.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="row-inventory",
        actor="claude-main",
        detail=(
            "Rows 1-10 size/dimension/depth/shape; 11-25 material and state qualities; 26-40 colour/value/body/edge/age "
            "qualities; 41-50 property inchoatives and causatives including mumbu-bunga, mumbu-kanga, and jirra-bunga."
        ),
        artifact_path="process_logs/batch-145-physical-properties-process.md",
        created_at=NOW,
    )
    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())
