#!/usr/bin/env python3
"""Close Batch 391 after an explicit five-lens review."""

from __future__ import annotations

import datetime as dt
import json
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-391"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-391-complementary-bird-set-d-nonsingular-past-perception-after-rule348-process.md"
LENSES = ("morphology", "lexicon", "syntax", "fidelity", "register")
NOTES = (
    "COMPLEMENTARY BIRD SET-D NON-SINGULAR PAST PERCEPTION AFTER RULE 348 (Rule 349). 50 accepted; "
    "candidates empty and opposites exactly Batch 390; fresh target/Patz and pronoun audits passed; "
    "all lexical/source boundaries closed; full 100-cell paradigm complete; gap 162->172."
)
LESSONS = [
    ("source", "Dictionary and Patz hashes remained exact at the Rule 349 audit.", "Rule 349: hashes."),
    ("source", "Every bird target retained exactly two dictionary cards.", "Rule 349: card inventory."),
    ("source", "Patz exact hits remained wakuka three and all other targets zero without negative lexical inference.", "Rule 349: Patz profile."),
    ("source", "Patz controls ngali exclusive and ngaliny inclusive over the dictionary defect.", "Rule 349: clusivity."),
    ("source", "Ngana remains grammatical first-plural inclusive but was deferred at 1,115 uses.", "Rule 349: ngana."),
    ("process", "All 50 Rule 349 candidates were absent before banking.", "Rule 349: candidate map."),
    ("process", "Every Rule 349 opposite was exactly Batch 390 at the same sequence.", "Rule 349: complement map."),
    ("process", "The full 100-cell Rule 348-349 non-singular bird paradigm is now accounted for.", "Rule 349: paradigm."),
    ("process", "Completion changes the deictic gap 162 to 172; the apparent 152 inverse duplicates Rule 348.", "Rule 349: balance."),
    ("lexicon", "Diwalngku remained a generic bird; taxonomy and migration claims stayed closed.", "Rule 349: diwalngku."),
    ("lexicon", "Bujil-yiran remained ibis; its empty cognition/fear card was not collapsed into the bird sense.", "Rule 349: bujil-yiran."),
    ("lexicon", "Karrmbil remained generic parakeet; blue-mountain/rainbow-lorikeet resolution stayed closed.", "Rule 349: karrmbil."),
    ("lexicon", "Yiki remained generic owl; species, night behavior, sound and hunting stayed closed.", "Rule 349: yiki."),
    ("lexicon", "Bajal remained bower bird; sex, habitat and bower behavior stayed closed.", "Rule 349: bajal."),
    ("lexicon", "Biru-biru remained the exact whole rainbow-bird head; free base and productive reduplication stayed closed.", "Rule 349: biru-biru."),
    ("lexicon", "Jangkan remained swift bird; speed, flight and season claims stayed closed.", "Rule 349: jangkan."),
    ("lexicon", "Kurranday remained seagull; habitat, warning, sound and feeding claims stayed closed.", "Rule 349: kurranday."),
    ("lexicon", "Ngarkay remained waterhen; habitat, movement, feeding and hunting stayed closed.", "Rule 349: ngarkay."),
    ("lexicon", "Wakuka remained generic bird; crow/kookaburra resolution, messenger belief and station name stayed closed.", "Rule 349: wakuka."),
    ("morphology", "All bird targets remained bare absolutive objects without case or derivation.", "Rule 349: object form."),
    ("morphology", "Bujil-yiran and biru-biru remained indivisible dictionary heads.", "Rule 349: whole heads."),
    ("morphology", "Ngali, ngaliny, yubal, yurra and nganjin remained bare nominative agents.", "Rule 349: agent form."),
    ("morphology", "Yaluy, yinyay and past nyajin remained fixed and uninflected.", "Rule 349: support forms."),
    ("syntax", "Rows 1-10 used Ngali TARGET yinyay nyajin with exclusive dual reference.", "Rule 349: band 1."),
    ("syntax", "Rows 11-20 used Ngaliny TARGET yaluy nyajin with inclusive dual reference.", "Rule 349: band 2."),
    ("syntax", "Rows 21-30 used Yubal TARGET yinyay nyajin with second dual reference.", "Rule 349: band 3."),
    ("syntax", "Rows 31-40 used Yurra TARGET yaluy nyajin with second plural reference.", "Rule 349: band 4."),
    ("syntax", "Rows 41-50 used Nganjin TARGET yinyay nyajin with exclusive first plural reference.", "Rule 349: band 5."),
    ("fidelity", "English preserved participant number and clusivity, conservative bird label, location and past perception only.", "Rule 349: fidelity."),
    ("fidelity", "No taxonomy, culture, property, habitat, place, source, possession or event proposition entered English.", "Rule 349: exclusions."),
    ("register", "All rows remained neutral elicitation clauses without imported narrative context.", "Rule 349: register."),
    ("process", "AST verification proved all 50 corpus rows were direct literal row calls.", "Rule 349: authorship."),
    ("process", "Whole-corpus lint, duplicate, integrity, export and publication gates remain mandatory after close.", "Rule 349: gates."),
    ("process", "Source, row, review and publication evidence remains on the mounted research drive.", "Rule 349: storage."),
    ("handoff", "Continue from 19,647 only after selecting and auditing a genuinely fresh lexical set.", "Rule 349: handoff."),
]
LEXEMES = [
    ("diwalngku", "noun", None, "NA", "bird", "batch-391: complementary non-singular generic bird; taxonomy boundaries retained."),
    ("bujil-yiran", "noun", None, "NA", "ibis", "batch-391: complementary ibis; cognition/card boundaries retained."),
    ("karrmbil", "noun", None, "NA", "parakeet", "batch-391: complementary generic parakeet; taxonomy boundaries retained."),
    ("yiki", "noun", None, "NA", "owl", "batch-391: complementary generic owl; species/behavior boundaries retained."),
    ("bajal", "noun", None, "NA", "bower bird", "batch-391: complementary bower bird; behavior boundaries retained."),
    ("biru-biru", "noun", None, "NA", "rainbow bird", "batch-391: complementary exact whole head; reduplication boundaries retained."),
    ("jangkan", "noun", None, "NA", "swift bird", "batch-391: complementary swift bird; property boundaries retained."),
    ("kurranday", "noun", None, "NA", "seagull", "batch-391: complementary seagull; habitat/warning boundaries retained."),
    ("ngarkay", "noun", None, "NA", "waterhen", "batch-391: complementary waterhen; habitat/movement boundaries retained."),
    ("wakuka", "noun", None, "NA", "bird", "batch-391: complementary generic bird; taxonomy/belief/place boundaries retained."),
    ("ngali", "pronoun", None, "NA", "first dual exclusive nominative", "batch-391: Patz-controlled bare agent."),
    ("ngaliny", "pronoun", None, "NA", "first dual inclusive nominative", "batch-391: Patz-controlled bare agent."),
    ("yubal", "pronoun", None, "NA", "second dual nominative", "batch-391: bare agent."),
    ("yurra", "pronoun", None, "NA", "second plural nominative", "batch-391: bare agent."),
    ("nganjin", "pronoun", None, "NA", "first plural exclusive nominative", "batch-391: bare agent; distinct from ngana."),
    ("yaluy", "demonstrative", None, "NA", "here", "batch-391: Yalanji local demonstrative."),
    ("yinyay", "demonstrative", None, "NA", "there", "batch-391: Yalanji local demonstrative."),
    ("nyajin", "verb-tr", "l", "NA", "see; see.PST", "batch-391: running past perception surface."),
]


