// comprehend.el - ELP native COMPREHENSION front-end: text -> meaning-spec.
//
// The input half of the ELP, the deterministic inverse of the realizer. No LLM,
// no spaCy: analysis uses ELP's own morphology tables run BACKWARD (invertible
// morphology), a deterministic clause grammar for roles / subordination /
// coordination / polarity, and a word-sense picker that (at runtime) defers to
// the engram's own nearest-region embeddings.
//
// parse_spec(text) -> [String]  (a slot map, the same shape realize() consumes,
//   EXTENDED with the SACRED polarity field):
//     intent      "assert" | "question" | "command"
//     agent       subject referent (pronoun surface, or "det adj noun")
//     predicate   verb concept (English lemma = interlingua)
//     patient     direct-object NP (optional)
//     iobj        recipient NP for ditransitives (optional)
//     location    prepositional adjunct e.g. "with the telescope" (optional)
//     tense       "present" | "past" | "future"
//     aspect      "simple" | "progressive" | "perfect"
//     polarity    "aff" | "neg"   <-- SACRED. Always present. Never inferred away.
//     neg_word    standalone negative adverb e.g. "never" (optional)
//     subord_conj subordinating conjunction concept e.g. "because" (optional)
//     subord_pred predicate of the subordinate clause (optional)
//     lang        ISO 639-1 code
//
// Depends on (via concatenation order): language-profile, morphology, grammar.

// ── token cleaning ────────────────────────────────────────────────────────────

fn cp_is_punct(c: String) -> Bool {
    if str_eq(c, ".") { return true }
    if str_eq(c, ",") { return true }
    if str_eq(c, "!") { return true }
    if str_eq(c, "?") { return true }
    if str_eq(c, ";") { return true }
    if str_eq(c, ":") { return true }
    if str_eq(c, "\"") { return true }
    if str_eq(c, "'") { return true }
    if str_eq(c, "(") { return true }
    if str_eq(c, ")") { return true }
    return false
}

fn cp_trim_punct(s: String) -> String {
    let n: Int = str_len(s)
    let end: Int = n
    let running: Bool = true
    while running {
        if end <= 0 {
            let running = false
        } else {
            let c: String = str_slice(s, end - 1, end)
            if cp_is_punct(c) {
                let end = end - 1
            } else {
                let running = false
            }
        }
    }
    let start: Int = 0
    let running2: Bool = true
    while running2 {
        if start >= end {
            let running2 = false
        } else {
            let c2: String = str_slice(s, start, start + 1)
            if cp_is_punct(c2) {
                let start = start + 1
            } else {
                let running2 = false
            }
        }
    }
    return str_slice(s, start, end)
}

fn cp_clean(tok: String) -> String {
    return str_to_lower(cp_trim_punct(tok))
}

fn cp_tokenize(text: String) -> [String] {
    let raw: [String] = str_split(text, " ")
    let n: Int = native_list_len(raw)
    let out: [String] = native_list_empty()
    let i: Int = 0
    while i < n {
        let t: String = cp_clean(native_list_get(raw, i))
        if !str_eq(t, "") {
            let out = native_list_append(out, t)
        }
        let i = i + 1
    }
    return out
}

// ── closed-class lexicon ──────────────────────────────────────────────────────

fn cp_is_determiner(w: String) -> Bool {
    if str_eq(w, "the")   { return true }
    if str_eq(w, "a")     { return true }
    if str_eq(w, "an")    { return true }
    if str_eq(w, "this")  { return true }
    if str_eq(w, "these") { return true }
    if str_eq(w, "those") { return true }
    if str_eq(w, "my")    { return true }
    if str_eq(w, "your")  { return true }
    if str_eq(w, "his")   { return true }
    if str_eq(w, "its")   { return true }
    if str_eq(w, "our")   { return true }
    if str_eq(w, "their") { return true }
    return false
}

// English subject/object pronoun -> concept ("" if not a pronoun).
fn cp_pron_concept(w: String) -> String {
    if str_eq(w, "i")    { return "i" }
    if str_eq(w, "me")   { return "i" }
    if str_eq(w, "we")   { return "we" }
    if str_eq(w, "us")   { return "we" }
    if str_eq(w, "you")  { return "you" }
    if str_eq(w, "he")   { return "he" }
    if str_eq(w, "him")  { return "he" }
    if str_eq(w, "she")  { return "she" }
    if str_eq(w, "it")   { return "it" }
    if str_eq(w, "they") { return "they" }
    if str_eq(w, "them") { return "they" }
    return ""
}

// concept -> canonical subject surface (the form realize()/agent_person expects).
fn cp_pron_surface(concept: String) -> String {
    if str_eq(concept, "i")    { return "I" }
    if str_eq(concept, "we")   { return "we" }
    if str_eq(concept, "you")  { return "you" }
    if str_eq(concept, "he")   { return "he" }
    if str_eq(concept, "she")  { return "she" }
    if str_eq(concept, "it")   { return "it" }
    if str_eq(concept, "they") { return "they" }
    return concept
}

fn cp_is_preposition(w: String) -> Bool {
    if str_eq(w, "in")      { return true }
    if str_eq(w, "on")      { return true }
    if str_eq(w, "at")      { return true }
    if str_eq(w, "to")      { return true }
    if str_eq(w, "with")    { return true }
    if str_eq(w, "from")    { return true }
    if str_eq(w, "by")      { return true }
    if str_eq(w, "for")     { return true }
    if str_eq(w, "of")      { return true }
    if str_eq(w, "into")    { return true }
    if str_eq(w, "onto")    { return true }
    if str_eq(w, "over")    { return true }
    if str_eq(w, "under")   { return true }
    if str_eq(w, "about")   { return true }
    if str_eq(w, "than")    { return true }
    if str_eq(w, "through") { return true }
    if str_eq(w, "near")    { return true }
    if str_eq(w, "around")  { return true }
    if str_eq(w, "between") { return true }
    if str_eq(w, "without") { return true }
    if str_eq(w, "upon")    { return true }
    return false
}

