// morphology-sw.el - Swahili morphology for the NLG engine.
//
// Implements Swahili noun class detection, subject/object agreement prefixes,
// tense markers, verb conjugation, negation, noun pluralization, and adjective
// agreement.
//
// Swahili is an agglutinative SVO language that uses a noun class system
// (15+ classes) instead of grammatical gender.  Noun classes determine the
// agreement prefixes on verbs, adjectives, and other nominals.
//
// Key facts:
//   - No case endings — word order and prepositions handle relations
//   - Verb morphology: SUBJ-TENSE-OBJ-STEM(-final vowel)
//   - Latin script; tonal but tone unmarked in standard orthography
//
// Conventions used throughout:
//   class:   "1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"|"10"|"11"|"15"
//   person:  "1sg"|"2sg"|"3sg"|"1pl"|"2pl"|"3pl" | "1"|"2"|"3" + number "sg"/"pl"
//   tense:   "present"|"progressive"|"past"|"future"|"perfect"
//   number:  "sg" | "pl"
//
// Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with,
//             str_drop_last)

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

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

fn sw_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 sw_str_first_char(s: String) -> String {
    let n: Int = str_len(s)
    if n == 0 {
        return ""
    }
    return str_slice(s, 0, 1)
}

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

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

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

// ── Noun class detection ──────────────────────────────────────────────────────
//
// Returns the singular noun class number as a string.
// The noun class is inferred from the noun's prefix according to the standard
// Bantu noun class system used in Swahili.
//
// Class pairs (singular/plural):
//   Class 1/2  (M-WA):  m-/mw-  → wa-    people, animates
//   Class 3/4  (M-MI):  m-/mw-  → mi-    trees, plants, objects
//   Class 5/6  (JI-MA): ji-/j-  → ma-    fruits, augmentatives
//   Class 7/8  (KI-VI): ki-/ch- → vi-/vy- things, languages
//   Class 9/10 (N):     n-/m-/∅ → same   animals, loanwords
//   Class 11   (U):     u-/w-   → (10)   abstract nouns
//   Class 15   (KU):    ku-     → —      infinitives as nouns
//
// Disambiguation between class 1 and class 3 (both m-/mw- prefix):
//   Class 1 nouns refer to people/animates; class 3 to inanimate objects/plants.
//   A lexical lookup for common class 1 nouns handles the ambiguous cases.
//
// Note: Class 9/10 nouns often have no visible prefix (zero-prefix or nasal
// prefix that assimilates to the following consonant).

fn sw_is_class1_noun(noun: String) -> Bool {
    // Known class 1 (M-WA: human/animate) nouns
    if str_eq(noun, "mtu")      { return true }   // person
    if str_eq(noun, "mwanafunzi") { return true } // student
    if str_eq(noun, "mwalimu")  { return true }   // teacher
    if str_eq(noun, "mke")      { return true }   // wife/woman
    if str_eq(noun, "mume")     { return true }   // husband/man
    if str_eq(noun, "mtoto")    { return true }   // child
    if str_eq(noun, "mgeni")    { return true }   // guest/stranger
    if str_eq(noun, "mwana")    { return true }   // child/son
    if str_eq(noun, "mkubwa")   { return true }   // elder/adult
    if str_eq(noun, "mdogo")    { return true }   // young one
    if str_eq(noun, "mgonjwa")  { return true }   // sick person/patient
    if str_eq(noun, "mfanyakazi") { return true } // worker
    if str_eq(noun, "mkulima")  { return true }   // farmer
    if str_eq(noun, "mwimbaji") { return true }   // singer
    if str_eq(noun, "msomaji")  { return true }   // reader
    if str_eq(noun, "mwandishi") { return true }  // writer
    if str_eq(noun, "mpiganaji") { return true }  // fighter/warrior
    if str_eq(noun, "msaidizi") { return true }   // helper
    if str_eq(noun, "mpishi")   { return true }   // cook
    if str_eq(noun, "mwanasheria") { return true } // lawyer
    if str_eq(noun, "daktari")  { return true }   // doctor (cl.9, but animate)
    if str_eq(noun, "rafiki")   { return true }   // friend (cl.9)
    if str_eq(noun, "ndugu")    { return true }   // sibling/relative (cl.9)
    return false
}