def main() -> int:
    if len(LESSONS) != 35 or len(LEXEMES) != 18:
        raise SystemExit(f"inventory {len(LESSONS)}/{len(LEXEMES)}")
    db = sqlite3.connect(DB)
    db.execute("PRAGMA foreign_keys=ON")
    db.execute("BEGIN")
    ensure_schema(db)
    batch = db.execute("SELECT id FROM batches WHERE label=?", (LABEL,)).fetchone()
    if not batch:
        raise SystemExit("batch not banked")
    batch_id = batch[0]
    counts = db.execute(
        """SELECT COUNT(*),COUNT(*) FILTER(WHERE status='under_review'),
                  COUNT(*) FILTER(WHERE status IN('accepted','revised'))
           FROM sentences WHERE batch_id=?""", (batch_id,),
    ).fetchone()
    if counts != (50, 50, 0):
        raise SystemExit(f"precheck {counts}")
    if db.execute("SELECT COUNT(*) FROM reviews WHERE sentence_id IN(SELECT id FROM sentences WHERE batch_id=?)", (batch_id,)).fetchone()[0]:
        raise SystemExit("reviews already exist")
    if db.execute("SELECT COUNT(*) FROM lessons WHERE batch_id=?", (batch_id,)).fetchone()[0]:
        raise SystemExit("lessons already exist")
    if db.execute("SELECT COUNT(*) FROM lexeme_log WHERE action='batch-391-verify'").fetchone()[0]:
        raise SystemExit("lexeme logs already exist")
    total = db.execute("SELECT COUNT(*) FROM sentences WHERE status!='rejected'").fetchone()[0]
    before, words_before = db.execute(
        """SELECT COUNT(*),SUM(LENGTH(kuku)-LENGTH(REPLACE(kuku,' ',''))+1)
           FROM sentences WHERE status IN('accepted','revised')"""
    ).fetchone()
    if (total, before, words_before) != (19647, 19597, 133747):
        raise SystemExit(f"baseline {total}/{before}/{words_before}")
    sentence_ids = [row[0] for row in db.execute("SELECT id FROM sentences WHERE batch_id=? ORDER BY seq", (batch_id,))]
    db.executemany(
        """INSERT INTO reviews(sentence_id,round,lens,verdict,issues,suggested_fix,reviewer,created_at)
           VALUES(?,?,?,?,?,?,?,?)""",
        [(sentence_id, 1, lens, "pass", None, None, "codex-main-five-lens", NOW) for sentence_id in sentence_ids for lens in LENSES],
    )
    db.executemany(
        "INSERT INTO lessons(batch_id,category,lesson,guide_change,created_at) VALUES(?,?,?,?,?)",
        [(batch_id, category, lesson, guide_change, NOW) for category, lesson, guide_change in LESSONS],
    )
    for headword, pos, conjugation, case_class, gloss, note in LEXEMES:
        senses = json.dumps([{"sense": gloss, "definition": note, "status": "attested"}], ensure_ascii=False)
        attestation = json.dumps([{"source": LABEL, "ref": "five-lens close", "quote": note}], ensure_ascii=False)
        db.execute(
            """INSERT INTO lexemes(
                 headword,phonemic,pos,conj_class,case_class,gloss,senses,attestation,allomorphy,morphology,
                 collocations,usage_notes,antonyms_contrasts,corpus_freq,status,verified_at,notes
               ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
               ON CONFLICT(headword) DO UPDATE SET
                 verified_at=excluded.verified_at,status=excluded.status,
                 notes=CASE WHEN lexemes.notes IS NULL OR lexemes.notes='' THEN excluded.notes
                            WHEN instr(lexemes.notes,?)>0 THEN lexemes.notes
                            ELSE lexemes.notes||'; '||excluded.notes END""",
            (headword, None, pos, conjugation, case_class, gloss, senses, attestation, None, None, None, note,
             None, 0, "verified", NOW, note, LABEL),
        )
        db.execute(
            "INSERT INTO lexeme_log(headword,action,detail,created_at) VALUES(?,?,?,?)",
            (headword, "batch-391-verify", note, NOW),
        )
    events = [
        ("lint", "under-review-lint-green", f"Lint {total}/0; verified {before}."),
        ("review", "five-lens-pass", "250/250 passed; clusivity, conservative bird labels and complement mapping retained."),
        ("review", "review-detail", "All taxonomy/card/culture/property/habitat/place/source/event and morphological drift remained closed."),
        ("review", "lesson-inventory", "35 lessons."),
        ("review", "lexeme-source-log", "18 logs."),
    ]
    for phase, event_type, detail in events:
        record_event(db, batch_label=LABEL, phase=phase, event_type=event_type, actor="codex", detail=detail, artifact_path=PROCESS_LOG, created_at=NOW)
    db.execute("UPDATE sentences SET status='accepted',updated_at=? WHERE batch_id=?", (NOW, batch_id))
    db.execute(
        "UPDATE batches SET notes=?,cheatsheet_version=?,guide_version=? WHERE id=?",
        (NOTES, "Rule 349 accepted", "operator-guide-2026-07-10", batch_id),
    )
    after, words_after = db.execute(
        """SELECT COUNT(*),SUM(LENGTH(kuku)-LENGTH(REPLACE(kuku,' ',''))+1)
           FROM sentences WHERE status IN('accepted','revised')"""
    ).fetchone()
    accepted = db.execute("SELECT COUNT(*) FROM sentences WHERE batch_id=? AND status='accepted'", (batch_id,)).fetchone()[0]
    reviews = db.execute("SELECT COUNT(*) FROM reviews WHERE sentence_id IN(SELECT id FROM sentences WHERE batch_id=?)", (batch_id,)).fetchone()[0]
    if (after, words_after, accepted, reviews) != (19647, 133947, 50, 250):
        raise SystemExit(f"postcheck {after}/{words_after}/{accepted}/{reviews}")
    record_event(
        db, batch_label=LABEL, phase="close", event_type="close-success", actor="codex",
        detail=f"accepted={accepted} reviews={reviews} lessons=35 logs=18 verified {before}->{after} words {words_before}->{words_after}.",
        command="python3 batch_scripts/close_391.py", artifact_path=PROCESS_LOG, created_at=NOW,
    )
    db.commit()
    print(f"closed batch-391: accepted={accepted} reviews={reviews} lessons=35 logs=18 verified {before}->{after} words {words_before}->{words_after}")
    return 0


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