// morphology.el - Morphology engine: inflection driven by language profile.
//
// Strategy dispatch is based on the morph_type field of the language profile:
//
//   "isolating"     (zh, vi)       - no inflection; base form is final form.
//   "agglutinative" (ja, fi, sw)   - suffix chains; each feature adds a suffix.
//   "fusional"      (en, de, ru)   - endings encode multiple features at once;
//                                    table-driven per language.
//   "polysynthetic" (various)      - complex verb complexes; returns base form,
//                                    full support deferred.
//
// Language-specific dispatch (added in 0.3.0):
//   morph_conjugate and morph_pluralize first check the profile language code
//   and delegate to the corresponding language module when one is available:
//     es -> morphology-es.el     (Spanish)
//     fr -> morphology-fr.el     (French)
//     de -> morphology-de.el     (German)
//     ru -> morphology-ru.el     (Russian)
//     ja -> morphology-ja.el     (Japanese)
//     fi -> morphology-fi.el     (Finnish)
//     ar -> morphology-ar.el     (Arabic)
//     hi -> morphology-hi.el     (Hindi)
//     sw -> morphology-sw.el     (Swahili)
//   Unknown codes fall through to the morph_type strategy dispatch below.
//
// For English (fusional, code="en") the existing rules are preserved and
// routed through the engine interface rather than exposed as the sole path.
//
// Depends on: language-profile
// Language modules (loaded after this file, depend on this file):
//   morphology-es, morphology-fr, morphology-de, morphology-ru, morphology-ja,
//   morphology-fi, morphology-ar, morphology-hi, morphology-sw

// ── String helpers ────────────────────────────────────────────────────────────

fn str_ends(s: String, suf: String) -> Bool {
    return str_ends_with(s, suf)
}

fn str_last_char(s: String) -> String {
    let n: Int = str_len(s)
    if n == 0 {
        return ""
    }
    return str_slice(s, n - 1, n)
}

fn str_last2(s: String) -> String {
    let n: Int = str_len(s)
    if n < 2 {
        return s
    }
    return str_slice(s, n - 2, n)
}

fn str_last3(s: String) -> String {
    let n: Int = str_len(s)
    if n < 3 {
        return s
    }
    return str_slice(s, n - 3, n)
}

fn str_drop_last(s: String, n: Int) -> String {
    let len: Int = str_len(s)
    if n >= len {
        return ""
    }
    return str_slice(s, 0, len - n)
}

fn is_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
}

// ── Suffix application ────────────────────────────────────────────────────────
//
// Apply a suffix to a base form with basic phonological adjustments.
// Used by the agglutinative path and the fusional helper functions.
//
// Rules applied (in order):
//   base ends in "-e" and suffix starts with a vowel -> drop the trailing "e"
//   base ends in CVC and suffix starts with a vowel  -> double final consonant
//   default: concatenate