fn sw_noun_class(noun: String) -> String {
    // Class 15: ku- prefix — infinitives used as nouns
    if sw_str_ends(noun, "ku") {
        if str_eq(sw_str_first2(noun), "ku") { return "15" }
    }
    if sw_str_first2(noun) == "ku" { return "15" }

    // Explicit class 15 check
    let p2: String = sw_str_first2(noun)
    if str_eq(p2, "ku") { return "15" }

    // Class 7/8 (KI-VI): ki- or ch-
    let p3: String = sw_str_first3(noun)
    if str_eq(p3, "ki-") { return "7" }
    if str_eq(p2, "ki")  { return "7" }
    if str_eq(p2, "ch")  { return "7" }   // e.g. chakula (food), choo (toilet)

    // Class 11 (U): u- or w- prefix — abstract nouns
    // Must check before class 1/3 m- prefix checks
    let p1: String = sw_str_first_char(noun)
    if str_eq(p1, "u") { return "11" }
    if str_eq(p1, "w") { return "11" }    // e.g. wimbo (song) class 11

    // Class 5/6 (JI-MA): ji- prefix or zero-prefix fruits/augmentatives
    if str_eq(p2, "ji") { return "5" }
    // Common class 5 nouns with zero prefix (j- before vowel)
    if str_eq(noun, "jicho")  { return "5" }  // eye
    if str_eq(noun, "jino")   { return "5" }  // tooth
    if str_eq(noun, "bega")   { return "5" }  // shoulder
    if str_eq(noun, "tunda")  { return "5" }  // fruit
    if str_eq(noun, "embe")   { return "5" }  // mango
    if str_eq(noun, "gari")   { return "5" }  // car/vehicle
    if str_eq(noun, "bei")    { return "5" }  // price
    if str_eq(noun, "sauti")  { return "5" }  // voice/sound
    if str_eq(noun, "thamani") { return "5" } // value

    // Class 1/3 (M- prefix): disambiguate by lexical lookup
    if str_eq(p1, "m") {
        if sw_is_class1_noun(noun) { return "1" }
        return "3"   // class 3: trees, plants, inanimate m- nouns
    }
    if str_eq(p2, "mw") {
        if sw_is_class1_noun(noun) { return "1" }
        return "3"
    }

    // Class 9/10 (N): nasal prefix or zero prefix — catch-all for most
    // loanwords and animal names
    // Many class 9 nouns start with: n, ny, ng, mb, nd, nj, nz, or bare vowel
    if str_eq(p2, "ny") { return "9" }
    if str_eq(p2, "ng") { return "9" }
    if str_eq(p2, "mb") { return "9" }
    if str_eq(p2, "nd") { return "9" }
    if str_eq(p2, "nj") { return "9" }
    if str_eq(p2, "nz") { return "9" }
    if str_eq(p1, "n")  { return "9" }

    // Common lexical class 9 nouns (animals, loanwords, etc.)
    if str_eq(noun, "paka")    { return "9" }   // cat
    if str_eq(noun, "mbwa")    { return "9" }   // dog
    if str_eq(noun, "simba")   { return "9" }   // lion
    if str_eq(noun, "tembo")   { return "9" }   // elephant
    if str_eq(noun, "nyoka")   { return "9" }   // snake
    if str_eq(noun, "samaki")  { return "9" }   // fish
    if str_eq(noun, "rafiki")  { return "9" }   // friend
    if str_eq(noun, "daktari") { return "9" }   // doctor
    if str_eq(noun, "serikali") { return "9" }  // government
    if str_eq(noun, "hospitali") { return "9" } // hospital
    if str_eq(noun, "shule")   { return "9" }   // school
    if str_eq(noun, "kanisa")  { return "9" }   // church
    if str_eq(noun, "ofisi")   { return "9" }   // office
    if str_eq(noun, "picha")   { return "9" }   // picture/photo
    if str_eq(noun, "sehemu")  { return "9" }   // part/section
    if str_eq(noun, "habari")  { return "9" }   // news
    if str_eq(noun, "nchi")    { return "9" }   // country/land
    if str_eq(noun, "bahari")  { return "9" }   // sea/ocean
    if str_eq(noun, "dunia")   { return "9" }   // world
    if str_eq(noun, "ardhi")   { return "9" }   // ground/earth

    // Default to class 9 for unknown nouns (most common catch-all)
    return "9"
}

// ── Subject agreement prefixes ────────────────────────────────────────────────
//
// Returns the subject prefix for verb conjugation.
//
// For personal pronouns: person "1","2","3" + number "sg","pl"
// For noun class agreement: person "3" + the noun's class
//
// Personal:
//   1sg (mimi)  → ni-
//   2sg (wewe)  → u-
//   3sg class 1 → a-
//   1pl (sisi)  → tu-
//   2pl (nyinyi)→ m-
//   3pl class 2 → wa-
//
// Noun class 3sg:
//   cl.1  → a-     cl.2  → wa-
//   cl.3  → u-     cl.4  → i-
//   cl.5  → li-    cl.6  → ya-
//   cl.7  → ki-    cl.8  → vi-
//   cl.9  → i-     cl.10 → zi-
//   cl.11 → u-     cl.15 → ku-