// SACRED: negation lexeme test. Cross-lingual so the same field survives transfer.
fn cp_is_negation(w: String) -> Bool {
    if str_eq(w, "not")     { return true }
    if str_eq(w, "never")   { return true }
    if str_eq(w, "no")      { return true }
    if str_eq(w, "none")    { return true }
    if str_eq(w, "nothing") { return true }
    if str_eq(w, "nobody")  { return true }
    if str_eq(w, "cannot")  { return true }
    if str_contains(w, "n't") { return true }
    if str_eq(w, "nada")    { return true }
    if str_eq(w, "nadie")   { return true }
    if str_eq(w, "nunca")   { return true }
    if str_eq(w, "jamás")   { return true }
    if str_eq(w, "jamais")  { return true }
    if str_eq(w, "não")     { return true }
    if str_eq(w, "nem")     { return true }
    if str_eq(w, "nenhum")  { return true }
    if str_eq(w, "ninguém") { return true }
    return false
}

fn cp_is_neg_adverb(w: String) -> Bool {
    if str_eq(w, "never")  { return true }
    if str_eq(w, "nunca")  { return true }
    if str_eq(w, "jamás")  { return true }
    if str_eq(w, "jamais") { return true }
    return false
}

fn cp_is_aux(w: String) -> Bool {
    if str_eq(w, "am")    { return true }
    if str_eq(w, "is")    { return true }
    if str_eq(w, "are")   { return true }
    if str_eq(w, "was")   { return true }
    if str_eq(w, "were")  { return true }
    if str_eq(w, "be")    { return true }
    if str_eq(w, "been")  { return true }
    if str_eq(w, "being") { return true }
    if str_eq(w, "do")    { return true }
    if str_eq(w, "does")  { return true }
    if str_eq(w, "did")   { return true }
    if str_eq(w, "have")  { return true }
    if str_eq(w, "has")   { return true }
    if str_eq(w, "had")   { return true }
    if str_eq(w, "will")  { return true }
    if str_eq(w, "shall") { return true }
    if str_eq(w, "would") { return true }
    if str_eq(w, "should"){ return true }
    if str_eq(w, "can")   { return true }
    if str_eq(w, "could") { return true }
    if str_eq(w, "may")   { return true }
    if str_eq(w, "might") { return true }
    if str_eq(w, "must")  { return true }
    return false
}

// Subordinating conjunctions that segment a clause (concept-neutral English set).
fn cp_is_subordinator(w: String) -> Bool {
    if str_eq(w, "because")  { return true }
    if str_eq(w, "since")    { return true }
    if str_eq(w, "although") { return true }
    if str_eq(w, "though")   { return true }
    if str_eq(w, "if")       { return true }
    if str_eq(w, "when")     { return true }
    if str_eq(w, "while")    { return true }
    if str_eq(w, "before")   { return true }
    if str_eq(w, "after")    { return true }
    if str_eq(w, "until")    { return true }
    return false
}

// ── invertible English verb morphology (the realizer table, run BACKWARD) ──────
//
// cp_irr2(surface) -> [lemma, tense] or empty. Mirrors en_irregular_verb rows in
// morphology.el (same table), inverted, plus "fight" which the acceptance set
// needs.  This is the ELP invertibility principle: one table both speaks and
// understands.

fn cp_irr_row(base: String, three: String, past: String, pp: String, ger: String, surface: String) -> [String] {
    let r: [String] = native_list_empty()
    if str_eq(surface, past) {
        let r = native_list_append(r, base)
        let r = native_list_append(r, "past")
        return r
    }
    if str_eq(surface, pp) {
        let r = native_list_append(r, base)
        let r = native_list_append(r, "past")
        return r
    }
    if str_eq(surface, three) {
        let r = native_list_append(r, base)
        let r = native_list_append(r, "present")
        return r
    }
    if str_eq(surface, ger) {
        let r = native_list_append(r, base)
        let r = native_list_append(r, "present")
        return r
    }
    if str_eq(surface, base) {
        let r = native_list_append(r, base)
        let r = native_list_append(r, "present")
        return r
    }
    return r
}