fn morph_apply_suffix(base: String, suffix: String) -> String {
    if str_eq(suffix, "") {
        return base
    }
    let suf_start: String = str_slice(suffix, 0, 1)
    let suf_starts_vowel: Bool = is_vowel(suf_start)

    // Drop trailing silent -e before a vowel-initial suffix
    if suf_starts_vowel {
        if str_ends(base, "e") {
            if !str_ends(base, "ee") {
                return str_drop_last(base, 1) + suffix
            }
        }
    }

    // CVC doubling before a vowel-initial suffix
    if suf_starts_vowel {
        let n: Int = str_len(base)
        if n >= 3 {
            let c3: String = str_slice(base, n - 3, n - 2)
            let c2: String = str_slice(base, n - 2, n - 1)
            let c1: String = str_slice(base, n - 1, n)
            if !is_vowel(c3) {
                if is_vowel(c2) {
                    if !is_vowel(c1) {
                        if !str_eq(c1, "w") {
                            if !str_eq(c1, "x") {
                                if !str_eq(c1, "y") {
                                    return base + c1 + suffix
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    return base + suffix
}

// ── English irregular tables ──────────────────────────────────────────────────

fn en_irregular_plural(word: String) -> String {
    if str_eq(word, "child")   { return "children" }
    if str_eq(word, "man")     { return "men" }
    if str_eq(word, "woman")   { return "women" }
    if str_eq(word, "tooth")   { return "teeth" }
    if str_eq(word, "foot")    { return "feet" }
    if str_eq(word, "goose")   { return "geese" }
    if str_eq(word, "mouse")   { return "mice" }
    if str_eq(word, "louse")   { return "lice" }
    if str_eq(word, "ox")      { return "oxen" }
    if str_eq(word, "person")  { return "people" }
    if str_eq(word, "leaf")    { return "leaves" }
    if str_eq(word, "loaf")    { return "loaves" }
    if str_eq(word, "wolf")    { return "wolves" }
    if str_eq(word, "life")    { return "lives" }
    if str_eq(word, "knife")   { return "knives" }
    if str_eq(word, "wife")    { return "wives" }
    if str_eq(word, "half")    { return "halves" }
    if str_eq(word, "self")    { return "selves" }
    if str_eq(word, "elf")     { return "elves" }
    if str_eq(word, "shelf")   { return "shelves" }
    if str_eq(word, "fish")    { return "fish" }
    if str_eq(word, "sheep")   { return "sheep" }
    if str_eq(word, "deer")    { return "deer" }
    if str_eq(word, "moose")   { return "moose" }
    if str_eq(word, "series")  { return "series" }
    if str_eq(word, "species") { return "species" }
    return ""
}

fn en_irregular_singular(word: String) -> String {
    if str_eq(word, "children") { return "child" }
    if str_eq(word, "men")      { return "man" }
    if str_eq(word, "women")    { return "woman" }
    if str_eq(word, "teeth")    { return "tooth" }
    if str_eq(word, "feet")     { return "foot" }
    if str_eq(word, "geese")    { return "goose" }
    if str_eq(word, "mice")     { return "mouse" }
    if str_eq(word, "lice")     { return "louse" }
    if str_eq(word, "oxen")     { return "ox" }
    if str_eq(word, "people")   { return "person" }
    if str_eq(word, "leaves")   { return "leaf" }
    if str_eq(word, "wolves")   { return "wolf" }
    if str_eq(word, "lives")    { return "life" }
    if str_eq(word, "knives")   { return "knife" }
    if str_eq(word, "wives")    { return "wife" }
    if str_eq(word, "halves")   { return "half" }
    if str_eq(word, "selves")   { return "self" }
    if str_eq(word, "elves")    { return "elf" }
    if str_eq(word, "shelves")  { return "shelf" }
    if str_eq(word, "fish")     { return "fish" }
    if str_eq(word, "sheep")    { return "sheep" }
    if str_eq(word, "deer")     { return "deer" }
    if str_eq(word, "moose")    { return "moose" }
    if str_eq(word, "series")   { return "series" }
    if str_eq(word, "species")  { return "species" }
    return ""
}

// Returns [base, 3sg-present, past, past-participle, gerund] for English
// irregular verbs, or an empty list if the verb is regular.
fn en_irregular_verb(base: String) -> [String] {
    let empty: [String] = []
    if str_eq(base, "be")      { let r: [String] = ["be",     "is",    "was",    "been",    "being"];    return r }
    if str_eq(base, "have")    { let r: [String] = ["have",   "has",   "had",    "had",     "having"];   return r }
    if str_eq(base, "do")      { let r: [String] = ["do",     "does",  "did",    "done",    "doing"];    return r }
    if str_eq(base, "go")      { let r: [String] = ["go",     "goes",  "went",   "gone",    "going"];    return r }
    if str_eq(base, "say")     { let r: [String] = ["say",    "says",  "said",   "said",    "saying"];   return r }
    if str_eq(base, "make")    { let r: [String] = ["make",   "makes", "made",   "made",    "making"];   return r }
    if str_eq(base, "know")    { let r: [String] = ["know",   "knows", "knew",   "known",   "knowing"];  return r }
    if str_eq(base, "take")    { let r: [String] = ["take",   "takes", "took",   "taken",   "taking"];   return r }
    if str_eq(base, "see")     { let r: [String] = ["see",    "sees",  "saw",    "seen",    "seeing"];   return r }
    if str_eq(base, "come")    { let r: [String] = ["come",   "comes", "came",   "come",    "coming"];   return r }
    if str_eq(base, "think")   { let r: [String] = ["think",  "thinks","thought","thought", "thinking"]; return r }
    if str_eq(base, "get")     { let r: [String] = ["get",    "gets",  "got",    "gotten",  "getting"];  return r }
    if str_eq(base, "give")    { let r: [String] = ["give",   "gives", "gave",   "given",   "giving"];   return r }
    if str_eq(base, "find")    { let r: [String] = ["find",   "finds", "found",  "found",   "finding"];  return r }
    if str_eq(base, "tell")    { let r: [String] = ["tell",   "tells", "told",   "told",    "telling"];  return r }
    if str_eq(base, "become")  { let r: [String] = ["become", "becomes","became","become",  "becoming"]; return r }
    if str_eq(base, "leave")   { let r: [String] = ["leave",  "leaves","left",   "left",    "leaving"];  return r }
    if str_eq(base, "feel")    { let r: [String] = ["feel",   "feels", "felt",   "felt",    "feeling"];  return r }
    if str_eq(base, "put")     { let r: [String] = ["put",    "puts",  "put",    "put",     "putting"];  return r }
    if str_eq(base, "bring")   { let r: [String] = ["bring",  "brings","brought","brought", "bringing"]; return r }
    if str_eq(base, "begin")   { let r: [String] = ["begin",  "begins","began",  "begun",   "beginning"];return r }
    if str_eq(base, "keep")    { let r: [String] = ["keep",   "keeps", "kept",   "kept",    "keeping"];  return r }
    if str_eq(base, "hold")    { let r: [String] = ["hold",   "holds", "held",   "held",    "holding"];  return r }
    if str_eq(base, "write")   { let r: [String] = ["write",  "writes","wrote",  "written", "writing"];  return r }
    if str_eq(base, "stand")   { let r: [String] = ["stand",  "stands","stood",  "stood",   "standing"]; return r }
    if str_eq(base, "hear")    { let r: [String] = ["hear",   "hears", "heard",  "heard",   "hearing"];  return r }
    if str_eq(base, "let")     { let r: [String] = ["let",    "lets",  "let",    "let",     "letting"];  return r }
    if str_eq(base, "run")     { let r: [String] = ["run",    "runs",  "ran",    "run",     "running"];  return r }
    if str_eq(base, "meet")    { let r: [String] = ["meet",   "meets", "met",    "met",     "meeting"];  return r }
    if str_eq(base, "sit")     { let r: [String] = ["sit",    "sits",  "sat",    "sat",     "sitting"];  return r }
    if str_eq(base, "send")    { let r: [String] = ["send",   "sends", "sent",   "sent",    "sending"];  return r }
    if str_eq(base, "speak")   { let r: [String] = ["speak",  "speaks","spoke",  "spoken",  "speaking"]; return r }
    if str_eq(base, "buy")     { let r: [String] = ["buy",    "buys",  "bought", "bought",  "buying"];   return r }
    if str_eq(base, "pay")     { let r: [String] = ["pay",    "pays",  "paid",   "paid",    "paying"];   return r }
    if str_eq(base, "read")    { let r: [String] = ["read",   "reads", "read",   "read",    "reading"];  return r }
    if str_eq(base, "win")     { let r: [String] = ["win",    "wins",  "won",    "won",     "winning"];  return r }
    if str_eq(base, "eat")     { let r: [String] = ["eat",    "eats",  "ate",    "eaten",   "eating"];   return r }
    if str_eq(base, "fall")    { let r: [String] = ["fall",   "falls", "fell",   "fallen",  "falling"];  return r }
    if str_eq(base, "sleep")   { let r: [String] = ["sleep",  "sleeps","slept",  "slept",   "sleeping"]; return r }
    if str_eq(base, "drive")   { let r: [String] = ["drive",  "drives","drove",  "driven",  "driving"];  return r }
    if str_eq(base, "build")   { let r: [String] = ["build",  "builds","built",  "built",   "building"]; return r }
    if str_eq(base, "cut")     { let r: [String] = ["cut",    "cuts",  "cut",    "cut",     "cutting"];  return r }
    if str_eq(base, "set")     { let r: [String] = ["set",    "sets",  "set",    "set",     "setting"];  return r }
    if str_eq(base, "hit")     { let r: [String] = ["hit",    "hits",  "hit",    "hit",     "hitting"];  return r }
    return empty
}

// ── English regular inflection helpers ────────────────────────────────────────

fn en_verb_3sg(base: String) -> String {
    if str_ends(base, "s")  { return base + "es" }
    if str_ends(base, "x")  { return base + "es" }
    if str_ends(base, "z")  { return base + "es" }
    if str_ends(base, "ch") { return base + "es" }
    if str_ends(base, "sh") { return base + "es" }
    let last: String = str_last_char(base)
    if str_eq(last, "y") {
        let prev: String = str_drop_last(base, 1)
        let prev_last: String = str_last_char(prev)
        if !is_vowel(prev_last) {
            return prev + "ies"
        }
    }
    return base + "s"
}

fn en_should_double_final(base: String) -> Bool {
    let n: Int = str_len(base)
    if n < 3 {
        return false
    }
    let c3: String = str_slice(base, n - 3, n - 2)
    let c2: String = str_slice(base, n - 2, n - 1)
    let c1: String = str_slice(base, n - 1, n)
    if !is_vowel(c3) {
        if is_vowel(c2) {
            if !is_vowel(c1) {
                if !str_eq(c1, "w") {
                    if !str_eq(c1, "x") {
                        if !str_eq(c1, "y") {
                            return true
                        }
                    }
                }
            }
        }
    }
    return false
}

fn en_verb_past(base: String) -> String {
    if str_ends(base, "e") {
        return base + "d"
    }
    let last: String = str_last_char(base)
    if str_eq(last, "y") {
        let prev: String = str_drop_last(base, 1)
        let prev_last: String = str_last_char(prev)
        if !is_vowel(prev_last) {
            return prev + "ied"
        }
    }
    if en_should_double_final(base) {
        return base + last + "ed"
    }
    return base + "ed"
}

fn en_verb_gerund(base: String) -> String {
    if str_ends(base, "ie") {
        return str_drop_last(base, 2) + "ying"
    }
    if str_ends(base, "e") {
        if !str_ends(base, "ee") {
            return str_drop_last(base, 1) + "ing"
        }
    }
    let last: String = str_last_char(base)
    if en_should_double_final(base) {
        return base + last + "ing"
    }
    return base + "ing"
}

// English noun pluralization (regular).
fn en_pluralize_regular(singular: String) -> String {
    if str_ends(singular, "s")  { return singular + "es" }
    if str_ends(singular, "x")  { return singular + "es" }
    if str_ends(singular, "z")  { return singular + "es" }
    if str_ends(singular, "ch") { return singular + "es" }
    if str_ends(singular, "sh") { return singular + "es" }
    let last: String = str_last_char(singular)
    if str_eq(last, "y") {
        let prev: String = str_drop_last(singular, 1)
        let prev_last: String = str_last_char(prev)
        if !is_vowel(prev_last) {
            return prev + "ies"
        }
    }
    if str_ends(singular, "fe") {
        return str_drop_last(singular, 2) + "ves"
    }
    return singular + "s"
}

// ── English verb conjugation ──────────────────────────────────────────────────
//
// tense:  "present" | "past" | "future" | "perfect" | "progressive"
// person: "first" | "second" | "third"
// number: "singular" | "plural"

fn en_verb_form(base: String, tense: String, person: String, number: String) -> String {
    let irreg: [String] = en_irregular_verb(base)
    let is_irreg: Bool = false
    if native_list_len(irreg) > 0 {
        let is_irreg = true
    }

    // "be" is fully irregular across all persons and tenses
    if str_eq(base, "be") {
        if str_eq(tense, "present") {
            if str_eq(number, "plural") { return "are" }
            if str_eq(person, "first")  { return "am" }
            if str_eq(person, "second") { return "are" }
            return "is"
        }
        if str_eq(tense, "past") {
            if str_eq(number, "plural") { return "were" }
            if str_eq(person, "second") { return "were" }
            return "was"
        }
        if str_eq(tense, "future")      { return "will be" }
        if str_eq(tense, "perfect")     { return "been" }
        if str_eq(tense, "progressive") { return "being" }
        return "be"
    }

    if str_eq(tense, "present") {
        if str_eq(person, "third") {
            if str_eq(number, "singular") {
                if is_irreg {
                    return native_list_get(irreg, 1)
                }
                return en_verb_3sg(base)
            }
        }
        return base
    }

    if str_eq(tense, "past") {
        if is_irreg { return native_list_get(irreg, 2) }
        return en_verb_past(base)
    }

    if str_eq(tense, "future") {
        return "will " + base
    }

    if str_eq(tense, "perfect") {
        if is_irreg { return native_list_get(irreg, 3) }
        return en_verb_past(base)
    }

    if str_eq(tense, "progressive") {
        if is_irreg { return native_list_get(irreg, 4) }
        return en_verb_gerund(base)
    }

    return base
}

// ── Determiner agreement ──────────────────────────────────────────────────────
// Language-independent interface; only English has a/an distinction today.

fn agree_determiner(det: String, noun: String) -> String {
    if str_eq(det, "a") {
        let first: String = str_slice(noun, 0, 1)
        let fl: String = str_to_lower(first)
        if is_vowel(fl) {
            return "an"
        }
        return "a"
    }
    return det
}

// ── Morphology engine: public interface ───────────────────────────────────────

// morph_pluralize: pluralize a noun given a language profile.
//
// For isolating languages: return base form (no inflection).
// For agglutinative languages: look up plural suffix from Engram; fall back to
//   base form when not found (Engram data drives agglutinative languages).
// For fusional English: apply English rule + irregular table.
// For other fusional languages: return base form (Engram data required).
// For polysynthetic: return base form.

fn morph_pluralize(noun: String, profile: [String]) -> String {
    let mtype: String = lang_get(profile, "morph_type")
    let code: String = lang_get(profile, "code")

    // ── Language-specific dispatch (0.3.0) ────────────────────────────────────
    // Delegate to the language module when one is available.  Each module
    // exposes a well-known public function; unknown codes fall through to the
    // morph_type strategy below.
    if str_eq(code, "es") { return es_pluralize(noun) }
    if str_eq(code, "fr") { return fr_pluralize(noun) }
    if str_eq(code, "de") { return de_noun_plural(noun, "unknown") }
    if str_eq(code, "ru") { return ru_noun_case(noun, "m", "nom", "pl") }
    if str_eq(code, "ja") { return noun }  // Japanese nouns do not pluralize
    if str_eq(code, "fi") { return fi_apply_case(noun, "nom", "pl") }
    if str_eq(code, "ar") { return ar_sound_plural(noun, "m") }
    if str_eq(code, "hi") { return hi_noun_direct(noun, hi_gender(noun), "pl") }
    if str_eq(code, "sw") { return sw_noun_plural(noun) }

    // ── morph_type fallback (isolating / agglutinative / fusional) ────────────

    if str_eq(mtype, "isolating") {
        // Isolating languages (zh, vi, etc.) do not inflect nouns.
        // Number is expressed through context, classifiers, or separate words.
        return noun
    }

    if str_eq(mtype, "agglutinative") {
        // Agglutinative languages attach suffixes that come from vocabulary data
        // in the Engram.  The engine provides the mechanism; the data (suffixes)
        // come from language-specific vocabulary nodes.  Without loaded Engram
        // data for the target language, we return the base form and let the
        // caller supply the inflected form directly.
        return noun
    }

    if str_eq(mtype, "fusional") {
        if str_eq(code, "en") {
            let irreg: String = en_irregular_plural(noun)
            if !str_eq(irreg, "") {
                return irreg
            }
            return en_pluralize_regular(noun)
        }
        // Other fusional languages: inflection tables are extensive and
        // language-specific.  Return base; Engram vocabulary nodes supply the
        // correct plural form for the target language.
        return noun
    }

    // polysynthetic and unknown: return base form
    return noun
}

// morph_map_canonical: map cross-lingual canonical verbs to language-specific forms.
//
// Semantic layers pass English canonical labels ("be", "have", "do") as predicates.
// Language modules expect their native infinitives ("ser", "sein", "olla" ...).
// This function normalises before dispatch so each module sees its native form.
//
// Zero-copula languages (ar, ja, hi) return "" for "be" — callers that receive ""
// should omit the verb from the surface form.

fn morph_map_canonical(verb: String, code: String) -> String {
    if str_eq(verb, "be") {
        if str_eq(code, "es") { return "ser"  }
        if str_eq(code, "fr") { return "etre" }   // ASCII alias; fr handles "etre" -> être
        if str_eq(code, "de") { return "sein" }
        if str_eq(code, "fi") { return "olla" }
        if str_eq(code, "ru") { return "byt"  }   // Latin transliteration for now
        if str_eq(code, "sw") { return "kuwa" }
    }
    return verb
}

// morph_conjugate: conjugate a verb given tense, person, number, and profile.
//
// tense:  "present" | "past" | "future" | "perfect" | "progressive"
// person: "first" | "second" | "third"
// number: "singular" | "plural"

fn morph_conjugate(verb: String, tense: String, person: String, number: String, profile: [String]) -> String {
    let mtype: String = lang_get(profile, "morph_type")
    let code: String = lang_get(profile, "code")

    // Map canonical English verb labels to language-specific infinitives before dispatch.
    let verb = morph_map_canonical(verb, code)

    // ── Language-specific dispatch (0.3.0) ────────────────────────────────────
    // Delegate to the language module when one is available.  Each module
    // exposes a well-known public function; unknown codes fall through to the
    // morph_type strategy below.
    if str_eq(code, "es") { return es_conjugate(verb, tense, person, number) }
    if str_eq(code, "fr") { return fr_conjugate(verb, tense, person, number) }
    if str_eq(code, "de") { return de_conjugate(verb, tense, person, number) }
    if str_eq(code, "ru") { return ru_conjugate(verb, tense, person, number, "unknown") }
    if str_eq(code, "ja") { return ja_conjugate(verb, "present") }
    if str_eq(code, "fi") { return fi_conjugate(verb, tense, person, number) }
    if str_eq(code, "ar") { return ar_conjugate(verb, tense, person, "m", number) }
    if str_eq(code, "hi") { return hi_conjugate(verb, tense, person, "m", number) }
    if str_eq(code, "sw") { return sw_conjugate(verb, person, number, "1", tense) }
    if str_eq(code, "la")  { return la_conjugate(verb, tense, person, number) }
    if str_eq(code, "he")  { return he_conjugate(verb, tense, person, "m", number) }
    if str_eq(code, "grc") { return grc_conjugate(verb, tense, person, number) }
    if str_eq(code, "ang") { return ang_conjugate(verb, tense, person, number) }
    if str_eq(code, "sa")  { return sa_conjugate(verb, tense, person, number) }
    if str_eq(code, "got") { return got_conjugate(verb, tense, person, number) }
    if str_eq(code, "non") { return non_conjugate(verb, tense, person, number) }
    if str_eq(code, "enm") { return enm_conjugate(verb, tense, person, number) }
    if str_eq(code, "pi")  { return pi_conjugate(verb, tense, person, number) }
    if str_eq(code, "fro") { return fro_conjugate(verb, tense, person, number) }
    if str_eq(code, "goh") { return goh_conjugate(verb, tense, person, number) }
    if str_eq(code, "sga") { return sga_conjugate(verb, tense, person, number) }
    if str_eq(code, "txb") { return txb_conjugate(verb, tense, person, number) }
    if str_eq(code, "peo") { return peo_conjugate(verb, tense, person, number) }
    if str_eq(code, "akk") { return akk_conjugate(verb, tense, person, number) }
    if str_eq(code, "uga") { return uga_conjugate(verb, tense, person, number) }
    if str_eq(code, "egy") { return egy_conjugate(verb, tense, person, number) }
    if str_eq(code, "sux") { return sux_conjugate(verb, tense, person, number) }
    if str_eq(code, "gez") { return gez_conjugate(verb, tense, person, number) }
    if str_eq(code, "cop") { return cop_conjugate(verb, tense, person, number) }

    // ── morph_type fallback (isolating / agglutinative / fusional) ────────────

    if str_eq(mtype, "isolating") {
        // Isolating languages do not inflect verbs.  Tense and aspect are
        // expressed through particles and separate words (e.g. 了 in Mandarin).
        return verb
    }

    if str_eq(mtype, "agglutinative") {
        // Agglutinative suffix chains.  The engine builds the suffix sequence;
        // the actual suffix strings come from Engram vocabulary nodes tagged with
        // the language code and the grammatical feature.
        // Without Engram-loaded suffix tables, return base form.
        return verb
    }

    if str_eq(mtype, "fusional") {
        if str_eq(code, "en") {
            return en_verb_form(verb, tense, person, number)
        }
        // Other fusional languages: return base form.
        // Engram vocabulary nodes carry the inflected forms for de, ru, ar, etc.
        return verb
    }

    // polysynthetic and unknown
    return verb
}

// morph_inflect: general inflection entry point.
//
// features: a semicolon-separated feature string, e.g. "plural" | "past;third;singular"
// Dispatches to the appropriate engine path based on the profile morph_type.

fn morph_inflect(word: String, features: String, profile: [String]) -> String {
    // Parse the first feature token to decide what kind of inflection to apply.
    let n: Int = str_len(features)
    if n == 0 {
        return word
    }

    // Scan to the first ';' to extract the leading feature token.
    let i: Int = 0
    let running: Bool = true
    while running {
        if i >= n {
            let running = false
        } else {
            let c: String = str_slice(features, i, i + 1)
            if str_eq(c, ";") {
                let running = false
            } else {
                let i = i + 1
            }
        }
    }
    let first_feat: String = str_slice(features, 0, i)

    if str_eq(first_feat, "plural") {
        return morph_pluralize(word, profile)
    }

    // For verb features: expect "tense;person;number"
    // Parse remaining tokens after the first ';'
    if i < n {
        let rest: String = str_slice(features, i + 1, n)
        // Find second ';'
        let j: Int = 0
        let rn: Int = str_len(rest)
        let running2: Bool = true
        while running2 {
            if j >= rn {
                let running2 = false
            } else {
                let c: String = str_slice(rest, j, j + 1)
                if str_eq(c, ";") {
                    let running2 = false
                } else {
                    let j = j + 1
                }
            }
        }
        let person: String = str_slice(rest, 0, j)
        let number: String = ""
        if j < rn {
            let number = str_slice(rest, j + 1, rn)
        }
        return morph_conjugate(word, first_feat, person, number, profile)
    }

    // Single token that is a tense (e.g. "past") with no person/number
    return morph_conjugate(word, first_feat, "third", "singular", profile)
}

// ── Backward-compatible English-only entry points ─────────────────────────────
//
// These preserve the original signatures for callers that were written before
// the language-profile system was introduced.

fn pluralize(singular: String) -> String {
    return morph_pluralize(singular, lang_default())
}

fn singularize(plural: String) -> String {
    let irreg: String = en_irregular_singular(plural)
    if !str_eq(irreg, "") {
        return irreg
    }
    if str_ends(plural, "ies") {
        return str_drop_last(plural, 3) + "y"
    }
    if str_ends(plural, "ves") {
        let stem: String = str_drop_last(plural, 3)
        let last_stem: String = str_last_char(stem)
        if str_eq(last_stem, "i") {
            return stem + "fe"
        }
        return stem + "f"
    }
    if str_ends(plural, "ches") { return str_drop_last(plural, 2) }
    if str_ends(plural, "shes") { return str_drop_last(plural, 2) }
    if str_ends(plural, "xes")  { return str_drop_last(plural, 2) }
    if str_ends(plural, "zes")  { return str_drop_last(plural, 2) }
    if str_ends(plural, "ses")  { return str_drop_last(plural, 2) }
    if str_ends(plural, "s") {
        return str_drop_last(plural, 1)
    }
    return plural
}

// verb_form: English verb conjugation (original signature).
fn verb_form(base: String, tense: String, person: String, number: String) -> String {
    return morph_conjugate(base, tense, person, number, lang_default())
}

// irregular_plural: English irregular plural lookup (backward compat).
fn irregular_plural(word: String) -> String {
    return en_irregular_plural(word)
}

// irregular_singular: English irregular singular lookup (backward compat).
fn irregular_singular(word: String) -> String {
    return en_irregular_singular(word)
}