fn sw_subj_prefix(person: String, number: String, noun_class: String) -> String {
    // First and second person always use personal prefixes regardless of noun class
    if str_eq(person, "1") {
        if str_eq(number, "sg") { return "ni" }
        return "tu"
    }
    if str_eq(person, "2") {
        if str_eq(number, "sg") { return "u" }
        return "m"
    }

    // Third person: agree with noun class
    // Plural classes
    if str_eq(number, "pl") {
        if str_eq(noun_class, "1") { return "wa" }   // class 2 (wa-)
        if str_eq(noun_class, "2") { return "wa" }
        if str_eq(noun_class, "3") { return "i" }    // class 4 (i-)
        if str_eq(noun_class, "4") { return "i" }
        if str_eq(noun_class, "5") { return "ya" }   // class 6 (ya-)
        if str_eq(noun_class, "6") { return "ya" }
        if str_eq(noun_class, "7") { return "vi" }   // class 8 (vi-)
        if str_eq(noun_class, "8") { return "vi" }
        if str_eq(noun_class, "9") { return "zi" }   // class 10 (zi-)
        if str_eq(noun_class, "10") { return "zi" }
        if str_eq(noun_class, "11") { return "zi" }  // class 11 pl uses cl.10
        return "zi"
    }

    // Singular classes
    if str_eq(noun_class, "1")  { return "a" }
    if str_eq(noun_class, "3")  { return "u" }
    if str_eq(noun_class, "4")  { return "i" }
    if str_eq(noun_class, "5")  { return "li" }
    if str_eq(noun_class, "6")  { return "ya" }
    if str_eq(noun_class, "7")  { return "ki" }
    if str_eq(noun_class, "8")  { return "vi" }
    if str_eq(noun_class, "9")  { return "i" }
    if str_eq(noun_class, "10") { return "zi" }
    if str_eq(noun_class, "11") { return "u" }
    if str_eq(noun_class, "15") { return "ku" }
    // Default: third person animate sg
    return "a"
}

// ── Object agreement prefixes ─────────────────────────────────────────────────
//
// Object infixes are inserted between tense marker and verb stem.
// They mirror the subject prefixes with minor differences.
//
//   1sg → ni-    2sg → ku-    3sg cl.1 → m-/mw-
//   1pl → tu-    2pl → wa-    3pl cl.2 → wa-
//   cl.3 sg → u-    cl.4 pl → i-
//   cl.5 sg → li-   cl.6 pl → ya-
//   cl.7 sg → ki-   cl.8 pl → vi-
//   cl.9 sg → i-    cl.10 pl → zi-
//   cl.11 → u-      cl.15 → ku-

fn sw_obj_prefix(person: String, number: String, noun_class: String) -> String {
    if str_eq(person, "1") {
        if str_eq(number, "sg") { return "ni" }
        return "tu"
    }
    if str_eq(person, "2") {
        if str_eq(number, "sg") { return "ku" }
        return "wa"
    }
    // Third person object
    if str_eq(number, "pl") {
        if str_eq(noun_class, "1") { return "wa" }
        if str_eq(noun_class, "2") { return "wa" }
        if str_eq(noun_class, "3") { return "i" }
        if str_eq(noun_class, "4") { return "i" }
        if str_eq(noun_class, "5") { return "ya" }
        if str_eq(noun_class, "6") { return "ya" }
        if str_eq(noun_class, "7") { return "vi" }
        if str_eq(noun_class, "8") { return "vi" }
        if str_eq(noun_class, "9") { return "zi" }
        if str_eq(noun_class, "10") { return "zi" }
        return "wa"
    }
    // Singular
    if str_eq(noun_class, "1")  { return "m" }
    if str_eq(noun_class, "3")  { return "u" }
    if str_eq(noun_class, "5")  { return "li" }
    if str_eq(noun_class, "7")  { return "ki" }
    if str_eq(noun_class, "9")  { return "i" }
    if str_eq(noun_class, "11") { return "u" }
    if str_eq(noun_class, "15") { return "ku" }
    return "m"
}

// ── Tense markers ─────────────────────────────────────────────────────────────
//
// Tense markers are inserted between the subject prefix and the verb stem.
//
//   "present"     → -a-  (habitual/simple present; final vowel -a)
//   "progressive" → -na- (present progressive)
//   "past"        → -li- (simple past)
//   "future"      → -ta- (future)
//   "perfect"     → -me- (present perfect)
//   "remote_past" → -li- (same marker; distinction via context or -ku-)
//   "subjunctive" → (no tense marker; final vowel changes to -e)

fn sw_tense_marker(tense: String) -> String {
    if str_eq(tense, "present")     { return "a" }
    if str_eq(tense, "progressive") { return "na" }
    if str_eq(tense, "past")        { return "li" }
    if str_eq(tense, "future")      { return "ta" }
    if str_eq(tense, "perfect")     { return "me" }
    if str_eq(tense, "subjunctive") { return "" }
    if str_eq(tense, "remote_past") { return "li" }
    // Default to progressive marker
    return "na"
}

// ── Final vowel (verb ending) ─────────────────────────────────────────────────
//
// The final vowel of the verb stem changes based on tense and negation.
//
// Positive forms:
//   present habitual / progressive / past / future / perfect: -a
//   subjunctive:  -e
//
// Negative forms:
//   present:  -i  (the negative present uses -i instead of -a)
//   past:     -a  (negative past: ha- prefix + -ku- tense + stem + -a)
//   others:   -e  (negative subjunctive)
//
// Note: For most tenses the final vowel is simply -a.  Only the negative present
// and subjunctive change it.

fn sw_verb_final(tense: String, negative: Bool) -> String {
    if negative {
        if str_eq(tense, "present")     { return "i" }
        if str_eq(tense, "progressive") { return "i" }
        if str_eq(tense, "subjunctive") { return "e" }
        return "a"
    }
    if str_eq(tense, "subjunctive") { return "e" }
    return "a"
}