fn cp_irr2(surface: String) -> [String] {
    let r: [String] = cp_irr_row("be", "is", "was", "been", "being", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("be", "are", "were", "been", "being", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("have", "has", "had", "had", "having", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("do", "does", "did", "done", "doing", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("go", "goes", "went", "gone", "going", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("say", "says", "said", "said", "saying", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("make", "makes", "made", "made", "making", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("know", "knows", "knew", "known", "knowing", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("take", "takes", "took", "taken", "taking", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("see", "sees", "saw", "seen", "seeing", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("come", "comes", "came", "come", "coming", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("think", "thinks", "thought", "thought", "thinking", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("get", "gets", "got", "gotten", "getting", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("give", "gives", "gave", "given", "giving", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("find", "finds", "found", "found", "finding", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("tell", "tells", "told", "told", "telling", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("leave", "leaves", "left", "left", "leaving", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("feel", "feels", "felt", "felt", "feeling", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("bring", "brings", "brought", "brought", "bringing", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("run", "runs", "ran", "run", "running", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("eat", "eats", "ate", "eaten", "eating", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("read", "reads", "read", "read", "reading", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("fight", "fights", "fought", "fought", "fighting", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("write", "writes", "wrote", "written", "writing", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("begin", "begins", "began", "begun", "beginning", surface)
    if native_list_len(r) > 0 { return r }
    let r = cp_irr_row("become", "becomes", "became", "become", "becoming", surface)
    if native_list_len(r) > 0 { return r }
    let empty: [String] = native_list_empty()
    return empty
}

// cp_reg_verb(surface) -> [lemma, tense, aspect] by suffix stripping (regular).
// ── English silent-e / consonant-doubling inverse (past-participle lemmatizer) ──
//
// Stripping "-ed" naively over-truncates two regular classes:
//   • silent-e verbs  ("loved" -> "lov" instead of "love")
//   • doubled-final    ("stopped" -> "stopp" instead of "stop")
// These helpers invert en_verb_past() so the round-trip closes.  The rules are
// the safe, high-confidence subset; residual orthographic edge cases (e.g. bare
// "ebb"/"arc") are rare and left to the runtime vocabulary confirmer.

fn cp_e_vowel(c: String) -> Bool {
    if str_eq(c, "a") { return true }
    if str_eq(c, "e") { return true }
    if str_eq(c, "i") { return true }
    if str_eq(c, "o") { return true }
    if str_eq(c, "u") { return true }
    return false
}

// consonants that double under inflection (CVC rule): b d g l m n p r t
fn cp_e_doubling(c: String) -> Bool {
    if str_eq(c, "b") { return true }
    if str_eq(c, "d") { return true }
    if str_eq(c, "g") { return true }
    if str_eq(c, "l") { return true }
    if str_eq(c, "m") { return true }
    if str_eq(c, "n") { return true }
    if str_eq(c, "p") { return true }
    if str_eq(c, "r") { return true }
    if str_eq(c, "t") { return true }
    return false
}

// restore the base after "-ed"/"-ing" stripping produced `stem`.
fn cp_e_restore(stem: String) -> String {
    let m: Int = str_len(stem)
    if m < 2 { return stem }
    let last: String = str_slice(stem, m - 1, m)
    let prev: String = str_slice(stem, m - 2, m - 1)
    // inflectional doubled consonant -> collapse (stopped->stop, planned->plan)
    if str_eq(last, prev) {
        if cp_e_doubling(last) { return str_slice(stem, 0, m - 1) }
    }
    // silent-e restoration
    if str_eq(last, "v") { return stem + "e" }
    if str_eq(last, "u") { return stem + "e" }
    let ctx: Bool = false
    if cp_e_vowel(prev) { let ctx = true }
    if str_eq(prev, "l") { let ctx = true }
    if str_eq(prev, "r") { let ctx = true }
    if str_eq(prev, "n") { let ctx = true }
    if ctx {
        if str_eq(last, "c") { return stem + "e" }
        if str_eq(last, "g") { return stem + "e" }
        if str_eq(last, "s") { return stem + "e" }
        if str_eq(last, "z") { return stem + "e" }
    }
    return stem
}

fn cp_reg_verb(w: String) -> [String] {
    let n: Int = str_len(w)
    let r: [String] = native_list_empty()
    if str_ends_with(w, "ing") {
        if n > 4 {
            let lemma: String = cp_e_restore(str_slice(w, 0, n - 3))
            let r = native_list_append(r, lemma)
            let r = native_list_append(r, "present")
            let r = native_list_append(r, "progressive")
            return r
        }
    }
    if str_ends_with(w, "ied") {
        let lemma: String = str_slice(w, 0, n - 3) + "y"
        let r = native_list_append(r, lemma)
        let r = native_list_append(r, "past")
        let r = native_list_append(r, "simple")
        return r
    }
    if str_ends_with(w, "ed") {
        if n > 2 {
            let lemma: String = cp_e_restore(str_slice(w, 0, n - 2))
            let r = native_list_append(r, lemma)
            let r = native_list_append(r, "past")
            let r = native_list_append(r, "simple")
            return r
        }
    }
    if str_ends_with(w, "ies") {
        let lemma: String = str_slice(w, 0, n - 3) + "y"
        let r = native_list_append(r, lemma)
        let r = native_list_append(r, "present")
        let r = native_list_append(r, "simple")
        return r
    }
    if str_ends_with(w, "es") {
        if n > 3 {
            let lemma: String = str_slice(w, 0, n - 2)
            let r = native_list_append(r, lemma)
            let r = native_list_append(r, "present")
            let r = native_list_append(r, "simple")
            return r
        }
    }
    if str_ends_with(w, "s") {
        if n > 2 {
            let lemma: String = str_slice(w, 0, n - 1)
            let r = native_list_append(r, lemma)
            let r = native_list_append(r, "present")
            let r = native_list_append(r, "simple")
            return r
        }
    }
    let r = native_list_append(r, w)
    let r = native_list_append(r, "present")
    let r = native_list_append(r, "simple")
    return r
}

// ── word-sense disambiguation (deterministic; engram nearest-region at runtime) ─
//
// The reference (wsd.py) ranks a FIXED sense inventory by embedding cosine to the
// token's syntactic neighbours using the ENGRAM's own geometry — never an
// external generator.  In this offline el module we resolve the same fixed
// inventory by deterministic SYNTACTIC POSITION (subject-follows -> verb, etc.);
// cp_wsd_needs_engram() marks the tokens whose final sense the live runtime
// should confirm via the engram's nearest-region ranker.

fn cp_is_ambiguous(w: String) -> Bool {
    if str_eq(w, "flies") { return true }
    if str_eq(w, "fly")   { return true }
    if str_eq(w, "like")  { return true }
    if str_eq(w, "saw")   { return true }
    if str_eq(w, "left")  { return true }
    if str_eq(w, "rose")  { return true }
    return false
}

// Is w a plausible finite verb form (used for subject|verb boundary detection)?
fn cp_is_verb_form(w: String) -> Bool {
    let irr: [String] = cp_irr2(w)
    if native_list_len(irr) > 0 { return true }
    if str_eq(w, "flies") { return true }
    if str_eq(w, "fly")   { return true }
    if str_eq(w, "rose")  { return true }
    if str_eq(w, "left")  { return true }
    let n: Int = str_len(w)
    if str_ends_with(w, "ing") { if n > 4 { return true } }
    if str_ends_with(w, "ed")  { if n > 2 { return true } }
    if str_ends_with(w, "es")  { if n > 3 { return true } }
    if str_ends_with(w, "s")   { if n > 2 { return true } }
    return false
}

// analyze one verb surface -> [lemma, tense, aspect], with WSD for ambiguous forms.
fn cp_analyze_verb(surface: String) -> [String] {
    if str_eq(surface, "flies") {
        let r: [String] = native_list_empty()
        let r = native_list_append(r, "fly")
        let r = native_list_append(r, "present")
        let r = native_list_append(r, "simple")
        return r
    }
    let irr: [String] = cp_irr2(surface)
    if native_list_len(irr) > 0 {
        let r: [String] = native_list_empty()
        let r = native_list_append(r, native_list_get(irr, 0))
        let r = native_list_append(r, native_list_get(irr, 1))
        let r = native_list_append(r, "simple")
        return r
    }
    return cp_reg_verb(surface)
}

// ── clause segmentation helpers ───────────────────────────────────────────────

// Index of the first subordinating conjunction in [0,n), or n if none.
fn cp_subord_start(toks: [String], n: Int) -> Int {
    let i: Int = 1
    while i < n {
        if cp_is_subordinator(native_list_get(toks, i)) {
            return i
        }
        let i = i + 1
    }
    return n
}

// Index where the verb cluster begins (end of the subject NP), in [0,end).
fn cp_verb_start(toks: [String], end: Int) -> Int {
    if end == 0 { return 0 }
    let first: String = native_list_get(toks, 0)
    if !str_eq(cp_pron_concept(first), "") {
        return 1
    }
    let have_head: Bool = false
    let i: Int = 0
    while i < end {
        let w: String = native_list_get(toks, i)
        if i > 0 {
            if cp_is_aux(w) { return i }
            if cp_is_negation(w) { return i }
            if have_head {
                if cp_is_verb_form(w) { return i }
            }
        }
        if !cp_is_determiner(w) {
            let have_head = true
        }
        let i = i + 1
    }
    return end
}

// Join tokens [a,b) with spaces.
fn cp_join_range(toks: [String], a: Int, b: Int) -> String {
    let parts: [String] = native_list_empty()
    let i: Int = a
    while i < b {
        let parts = native_list_append(parts, native_list_get(toks, i))
        let i = i + 1
    }
    return str_join(parts, " ")
}

// ── ES / PT (Romance) closed-class + light morphology ─────────────────────────
//
// The spec is language-neutral; only the SURFACE analysis differs. This is the
// deterministic Romance front-end: same clause skeleton (subject | neg | verb |
// object | PP), pro-drop aware (a Spanish/Portuguese sentence may open with the
// verb or a negator and carry no overt subject). SACRED polarity is already
// cross-lingual in cp_is_negation (no / não / nunca / jamás / jamais / nada /
// nadie / nem / nenhum / ninguém), so negation survives here for free.

fn cp_rom_det(w: String) -> Bool {
    if str_eq(w, "el")   { return true }
    if str_eq(w, "la")   { return true }
    if str_eq(w, "los")  { return true }
    if str_eq(w, "las")  { return true }
    if str_eq(w, "un")   { return true }
    if str_eq(w, "una")  { return true }
    if str_eq(w, "unos") { return true }
    if str_eq(w, "unas") { return true }
    if str_eq(w, "o")    { return true }
    if str_eq(w, "os")   { return true }
    if str_eq(w, "um")   { return true }
    if str_eq(w, "uma")  { return true }
    if str_eq(w, "uns")  { return true }
    if str_eq(w, "umas") { return true }
    if str_eq(w, "mi")   { return true }
    if str_eq(w, "tu")   { return true }
    if str_eq(w, "su")   { return true }
    if str_eq(w, "meu")  { return true }
    if str_eq(w, "minha"){ return true }
    if str_eq(w, "seu")  { return true }
    if str_eq(w, "sua")  { return true }
    return false
}

// Romance subject pronoun -> interlingua concept ("" if not a pronoun).
fn cp_rom_pron(w: String) -> String {
    if str_eq(w, "yo")       { return "i" }
    if str_eq(w, "eu")       { return "i" }
    if str_eq(w, "tú")       { return "you" }
    if str_eq(w, "você")     { return "you" }
    if str_eq(w, "usted")    { return "you" }
    if str_eq(w, "él")       { return "he" }
    if str_eq(w, "ele")      { return "he" }
    if str_eq(w, "ella")     { return "she" }
    if str_eq(w, "ela")      { return "she" }
    if str_eq(w, "nosotros") { return "we" }
    if str_eq(w, "nós")      { return "we" }
    if str_eq(w, "ellos")    { return "they" }
    if str_eq(w, "ellas")    { return "they" }
    if str_eq(w, "eles")     { return "they" }
    if str_eq(w, "elas")     { return "they" }
    return ""
}

// concept -> canonical Romance subject surface for the given language.
fn cp_rom_pron_surface(concept: String, lang: String) -> String {
    if str_eq(lang, "pt") {
        if str_eq(concept, "i")    { return "eu" }
        if str_eq(concept, "you")  { return "você" }
        if str_eq(concept, "he")   { return "ele" }
        if str_eq(concept, "she")  { return "ela" }
        if str_eq(concept, "we")   { return "nós" }
        if str_eq(concept, "they") { return "eles" }
        return concept
    }
    if str_eq(concept, "i")    { return "yo" }
    if str_eq(concept, "you")  { return "usted" }
    if str_eq(concept, "he")   { return "él" }
    if str_eq(concept, "she")  { return "ella" }
    if str_eq(concept, "we")   { return "nosotros" }
    if str_eq(concept, "they") { return "ellos" }
    return concept
}

fn cp_rom_prep(w: String) -> Bool {
    if str_eq(w, "en")    { return true }
    if str_eq(w, "em")    { return true }
    if str_eq(w, "a")     { return true }
    if str_eq(w, "de")    { return true }
    if str_eq(w, "del")   { return true }
    if str_eq(w, "do")    { return true }
    if str_eq(w, "da")    { return true }
    if str_eq(w, "con")   { return true }
    if str_eq(w, "com")   { return true }
    if str_eq(w, "por")   { return true }
    if str_eq(w, "para")  { return true }
    if str_eq(w, "sin")   { return true }
    if str_eq(w, "sem")   { return true }
    if str_eq(w, "sobre") { return true }
    if str_eq(w, "entre") { return true }
    if str_eq(w, "desde") { return true }
    if str_eq(w, "hasta") { return true }
    if str_eq(w, "até")   { return true }
    return false
}

// Romance auxiliaries / copulas (ser/estar/haber/haver/ter/ir cores).
fn cp_rom_aux(w: String) -> Bool {
    if str_eq(w, "es")    { return true }
    if str_eq(w, "está")  { return true }
    if str_eq(w, "son")   { return true }
    if str_eq(w, "están") { return true }
    if str_eq(w, "era")   { return true }
    if str_eq(w, "eran")  { return true }
    if str_eq(w, "fue")   { return true }
    if str_eq(w, "ha")    { return true }
    if str_eq(w, "han")   { return true }
    if str_eq(w, "va")    { return true }
    if str_eq(w, "van")   { return true }
    if str_eq(w, "é")     { return true }
    if str_eq(w, "são")   { return true }
    if str_eq(w, "foi")   { return true }
    if str_eq(w, "tem")   { return true }
    if str_eq(w, "têm")   { return true }
    if str_eq(w, "vai")   { return true }
    return false
}

// A plausible Romance finite verb by inflectional ending (boundary detector).
fn cp_rom_is_verb(w: String) -> Bool {
    let n: Int = str_len(w)
    if n < 3 { return false }
    // Closed-class words are never finite verbs — reject them before the
    // (ambiguous) inflectional-ending test.  This kills the ending-only
    // misfires on prepositions ("para", "sobre", "entre", "desde", "hasta")
    // and on determiners / subject pronouns that happen to end in -a/-e/-o.
    if cp_rom_prep(w)   { return false }
    if cp_rom_det(w)    { return false }
    if cp_rom_aux(w)    { return false }
    if cp_is_negation(w) { return false }
    if !str_eq(cp_rom_pron(w), "") { return false }
    if str_ends_with(w, "ar") { return true }
    if str_ends_with(w, "er") { return true }
    if str_ends_with(w, "ir") { return true }
    if str_ends_with(w, "ó")  { return true }
    if str_ends_with(w, "ió") { return true }
    if str_ends_with(w, "ou") { return true }
    if str_ends_with(w, "eu") { return true }
    if str_ends_with(w, "iu") { return true }
    if str_ends_with(w, "aba"){ return true }
    if str_ends_with(w, "ía") { return true }
    if str_ends_with(w, "ava"){ return true }
    if str_ends_with(w, "an") { return true }
    if str_ends_with(w, "en") { return true }
    if str_ends_with(w, "am") { return true }
    if str_ends_with(w, "em") { return true }
    if str_ends_with(w, "a")  { return true }
    if str_ends_with(w, "e")  { return true }
    if str_ends_with(w, "o")  { return true }
    return false
}

// Romance verb -> [lemma, tense]. Lemma recovery is best-effort (endings only,
// no full paradigm inversion yet); tense is read from the inflection class.
fn cp_rom_lemma_tense(w: String) -> [String] {
    let n: Int = str_len(w)
    let tense: String = "present"
    if str_ends_with(w, "ó")   { let tense = "past" }
    if str_ends_with(w, "ió")  { let tense = "past" }
    if str_ends_with(w, "ou")  { let tense = "past" }
    if str_ends_with(w, "eu")  { let tense = "past" }
    if str_ends_with(w, "iu")  { let tense = "past" }
    if str_ends_with(w, "aba") { let tense = "past" }
    if str_ends_with(w, "ía")  { let tense = "past" }
    if str_ends_with(w, "ava") { let tense = "past" }
    if str_ends_with(w, "rá")  { let tense = "future" }
    if str_ends_with(w, "rão") { let tense = "future" }
    let r: [String] = native_list_empty()
    let r = native_list_append(r, w)
    let r = native_list_append(r, tense)
    return r
}

// Index where the Romance verb cluster begins (end of the subject NP), in [0,end).
fn cp_rom_verb_start(toks: [String], end: Int) -> Int {
    if end == 0 { return 0 }
    let first: String = native_list_get(toks, 0)
    // pro-drop / verb-initial / negator-initial: no overt subject NP.
    if cp_is_negation(first) { return 0 }
    if !str_eq(cp_rom_pron(first), "") { return 1 }
    if cp_rom_is_verb(first) { return 0 }
    let have_head: Bool = false
    let i: Int = 0
    while i < end {
        let w: String = native_list_get(toks, i)
        if i > 0 {
            if cp_is_negation(w) { return i }
            if cp_rom_aux(w) { return i }
            if have_head {
                if cp_rom_is_verb(w) { return i }
            }
        }
        if !cp_rom_det(w) { let have_head = true }
        let i = i + 1
    }
    return end
}

fn parse_spec_romance(text: String, lang: String) -> [String] {
    let toks: [String] = cp_tokenize(text)
    let n: Int = native_list_len(toks)

    let intent: String = "assert"
    if str_ends_with(text, "?") { let intent = "question" }
    let m_end: Int = cp_subord_start(toks, n)

    // SACRED polarity — cross-lingual negation lexemes in the main clause.
    let polarity: String = "aff"
    let neg_word: String = ""
    let pi: Int = 0
    while pi < m_end {
        let w: String = native_list_get(toks, pi)
        if cp_is_negation(w) {
            let polarity = "neg"
            if cp_is_neg_adverb(w) { let neg_word = w }
        }
        let pi = pi + 1
    }

    // subject (pro-drop aware)
    let vstart: Int = cp_rom_verb_start(toks, m_end)
    let agent: String = ""
    let first: String = ""
    if n > 0 { let first = native_list_get(toks, 0) }
    if vstart == 0 {
        let agent = ""
    } else {
        if vstart == 1 {
            if !str_eq(cp_rom_pron(first), "") {
                let agent = cp_rom_pron_surface(cp_rom_pron(first), lang)
            } else {
                let agent = cp_join_range(toks, 0, 1)
            }
        } else {
            let agent = cp_join_range(toks, 0, vstart)
        }
    }

    // verb cluster
    let tense: String = "present"
    let aspect: String = "simple"
    let predicate: String = ""
    let found_verb: Bool = false
    let i: Int = vstart
    while i < m_end {
        let w: String = native_list_get(toks, i)
        if found_verb {
            let i = m_end
        } else {
            if cp_is_negation(w) {
                let i = i + 1
            } else {
                if cp_rom_aux(w) {
                    if str_eq(w, "era")  { let tense = "past" }
                    if str_eq(w, "eran") { let tense = "past" }
                    if str_eq(w, "fue")  { let tense = "past" }
                    if str_eq(w, "foi")  { let tense = "past" }
                    if str_eq(w, "ha")   { let aspect = "perfect" }
                    if str_eq(w, "han")  { let aspect = "perfect" }
                    if str_eq(w, "va")   { let tense = "future" }
                    if str_eq(w, "van")  { let tense = "future" }
                    if str_eq(w, "vai")  { let tense = "future" }
                    // a bare copula with no following lexical verb is the predicate
                    let predicate = "be"
                    let i = i + 1
                } else {
                    if cp_rom_is_verb(w) {
                        let lt: [String] = cp_rom_lemma_tense(w)
                        let predicate = native_list_get(lt, 0)
                        let tense = native_list_get(lt, 1)
                        let found_verb = true
                    }
                    let i = i + 1
                }
            }
        }
    }
    if str_eq(predicate, "be") { let found_verb = true }

    // complements: first NP after the verb is the object; "a" -> recipient (iobj);
    // other prepositions -> location adjunct.
    let cstart: Int = vstart
    let seen_v: Bool = false
    let j: Int = vstart
    while j < m_end {
        let w: String = native_list_get(toks, j)
        if seen_v {
            let cstart = j
            let j = m_end
        } else {
            if cp_is_negation(w) { let j = j + 1 }
            else {
                if cp_rom_aux(w) { let j = j + 1 }
                else {
                    if cp_rom_is_verb(w) { let seen_v = true }
                    let j = j + 1
                }
            }
        }
    }

    let patient: String = ""
    let iobj: String = ""
    let location: String = ""
    let k: Int = cstart
    while k < m_end {
        let w: String = native_list_get(toks, k)
        if cp_rom_prep(w) {
            let prep: String = w
            let a: Int = k + 1
            let b: Int = a
            let run3: Bool = true
            while run3 {
                if b >= m_end { let run3 = false }
                else {
                    if cp_rom_prep(native_list_get(toks, b)) { let run3 = false }
                    else { let b = b + 1 }
                }
            }
            let np: String = cp_join_range(toks, a, b)
            if str_eq(prep, "a") {
                let iobj = np
            } else {
                if str_eq(location, "") { let location = prep + " " + np }
            }
            let k = b
        } else {
            let a: Int = k
            let b: Int = a
            let run4: Bool = true
            while run4 {
                if b >= m_end { let run4 = false }
                else {
                    if cp_rom_prep(native_list_get(toks, b)) { let run4 = false }
                    else { let b = b + 1 }
                }
            }
            if str_eq(patient, "") { let patient = cp_join_range(toks, a, b) }
            let k = b
        }
    }

    // subordinate clause conj (predicate recovery is left to a later pass)
    let subord_conj: String = ""
    let subord_pred: String = ""
    if m_end < n { let subord_conj = native_list_get(toks, m_end) }

    if str_eq(agent, "") {
        if found_verb {
            if str_eq(intent, "assert") { let intent = "command" }
        }
    }

    let spec: [String] = native_list_empty()
    let spec = native_list_append(spec, "intent");      let spec = native_list_append(spec, intent)
    let spec = native_list_append(spec, "agent");       let spec = native_list_append(spec, agent)
    let spec = native_list_append(spec, "predicate");   let spec = native_list_append(spec, predicate)
    let spec = native_list_append(spec, "patient");     let spec = native_list_append(spec, patient)
    let spec = native_list_append(spec, "iobj");        let spec = native_list_append(spec, iobj)
    let spec = native_list_append(spec, "location");    let spec = native_list_append(spec, location)
    let spec = native_list_append(spec, "tense");       let spec = native_list_append(spec, tense)
    let spec = native_list_append(spec, "aspect");      let spec = native_list_append(spec, aspect)
    let spec = native_list_append(spec, "polarity");    let spec = native_list_append(spec, polarity)
    let spec = native_list_append(spec, "neg_word");    let spec = native_list_append(spec, neg_word)
    let spec = native_list_append(spec, "subord_conj"); let spec = native_list_append(spec, subord_conj)
    let spec = native_list_append(spec, "subord_pred"); let spec = native_list_append(spec, subord_pred)
    let spec = native_list_append(spec, "lang");        let spec = native_list_append(spec, lang)
    return spec
}

// ── the parser ────────────────────────────────────────────────────────────────

fn parse_spec_lang(text: String, lang: String) -> [String] {
    if str_eq(lang, "es") { return parse_spec_romance(text, lang) }
    if str_eq(lang, "pt") { return parse_spec_romance(text, lang) }
    let toks: [String] = cp_tokenize(text)
    let n: Int = native_list_len(toks)

    let intent: String = "assert"
    if str_ends_with(text, "?") { let intent = "question" }

    // clause split: main [0, m_end), subordinate [m_end, n)
    let m_end: Int = cp_subord_start(toks, n)

    // SACRED polarity: scan the MAIN clause for any negation lexeme.
    let polarity: String = "aff"
    let neg_word: String = ""
    let pi: Int = 0
    while pi < m_end {
        let w: String = native_list_get(toks, pi)
        if cp_is_negation(w) {
            let polarity = "neg"
            if cp_is_neg_adverb(w) { let neg_word = w }
        }
        let pi = pi + 1
    }

    // subject
    let vstart: Int = cp_verb_start(toks, m_end)
    let agent: String = ""
    let first: String = ""
    if n > 0 { let first = native_list_get(toks, 0) }
    if vstart == 1 {
        if !str_eq(cp_pron_concept(first), "") {
            let agent = cp_pron_surface(cp_pron_concept(first))
        } else {
            let agent = cp_join_range(toks, 0, 1)
        }
    } else {
        let agent = cp_join_range(toks, 0, vstart)
    }

    // verb cluster: walk auxiliaries + main verb
    let tense: String = "present"
    let aspect: String = "simple"
    let predicate: String = ""
    let do_support: Bool = false
    let saw_be: Bool = false
    let found_verb: Bool = false
    let i: Int = vstart
    while i < m_end {
        let w: String = native_list_get(toks, i)
        if found_verb {
            let i = m_end
        } else {
            if cp_is_negation(w) {
                let i = i + 1
            } else {
                if cp_is_aux(w) {
                    if str_eq(w, "did")  { let tense = "past";   let do_support = true }
                    if str_eq(w, "does") { let tense = "present"; let do_support = true }
                    if str_eq(w, "do")   { let tense = "present"; let do_support = true }
                    if str_eq(w, "will")  { let tense = "future" }
                    if str_eq(w, "shall") { let tense = "future" }
                    if str_eq(w, "was")  { let tense = "past";  let saw_be = true }
                    if str_eq(w, "were") { let tense = "past";  let saw_be = true }
                    if str_eq(w, "is")   { let saw_be = true }
                    if str_eq(w, "are")  { let saw_be = true }
                    if str_eq(w, "am")   { let saw_be = true }
                    if str_eq(w, "has")  { let aspect = "perfect" }
                    if str_eq(w, "have") { let aspect = "perfect" }
                    if str_eq(w, "had")  { let aspect = "perfect"; let tense = "past" }
                    let i = i + 1
                } else {
                    // main verb token
                    if do_support {
                        let predicate = w
                        // tense already set by the do-auxiliary; verb is the base form
                    } else {
                        let va: [String] = cp_analyze_verb(w)
                        let predicate = native_list_get(va, 0)
                        let tense = native_list_get(va, 1)
                        if str_eq(aspect, "simple") {
                            let aspect = native_list_get(va, 2)
                        }
                    }
                    let found_verb = true
                    let i = i + 1
                }
            }
        }
    }
    // copula: be-auxiliary with no following lexical verb IS the predicate.
    if !found_verb {
        if saw_be {
            let predicate = "be"
            let found_verb = true
        }
    }

    // complements: object NP, to-recipient (iobj), and PP adjunct (location).
    // start scanning after the verb we consumed.
    let patient: String = ""
    let iobj: String = ""
    let location: String = ""
    // recompute where complements begin: first token after the main verb.
    let cstart: Int = vstart
    let scanned: Bool = false
    let j: Int = vstart
    let seen_v: Bool = false
    while j < m_end {
        let w: String = native_list_get(toks, j)
        if seen_v {
            let j = m_end
        } else {
            if cp_is_negation(w) {
                let j = j + 1
            } else {
                if cp_is_aux(w) {
                    let j = j + 1
                } else {
                    // main verb consumed — complements begin at the NEXT token.
                    // (Setting cstart here, not on a later iteration, is what makes
                    //  a sentence-final verb yield no phantom object.)
                    let seen_v = true
                    let cstart = j + 1
                    let j = m_end
                }
            }
        }
    }

    let k: Int = cstart
    while k < m_end {
        let w: String = native_list_get(toks, k)
        let is_prep: Bool = cp_is_preposition(w)
        if str_eq(w, "like") { let is_prep = true }
        if is_prep {
            let prep: String = w
            let a: Int = k + 1
            let b: Int = a
            let run3: Bool = true
            while run3 {
                if b >= m_end {
                    let run3 = false
                } else {
                    let wb: String = native_list_get(toks, b)
                    let wb_prep: Bool = cp_is_preposition(wb)
                    if str_eq(wb, "like") { let wb_prep = true }
                    if wb_prep {
                        let run3 = false
                    } else {
                        let b = b + 1
                    }
                }
            }
            let np: String = cp_join_range(toks, a, b)
            if str_eq(prep, "to") {
                let iobj = np
            } else {
                if str_eq(location, "") {
                    let location = prep + " " + np
                }
            }
            let k = b
        } else {
            let a: Int = k
            let b: Int = a
            let run4: Bool = true
            while run4 {
                if b >= m_end {
                    let run4 = false
                } else {
                    let wb: String = native_list_get(toks, b)
                    let wb_prep: Bool = cp_is_preposition(wb)
                    if str_eq(wb, "like") { let wb_prep = true }
                    if wb_prep {
                        let run4 = false
                    } else {
                        let b = b + 1
                    }
                }
            }
            if str_eq(patient, "") {
                let patient = cp_join_range(toks, a, b)
            }
            let k = b
        }
    }

    // subordinate clause: conj + (lightweight) predicate recovery.
    //   subord_conj  the subordinating conjunction ("because", "if", ...)
    //   subord_pred  the recovered predicate lemma of the subordinate clause
    //   subord_text  the SURFACE of the subordinate clause AFTER the conjunction,
    //                carried verbatim so realization is byte-complete (the clause
    //                is preserved, not dropped — SACRED completeness of meaning).
    let subord_conj: String = ""
    let subord_pred: String = ""
    let subord_text: String = ""
    if m_end < n {
        let subord_conj = native_list_get(toks, m_end)
        let subord_text = cp_join_range(toks, m_end + 1, n)
        // find the subordinate predicate: first aux/verb after the conjunction.
        let s: Int = m_end + 1
        let sfound: Bool = false
        while s < n {
            if sfound {
                let s = n
            } else {
                let sw: String = native_list_get(toks, s)
                if cp_is_aux(sw) {
                    if str_eq(sw, "was")  { let subord_pred = "be"; let sfound = true }
                    if str_eq(sw, "were") { let subord_pred = "be"; let sfound = true }
                    if str_eq(sw, "is")   { let subord_pred = "be"; let sfound = true }
                    if str_eq(sw, "are")  { let subord_pred = "be"; let sfound = true }
                    let s = s + 1
                } else {
                    if cp_is_verb_form(sw) {
                        let va2: [String] = cp_analyze_verb(sw)
                        let subord_pred = native_list_get(va2, 0)
                        let sfound = true
                    }
                    let s = s + 1
                }
            }
        }
    }

    // intent refinement: no subject + leading verb => imperative command.
    if str_eq(agent, "") {
        if found_verb {
            if str_eq(intent, "assert") { let intent = "command" }
        }
    }

    // ── emit the slot map (realizer-compatible + SACRED polarity) ─────────────
    let spec: [String] = native_list_empty()
    let spec = native_list_append(spec, "intent");      let spec = native_list_append(spec, intent)
    let spec = native_list_append(spec, "agent");       let spec = native_list_append(spec, agent)
    let spec = native_list_append(spec, "predicate");   let spec = native_list_append(spec, predicate)
    let spec = native_list_append(spec, "patient");     let spec = native_list_append(spec, patient)
    let spec = native_list_append(spec, "iobj");        let spec = native_list_append(spec, iobj)
    let spec = native_list_append(spec, "location");    let spec = native_list_append(spec, location)
    let spec = native_list_append(spec, "tense");       let spec = native_list_append(spec, tense)
    let spec = native_list_append(spec, "aspect");      let spec = native_list_append(spec, aspect)
    let spec = native_list_append(spec, "polarity");    let spec = native_list_append(spec, polarity)
    let spec = native_list_append(spec, "neg_word");    let spec = native_list_append(spec, neg_word)
    let spec = native_list_append(spec, "subord_conj"); let spec = native_list_append(spec, subord_conj)
    let spec = native_list_append(spec, "subord_pred"); let spec = native_list_append(spec, subord_pred)
    let spec = native_list_append(spec, "subord_text"); let spec = native_list_append(spec, subord_text)
    let spec = native_list_append(spec, "lang");        let spec = native_list_append(spec, lang)
    return spec
}

// English default entry point.
fn parse_spec(text: String) -> [String] {
    return parse_spec_lang(text, "en")
}

// ── JSON emission (integration contract for generate_lang / build_form_from_json)
fn cp_json_field(key: String, val: String) -> String {
    return "\"" + key + "\": \"" + val + "\""
}

fn parse_json_lang(text: String, lang: String) -> String {
    let spec: [String] = parse_spec_lang(text, lang)
    let parts: [String] = native_list_empty()
    let parts = native_list_append(parts, cp_json_field("intent",    slots_get(spec, "intent")))
    let parts = native_list_append(parts, cp_json_field("agent",     slots_get(spec, "agent")))
    let parts = native_list_append(parts, cp_json_field("predicate", slots_get(spec, "predicate")))
    let parts = native_list_append(parts, cp_json_field("patient",   slots_get(spec, "patient")))
    let parts = native_list_append(parts, cp_json_field("iobj",      slots_get(spec, "iobj")))
    let parts = native_list_append(parts, cp_json_field("location",  slots_get(spec, "location")))
    let parts = native_list_append(parts, cp_json_field("tense",     slots_get(spec, "tense")))
    let parts = native_list_append(parts, cp_json_field("aspect",    slots_get(spec, "aspect")))
    let parts = native_list_append(parts, cp_json_field("polarity",  slots_get(spec, "polarity")))
    let parts = native_list_append(parts, cp_json_field("neg_word",  slots_get(spec, "neg_word")))
    let parts = native_list_append(parts, cp_json_field("lang",      lang))
    return "{" + str_join(parts, ", ") + "}"
}

fn parse_json(text: String) -> String {
    return parse_json_lang(text, "en")
}