// ── Negative subject prefix ───────────────────────────────────────────────────
//
// Negation in Swahili is expressed by replacing the subject prefix with a
// negative counterpart (ha- for most; si- for 1sg).
//
// Positive → Negative subject prefixes:
//   ni-  → si-    (1sg)
//   u-   → hu-    (2sg)
//   a-   → ha-    (3sg cl.1)
//   tu-  → hatu-  (1pl)
//   m-   → ham-   (2pl)
//   wa-  → hawa-  (3pl cl.2)
//   other class 3sg prefixes: ha + prefix (e.g. ha+u = hau, ha+li = hali)

fn sw_neg_subj_prefix(person: String, number: String, noun_class: String) -> String {
    if str_eq(person, "1") {
        if str_eq(number, "sg") { return "si" }
        return "hatu"
    }
    if str_eq(person, "2") {
        if str_eq(number, "sg") { return "hu" }
        return "ham"
    }
    // Third person negative = ha + positive subject prefix
    let pos: String = sw_subj_prefix(person, number, noun_class)
    return "ha" + pos
}

// ── Stem extraction ───────────────────────────────────────────────────────────
//
// Swahili infinitives begin with ku-.  The verb stem is the infinitive minus ku-.
// For verbs where the stem itself starts with a vowel, the ku- is retained in
// some contexts (kuomba, kusoma etc.) but stripped for conjugation.
//
// Special cases:
//   kula  (to eat)  → stem: l-  (kula → la in conjugation: anakula)
//   kuwa  (to be)   → stem: wa  (but equatorial forms are suppletive: ni/u/ni)
//   kwenda (to go)  → stem: end (kw + end → enda)
//   kuja  (to come) → stem: ja

fn sw_verb_stem(infinitive: String) -> String {
    if str_eq(infinitive, "kula")    { return "l" }   // eat — irregular: anakula
    if str_eq(infinitive, "kuwa")    { return "wa" }  // be
    if str_eq(infinitive, "kwenda")  { return "enda" }
    if str_eq(infinitive, "kuja")    { return "ja" }
    if str_eq(infinitive, "kusoma")  { return "soma" }
    if str_eq(infinitive, "kusema")  { return "sema" }
    if str_eq(infinitive, "kuona")   { return "ona" }
    if str_eq(infinitive, "kufanya") { return "fanya" }
    if str_eq(infinitive, "kutaka")  { return "taka" }
    if str_eq(infinitive, "kujua")   { return "jua" }
    if str_eq(infinitive, "kupata")  { return "pata" }
    if str_eq(infinitive, "kuambia") { return "ambia" }
    if str_eq(infinitive, "kuleta")  { return "leta" }
    if str_eq(infinitive, "kuweka")  { return "weka" }
    if str_eq(infinitive, "kuingia") { return "ingia" }
    if str_eq(infinitive, "kutoka")  { return "toka" }
    if str_eq(infinitive, "kupiga")  { return "piga" }
    if str_eq(infinitive, "kuimba")  { return "imba" }
    if str_eq(infinitive, "kucheza") { return "cheza" }
    if str_eq(infinitive, "kulala")  { return "lala" }
    if str_eq(infinitive, "kuandika") { return "andika" }
    if str_eq(infinitive, "kununua") { return "nunua" }
    if str_eq(infinitive, "kuuza")   { return "uza" }
    if str_eq(infinitive, "kupenda") { return "penda" }
    if str_eq(infinitive, "kuchukua") { return "chukua" }
    if str_eq(infinitive, "kulipa")  { return "lipa" }
    if str_eq(infinitive, "kusikia") { return "sikia" }
    if str_eq(infinitive, "kuamka")  { return "amka" }
    if str_eq(infinitive, "kukaa")   { return "kaa" }
    if str_eq(infinitive, "kurudi")  { return "rudi" }
    if str_eq(infinitive, "kushinda") { return "shinda" }
    if str_eq(infinitive, "kusaidia") { return "saidia" }
    if str_eq(infinitive, "kuzungumza") { return "zungumza" }
    if str_eq(infinitive, "kupumzika") { return "pumzika" }
    if str_eq(infinitive, "kufika")  { return "fika" }
    if str_eq(infinitive, "kuomba")  { return "omba" }
    if str_eq(infinitive, "kushukuru") { return "shukuru" }
    // Generic: strip leading ku (2 chars) or kw (for kwenda etc.)
    if sw_str_first2(infinitive) == "ku" {
        return str_slice(infinitive, 2, str_len(infinitive))
    }
    if sw_str_first2(infinitive) == "kw" {
        return str_slice(infinitive, 2, str_len(infinitive))
    }
    return infinitive
}

// ── Positive verb conjugation ─────────────────────────────────────────────────
//
// Structure: SUBJ_PREFIX + TENSE_MARKER + STEM + FINAL_VOWEL
//
// Examples:
//   anasoma    = a (3sg cl.1) + na (prog) + soma (read) + [stem ends -a]
//   nilikula   = ni (1sg) + li (past) + ku (special) + la (kula stem)
//   tutasema   = tu (1pl) + ta (future) + sema (say) + [stem ends -a]
//   amesoma    = a (3sg) + me (perfect) + soma + [final -a]
//
// Special case: kula — the stem is bare "l", and the full conjugation inserts
// ku before the stem in most tenses: a-na-ku-la, a-li-ku-la, a-ta-ku-la
// but a-me-l-a is not standard — standard is amekula.

fn sw_conjugate(verb_stem: String, person: String, number: String, noun_class: String, tense: String) -> String {
    let subj: String = sw_subj_prefix(person, number, noun_class)
    let tm: String = sw_tense_marker(tense)
    let fv: String = sw_verb_final(tense, false)

    // kula (eat) — stem is "l" but conjugates as "kula" after prefix+tense
    if str_eq(verb_stem, "l") {
        if str_eq(tm, "") {
            return subj + "kula"
        }
        return subj + tm + "kula"
    }

    // kuwa (be) — present equational forms are suppletive
    if str_eq(verb_stem, "wa") {
        if str_eq(tense, "present") {
            // Equational: ni/u/ni for 1sg/2sg/3sg; tu/m/ni for pl
            if str_eq(person, "1") {
                if str_eq(number, "sg") { return "ni" }
                return "tu ni"
            }
            if str_eq(person, "2") {
                if str_eq(number, "sg") { return "u" }
                return "m ni"
            }
            // 3sg/3pl: use class-based copula
            if str_eq(number, "sg") {
                // Locative/existential forms: niko, uko, yuko etc. (class-based)
                if str_eq(noun_class, "1")  { return "yuko" }
                if str_eq(noun_class, "3")  { return "upo" }
                if str_eq(noun_class, "5")  { return "lipo" }
                if str_eq(noun_class, "7")  { return "kipo" }
                if str_eq(noun_class, "9")  { return "ipo" }
                if str_eq(noun_class, "11") { return "upo" }
                return "yuko"
            }
            // plural
            if str_eq(noun_class, "1")  { return "wako" }
            if str_eq(noun_class, "3")  { return "ipo" }
            if str_eq(noun_class, "5")  { return "yapo" }
            if str_eq(noun_class, "7")  { return "vipo" }
            if str_eq(noun_class, "9")  { return "zipo" }
            return "wako"
        }
        // Progressive kuwa: niko/uko/yuko (existence/location)
        if str_eq(tense, "progressive") {
            if str_eq(person, "1") {
                if str_eq(number, "sg") { return "niko" }
                return "tuko"
            }
            if str_eq(person, "2") {
                if str_eq(number, "sg") { return "uko" }
                return "mko"
            }
            if str_eq(number, "sg") {
                if str_eq(noun_class, "1") { return "yuko" }
                return subj + "ko"
            }
            if str_eq(noun_class, "1") { return "wako" }
            return subj + "ko"
        }
        // Past/future/perfect kuwa: regular
    }

    // Regular conjugation
    let stem_final: String = sw_str_last_char(verb_stem)

    // If stem already ends in the correct final vowel, don't double it
    if str_eq(fv, "a") {
        if str_eq(stem_final, "a") {
            // Stem ends in -a: result is subj+tm+stem (final -a already present)
            if str_eq(tm, "") {
                return subj + verb_stem
            }
            return subj + tm + verb_stem
        }
    }

    if str_eq(tm, "") {
        return subj + verb_stem + fv
    }
    return subj + tm + verb_stem + fv
}

// ── Negative verb conjugation ─────────────────────────────────────────────────
//
// Negative structure: NEG_SUBJ_PREFIX + (tense) + STEM + FINAL_VOWEL_I
//
// Negative present:
//   ha- prefix replaces subject prefix; final vowel -i
//   e.g. hasomi (he/she doesn't read), sisomi (I don't read)
//
// Negative past:
//   ha- prefix + -ku- tense infix + stem + -a
//   e.g. hakusoma (he/she didn't read), sikusoma (I didn't read)
//
// Negative future:
//   ha- prefix + -ta- + stem + -i (or sometimes -a; -i is more standard)
//   e.g. hatasoma (he/she won't read)
//
// Negative perfect:
//   ha- prefix + -ja- (not yet) + stem + -a
//   e.g. hajaenda (he/she hasn't gone yet)

fn sw_negative(verb_stem: String, person: String, number: String, noun_class: String, tense: String) -> String {
    let neg_subj: String = sw_neg_subj_prefix(person, number, noun_class)

    // kula special gram_case
    if str_eq(verb_stem, "l") {
        if str_eq(tense, "past")   { return neg_subj + "kukula" }
        if str_eq(tense, "perfect") { return neg_subj + "jakula" }
        return neg_subj + "kuli"
    }

    if str_eq(tense, "present") {
        let fv: String = sw_verb_final("present", true)  // -i
        let stem_no_a: String = verb_stem
        // If stem ends in -a, drop it before adding -i (some grammars)
        // Standard: ha + stem-minus-final-a + i
        let slen: Int = str_len(verb_stem)
        if slen > 0 {
            let last: String = sw_str_last_char(verb_stem)
            if str_eq(last, "a") {
                return neg_subj + sw_str_drop_last(verb_stem, 1) + fv
            }
        }
        return neg_subj + verb_stem + fv
    }

    if str_eq(tense, "past") {
        // Negative past: ha + ku + stem + a
        return neg_subj + "ku" + verb_stem + "a"
    }

    if str_eq(tense, "future") {
        // Negative future: ha + ta + stem + i (standard)
        let fv: String = sw_verb_final("present", true)  // -i
        return neg_subj + "ta" + verb_stem + fv
    }

    if str_eq(tense, "perfect") {
        // Negative perfect "not yet": ha + ja + stem + a
        return neg_subj + "ja" + verb_stem + "a"
    }

    if str_eq(tense, "progressive") {
        // Negative progressive: same as negative present
        let fv: String = sw_verb_final("present", true)
        let slen: Int = str_len(verb_stem)
        if slen > 0 {
            let last: String = sw_str_last_char(verb_stem)
            if str_eq(last, "a") {
                return neg_subj + sw_str_drop_last(verb_stem, 1) + fv
            }
        }
        return neg_subj + verb_stem + fv
    }

    // Default negative: ha + stem + i
    return neg_subj + verb_stem + "i"
}

// ── Noun plural formation ─────────────────────────────────────────────────────
//
// Swahili plurals are formed by changing the noun class prefix.
//
// Class 1 (m-/mw-) → Class 2 (wa-):  mtu → watu, mwalimu → walimu
// Class 3 (m-/mw-) → Class 4 (mi-):  mti → miti, mwamba → miamba
// Class 5 (ji-/∅)  → Class 6 (ma-):  jicho → macho, tunda → matunda
// Class 7 (ki-/ch-)→ Class 8 (vi-/vy-): kitu → vitu, chakula → vyakula
// Class 9 (n-/∅)   → Class 10 (n-/∅): same prefix — no change in prefix;
//                    some class 9 nouns add ma- (ma- class) in plural
// Class 11 (u-)    → Class 10 (n-/∅): ubao → mbao
//
// Known lexical exceptions are handled first.

fn sw_noun_plural(noun: String) -> String {
    // ── Lexical irregulars ──────────────────────────────────────────────────
    if str_eq(noun, "mtu")      { return "watu" }
    if str_eq(noun, "mtoto")    { return "watoto" }
    if str_eq(noun, "mke")      { return "wake" }
    if str_eq(noun, "mume")     { return "waume" }
    if str_eq(noun, "mwana")    { return "wana" }
    if str_eq(noun, "mwalimu")  { return "walimu" }
    if str_eq(noun, "mgeni")    { return "wageni" }
    if str_eq(noun, "mwanafunzi") { return "wanafunzi" }
    if str_eq(noun, "mfanyakazi") { return "wafanyakazi" }
    if str_eq(noun, "mkulima")  { return "wakulima" }
    if str_eq(noun, "mgonjwa")  { return "wagonjwa" }
    if str_eq(noun, "jicho")    { return "macho" }
    if str_eq(noun, "jino")     { return "meno" }
    if str_eq(noun, "bega")     { return "mabega" }
    if str_eq(noun, "tunda")    { return "matunda" }
    if str_eq(noun, "gari")     { return "magari" }
    if str_eq(noun, "embe")     { return "maembe" }
    if str_eq(noun, "wimbo")    { return "nyimbo" }   // class 11 → 10
    if str_eq(noun, "ubao")     { return "mbao" }
    if str_eq(noun, "ugonjwa")  { return "magonjwa" }
    if str_eq(noun, "uso")      { return "nyuso" }
    if str_eq(noun, "ukuta")    { return "kuta" }
    if str_eq(noun, "ulimi")    { return "ndimi" }
    if str_eq(noun, "upande")   { return "pande" }
    if str_eq(noun, "uwezo")    { return "nguvu" }  // approximate
    // Class 9/10 nouns: same form sg/pl (zero-change class)
    if str_eq(noun, "paka")    { return "paka" }
    if str_eq(noun, "samaki")  { return "samaki" }
    if str_eq(noun, "rafiki")  { return "rafiki" }
    if str_eq(noun, "daktari") { return "madaktari" }  // some use ma- pl
    if str_eq(noun, "habari")  { return "habari" }
    if str_eq(noun, "nchi")    { return "nchi" }
    if str_eq(noun, "bahari")  { return "bahari" }
    if str_eq(noun, "shule")   { return "shule" }
    if str_eq(noun, "hospitali") { return "hospitali" }
    if str_eq(noun, "ofisi")   { return "ofisi" }
    if str_eq(noun, "serikali") { return "serikali" }

    // ── Productive rules ────────────────────────────────────────────────────

    // Class 1 (m- people): m- → wa-
    if sw_is_class1_noun(noun) {
        if str_eq(sw_str_first2(noun), "mw") {
            return "wa" + str_slice(noun, 2, str_len(noun))
        }
        if str_eq(sw_str_first_char(noun), "m") {
            return "wa" + str_slice(noun, 1, str_len(noun))
        }
    }

    // Class 7 (ki-/ch-) → Class 8 (vi-/vy-)
    let p2: String = sw_str_first2(noun)
    if str_eq(p2, "ki") {
        return "vi" + str_slice(noun, 2, str_len(noun))
    }
    if str_eq(p2, "ch") {
        return "vy" + str_slice(noun, 2, str_len(noun))
    }

    // Class 5 (ji-) → Class 6 (ma-)
    if str_eq(p2, "ji") {
        return "ma" + str_slice(noun, 2, str_len(noun))
    }

    // Class 11 (u-/w-) → Class 10 (n-/∅): drop u, may add n
    let p1: String = sw_str_first_char(noun)
    if str_eq(p1, "u") {
        // Most u- class nouns: drop u → bare stem (class 10 zero prefix)
        return str_slice(noun, 1, str_len(noun))
    }

    // Class 3 (m-/mw- inanimate) → Class 4 (mi-)
    if str_eq(p1, "m") {
        if str_eq(p2, "mw") {
            return "mi" + str_slice(noun, 2, str_len(noun))
        }
        return "mi" + str_slice(noun, 1, str_len(noun))
    }

    // Class 9/10: no prefix change (same form)
    return noun
}

// ── Adjective agreement ───────────────────────────────────────────────────────
//
// Adjectives in Swahili take a class agreement prefix that matches the noun
// they modify.  The prefix is placed before the adjective stem.
//
// Agreement prefixes (singular):
//   cl.1  → m-/mw-   cl.2  → wa-
//   cl.3  → m-/mw-   cl.4  → mi-
//   cl.5  → (j-)l-   cl.6  → ma-
//   cl.7  → ki-/ch-  cl.8  → vi-/vy-
//   cl.9  → (n-)     cl.10 → (n-)
//   cl.11 → mw-      cl.15 → ku-
//
// The adjective prefix for class 1/3 singular before a vowel-initial stem
// uses mw- instead of m-.
//
// number: "sg" | "pl"
// noun_class: singular class of the noun (the function applies the correct
//             plural class internally when number == "pl")

fn sw_adj_prefix(noun_class: String, number: String) -> String {
    // Plural classes
    if str_eq(number, "pl") {
        if str_eq(noun_class, "1") { return "wa" }   // cl.2
        if str_eq(noun_class, "2") { return "wa" }
        if str_eq(noun_class, "3") { return "mi" }   // cl.4
        if str_eq(noun_class, "4") { return "mi" }
        if str_eq(noun_class, "5") { return "ma" }   // cl.6
        if str_eq(noun_class, "6") { return "ma" }
        if str_eq(noun_class, "7") { return "vi" }   // cl.8
        if str_eq(noun_class, "8") { return "vi" }
        if str_eq(noun_class, "9") { return "n" }    // cl.10 (nasal)
        if str_eq(noun_class, "10") { return "n" }
        if str_eq(noun_class, "11") { return "n" }   // cl.10 in plural
        return "wa"
    }

    // Singular classes
    if str_eq(noun_class, "1")  { return "m" }
    if str_eq(noun_class, "3")  { return "m" }
    if str_eq(noun_class, "4")  { return "mi" }
    if str_eq(noun_class, "5")  { return "j" }   // l- before consonant, j- before vowel
    if str_eq(noun_class, "6")  { return "ma" }
    if str_eq(noun_class, "7")  { return "ki" }
    if str_eq(noun_class, "8")  { return "vi" }
    if str_eq(noun_class, "9")  { return "n" }
    if str_eq(noun_class, "10") { return "n" }
    if str_eq(noun_class, "11") { return "mw" }
    if str_eq(noun_class, "15") { return "ku" }
    return ""
}

// sw_agree_adj: apply the agreement prefix to an adjective stem.
//
// adj_stem:   the adjective without any class prefix (e.g. "zuri", "kubwa", "dogo")
// noun_class: class of the noun being modified (singular class number as string)
// number:     "sg" | "pl"
//
// Returns the fully prefixed adjective form.
//
// Note: many common adjectives in Swahili are Arabic/Bantu borrowings and do
// not take class prefixes (e.g. "nzuri" — beautiful, "kubwa" — big, "dogo" — small).
// Prefix agreement applies primarily to native Bantu adjective stems and
// demonstratives.  This function applies the prefix mechanically; for adjectives
// that are invariant loanwords the caller should use the bare form.

fn sw_agree_adj(adj_stem: String, noun_class: String, number: String) -> String {
    // Invariant adjectives (no class prefix required in standard usage)
    if str_eq(adj_stem, "nzuri")  { return "nzuri" }   // good/beautiful
    if str_eq(adj_stem, "baya")   { return "baya" }    // bad
    if str_eq(adj_stem, "safi")   { return "safi" }    // clean
    if str_eq(adj_stem, "chafu")  { return "chafu" }   // dirty
    if str_eq(adj_stem, "ghali")  { return "ghali" }   // expensive
    if str_eq(adj_stem, "rahisi") { return "rahisi" }  // cheap/easy
    if str_eq(adj_stem, "mzuri")  { return sw_adj_prefix(noun_class, number) + "zuri" }

    // Apply class prefix to Bantu adjective stems
    let prefix: String = sw_adj_prefix(noun_class, number)
    if str_eq(prefix, "") {
        return adj_stem
    }

    // cl.1/3 prefix m- before consonant, mw- before vowel
    if str_eq(prefix, "m") {
        let first: String = sw_str_first_char(adj_stem)
        if str_eq(first, "a") { return "mw" + adj_stem }
        if str_eq(first, "e") { return "mw" + adj_stem }
        if str_eq(first, "i") { return "mw" + adj_stem }
        if str_eq(first, "o") { return "mw" + adj_stem }
        if str_eq(first, "u") { return "mw" + adj_stem }
        return "m" + adj_stem
    }

    // cl.5 prefix j- before vowel, l- before consonant
    if str_eq(prefix, "j") {
        let first: String = sw_str_first_char(adj_stem)
        if str_eq(first, "a") { return "j" + adj_stem }
        if str_eq(first, "e") { return "j" + adj_stem }
        if str_eq(first, "i") { return "j" + adj_stem }
        if str_eq(first, "o") { return "j" + adj_stem }
        if str_eq(first, "u") { return "j" + adj_stem }
        return "l" + adj_stem
    }

    return prefix + adj_stem
}

// ── Demonstrative agreement ───────────────────────────────────────────────────
//
// Swahili demonstratives agree with the noun class.
// "this" (proximal): huyu (cl.1/2), huu (cl.3/11), hili (cl.5), hiki (cl.7),
//                    hii (cl.9), huu (cl.11)
// "that" (distal):   yule (cl.1/2), ule (cl.3/11), lile (cl.5), kile (cl.7),
//                    ile (cl.9)
//
// proximity: "near" | "far"

fn sw_demonstrative(noun_class: String, number: String, proximity: String) -> String {
    if str_eq(proximity, "near") {
        if str_eq(number, "pl") {
            if str_eq(noun_class, "1") { return "hawa" }
            if str_eq(noun_class, "3") { return "hii" }
            if str_eq(noun_class, "5") { return "haya" }
            if str_eq(noun_class, "7") { return "hivi" }
            if str_eq(noun_class, "9") { return "hizi" }
            return "hawa"
        }
        if str_eq(noun_class, "1")  { return "huyu" }
        if str_eq(noun_class, "3")  { return "huu" }
        if str_eq(noun_class, "5")  { return "hili" }
        if str_eq(noun_class, "7")  { return "hiki" }
        if str_eq(noun_class, "9")  { return "hii" }
        if str_eq(noun_class, "11") { return "huu" }
        if str_eq(noun_class, "15") { return "huku" }
        return "hii"
    }
    // distal
    if str_eq(number, "pl") {
        if str_eq(noun_class, "1") { return "wale" }
        if str_eq(noun_class, "3") { return "ile" }
        if str_eq(noun_class, "5") { return "yale" }
        if str_eq(noun_class, "7") { return "vile" }
        if str_eq(noun_class, "9") { return "zile" }
        return "wale"
    }
    if str_eq(noun_class, "1")  { return "yule" }
    if str_eq(noun_class, "3")  { return "ule" }
    if str_eq(noun_class, "5")  { return "lile" }
    if str_eq(noun_class, "7")  { return "kile" }
    if str_eq(noun_class, "9")  { return "ile" }
    if str_eq(noun_class, "11") { return "ule" }
    if str_eq(noun_class, "15") { return "kule" }
    return "ile"
}

// ── Swahili copula (kuwa — to be) ─────────────────────────────────────────────
//
// The copula in Swahili is complex:
//
// Equational ("X is Y", noun predicate):
//   ni (is/am/are) — invariant for present equational
//   si — negative present equational
//
// Locative/existential ("X is [somewhere]", with -ko/-po/-mo):
//   niko/uko/yuko/tuko/mko/wako — with -ko (definite location)
//   nipo/upo/yupo/tupo/mpo/wapo — with -po (near location)
//   nimo/umo/yumo/tumo/mmo/wamo — with -mo (inside)
//
// sw_copula_present: returns the present copula form.
// use_case: "equative" | "locative"

fn sw_copula_present(person: String, number: String, use_case: String) -> String {
    if str_eq(use_case, "equative") {
        if str_eq(person, "1") { return "ni" }
        if str_eq(person, "2") { return "ni" }
        return "ni"
    }
    // locative -ko (most common)
    if str_eq(person, "1") {
        if str_eq(number, "sg") { return "niko" }
        return "tuko"
    }
    if str_eq(person, "2") {
        if str_eq(number, "sg") { return "uko" }
        return "mko"
    }
    if str_eq(number, "sg") { return "yuko" }
    return "wako"
}

// sw_copula_neg_present: negative present copula.
// equative: si (invariant for all persons in negative equational)
fn sw_copula_neg_present(person: String, number: String) -> String {
    if str_eq(person, "1") {
        if str_eq(number, "sg") { return "si" }
        return "si"
    }
    if str_eq(person, "2") {
        if str_eq(number, "sg") { return "si" }
        return "si"
    }
    return "si"
}
