// grammar.el - Grammar engine: syntactic structure, word order, phrase assembly.
//
// Language-specific word order and question strategy are driven by the language
// profile, not hardcoded.  The slot map format (GramSpec) is universal; a "lang"
// key carries the ISO 639-1 code so every downstream function can resolve the
// active profile.
//
// GramSpec slot keys:
//   intent      - "assert" | "question" | "command"
//   agent       - subject referent string
//   predicate   - verb base form
//   patient     - object noun phrase (optional)
//   location    - prepositional phrase (optional)
//   tense       - "present" | "past" | "future"
//   aspect      - "simple" | "progressive" | "perfect"
//   lang        - ISO 639-1 code (default "en")
//   verb_surf   - conjugated verb surface form (computed)
//   aux_surf    - auxiliary surface form (computed)
//
// Depends on: language-profile

// ── Slot map helpers ──────────────────────────────────────────────────────────

fn slots_get(slots: [String], key: String) -> String {
    let n: Int = native_list_len(slots)
    let i: Int = 0
    while i < n - 1 {
        let k: String = native_list_get(slots, i)
        if str_eq(k, key) {
            return native_list_get(slots, i + 1)
        }
        let i = i + 2
    }
    return ""
}

fn slots_set(slots: [String], key: String, val: String) -> [String] {
    let n: Int = native_list_len(slots)
    let result: [String] = native_list_empty()
    let found: Bool = false
    let i: Int = 0
    while i < n - 1 {
        let k: String = native_list_get(slots, i)
        let v: String = native_list_get(slots, i + 1)
        if str_eq(k, key) {
            let result = native_list_append(result, k)
            let result = native_list_append(result, val)
            let found = true
        } else {
            let result = native_list_append(result, k)
            let result = native_list_append(result, v)
        }
        let i = i + 2
    }
    if !found {
        let result = native_list_append(result, key)
        let result = native_list_append(result, val)
    }
    return result
}

fn make_slots(k0: String, v0: String) -> [String] {
    let r: [String] = native_list_empty()
    let r = native_list_append(r, k0)
    let r = native_list_append(r, v0)
    return r
}

fn make_slots2(k0: String, v0: String, k1: String, v1: String) -> [String] {
    let r: [String] = make_slots(k0, v0)
    let r = native_list_append(r, k1)
    let r = native_list_append(r, v1)
    return r
}

fn make_slots3(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String) -> [String] {
    let r: [String] = make_slots2(k0, v0, k1, v1)
    let r = native_list_append(r, k2)
    let r = native_list_append(r, v2)
    return r
}

fn make_slots4(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String, k3: String, v3: String) -> [String] {
    let r: [String] = make_slots3(k0, v0, k1, v1, k2, v2)
    let r = native_list_append(r, k3)
    let r = native_list_append(r, v3)
    return r
}

fn make_slots5(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String, k3: String, v3: String, k4: String, v4: String) -> [String] {
    let r: [String] = make_slots4(k0, v0, k1, v1, k2, v2, k3, v3)
    let r = native_list_append(r, k4)
    let r = native_list_append(r, v4)
    return r
}

// ── Grammar rule catalog ──────────────────────────────────────────────────────

fn rule_id(rule: [String]) -> String {
    return native_list_get(rule, 0)
}

fn rule_lhs(rule: [String]) -> String {
    return native_list_get(rule, 1)
}

fn rule_rhs_len(rule: [String]) -> Int {
    let n: Int = native_list_len(rule)
    return n - 2
}

fn rule_rhs(rule: [String], idx: Int) -> String {
    return native_list_get(rule, idx + 2)
}

fn make_rule(id: String, lhs: String, r0: String) -> [String] {
    let r: [String] = native_list_empty()
    let r = native_list_append(r, id)
    let r = native_list_append(r, lhs)
    let r = native_list_append(r, r0)
    return r
}

fn make_rule2(id: String, lhs: String, r0: String, r1: String) -> [String] {
    let r: [String] = make_rule(id, lhs, r0)
    let r = native_list_append(r, r1)
    return r
}

fn make_rule3(id: String, lhs: String, r0: String, r1: String, r2: String) -> [String] {
    let r: [String] = make_rule2(id, lhs, r0, r1)
    let r = native_list_append(r, r2)
    return r
}

fn make_rule4(id: String, lhs: String, r0: String, r1: String, r2: String, r3: String) -> [String] {
    let r: [String] = make_rule3(id, lhs, r0, r1, r2)
    let r = native_list_append(r, r3)
    return r
}

fn build_rules() -> [[String]] {
    let rules: [[String]] = native_list_empty()

    let rules = native_list_append(rules, make_rule2("S-DECL",      "S",  "NP", "VP"))
    let rules = native_list_append(rules, make_rule3("S-QUEST",     "S",  "Aux", "NP", "VP"))
    let rules = native_list_append(rules, make_rule("S-IMP",        "S",  "VP"))
    let rules = native_list_append(rules, make_rule2("NP-DET-N",    "NP", "Det", "N"))
    let rules = native_list_append(rules, make_rule3("NP-DET-ADJ-N","NP", "Det", "Adj", "N"))
    let rules = native_list_append(rules, make_rule("NP-PRON",      "NP", "Pron"))
    let rules = native_list_append(rules, make_rule("NP-N",         "NP", "N"))
    let rules = native_list_append(rules, make_rule("VP-V",         "VP", "V"))
    let rules = native_list_append(rules, make_rule2("VP-V-NP",     "VP", "V", "NP"))
    let rules = native_list_append(rules, make_rule2("VP-V-PP",     "VP", "V", "PP"))
    let rules = native_list_append(rules, make_rule3("VP-V-NP-PP",  "VP", "V", "NP", "PP"))
    let rules = native_list_append(rules, make_rule2("VP-AUX-V",    "VP", "Aux", "V"))
    let rules = native_list_append(rules, make_rule3("VP-AUX-V-NP", "VP", "Aux", "V", "NP"))
    let rules = native_list_append(rules, make_rule2("PP-P-NP",     "PP", "P", "NP"))

    return rules
}

fn get_rules() -> [[String]] {
    return build_rules()
}

fn find_rule(rule_id_str: String) -> [String] {
    let rules: [[String]] = get_rules()
    let n: Int = native_list_len(rules)
    let i: Int = 0
    while i < n {
        let rule: [String] = native_list_get(rules, i)
        let id: String = native_list_get(rule, 0)
        if str_eq(id, rule_id_str) {
            return rule
        }
        let i = i + 1
    }
    let empty: [String] = native_list_empty()
    return empty
}

// ── Tree node construction ────────────────────────────────────────────────────

fn make_leaf(label: String, word: String) -> String {
    return "(" + label + " " + word + ")"
}

fn make_node1(label: String, child0: String) -> String {
    return "(" + label + " _ " + child0 + ")"
}

fn make_node2(label: String, child0: String, child1: String) -> String {
    return "(" + label + " _ " + child0 + " " + child1 + ")"
}

fn make_node3(label: String, child0: String, child1: String, child2: String) -> String {
    return "(" + label + " _ " + child0 + " " + child1 + " " + child2 + ")"
}

fn make_node4(label: String, child0: String, child1: String, child2: String, child3: String) -> String {
    return "(" + label + " _ " + child0 + " " + child1 + " " + child2 + " " + child3 + ")"
}

// ── Tree rendering ────────────────────────────────────────────────────────────

fn nlg_is_ws(c: String) -> Bool {
    if str_eq(c, " ")  { return true }
    if str_eq(c, "\t") { return true }
    if str_eq(c, "\n") { return true }
    return false
}

fn skip_ws(s: String, pos: Int) -> Int {
    let n: Int = str_len(s)
    let i: Int = pos
    let running: Bool = true
    while running {
        if i >= n {
            let running = false
        } else {
            let c: String = str_slice(s, i, i + 1)
            if nlg_is_ws(c) {
                let i = i + 1
            } else {
                let running = false
            }
        }
    }
    return i
}

fn scan_token(s: String, start: Int) -> [String] {
    let n: Int = str_len(s)
    let i: Int = start
    let running: Bool = true
    while running {
        if i >= n {
            let running = false
        } else {
            let c: String = str_slice(s, i, i + 1)
            if nlg_is_ws(c) {
                let running = false
            } else {
                if str_eq(c, "(") {
                    let running = false
                } else {
                    if str_eq(c, ")") {
                        let running = false
                    } else {
                        let i = i + 1
                    }
                }
            }
        }
    }
    let tok: String = str_slice(s, start, i)
    let result: [String] = native_list_empty()
    let result = native_list_append(result, tok)
    let result = native_list_append(result, int_to_str(i))
    return result
}

fn render_tree(tree: String) -> String {
    let words: [String] = native_list_empty()
    let n: Int = str_len(tree)
    let i: Int = 0
    let prev_was_open: Bool = false
    while i < n {
        let c: String = str_slice(tree, i, i + 1)
        if str_eq(c, "(") {
            let prev_was_open = true
            let i = i + 1
        } else {
            if str_eq(c, ")") {
                let prev_was_open = false
                let i = i + 1
            } else {
                if nlg_is_ws(c) {
                    let i = i + 1
                } else {
                    let tok_info: [String] = scan_token(tree, i)
                    let tok: String = native_list_get(tok_info, 0)
                    let new_i: Int = str_to_int(native_list_get(tok_info, 1))
                    let i = new_i
                    if prev_was_open {
                        let prev_was_open = false
                    } else {
                        if !str_eq(tok, "_") {
                            let words = native_list_append(words, tok)
                        }
                    }
                }
            }
        }
    }
    return str_join(words, " ")
}

// ── Word-order engine ─────────────────────────────────────────────────────────

// gram_word_order: returns the word order string from a profile.
fn gram_word_order(profile: [String]) -> String {
    return lang_word_order(profile)
}

// gram_order_constituents: order Subject, Verb, Object tokens according to the
// language profile's word_order.
//
// subj, verb, obj: surface strings (may be empty).
// Returns a space-joined string in the correct order.
//
// Supported orders: SVO, SOV, VSO, VOS, OVS, OSV, free (defaults to SVO).

fn gram_order_constituents(subj: String, verb: String, obj: String, profile: [String]) -> String {
    let order: String = gram_word_order(profile)
    let parts: [String] = native_list_empty()

    if str_eq(order, "SVO") {
        if !str_eq(subj, "") { let parts = native_list_append(parts, subj) }
        if !str_eq(verb, "") { let parts = native_list_append(parts, verb) }
        if !str_eq(obj,  "") { let parts = native_list_append(parts, obj)  }
        return str_join(parts, " ")
    }

    if str_eq(order, "SOV") {
        if !str_eq(subj, "") { let parts = native_list_append(parts, subj) }
        if !str_eq(obj,  "") { let parts = native_list_append(parts, obj)  }
        if !str_eq(verb, "") { let parts = native_list_append(parts, verb) }
        return str_join(parts, " ")
    }

    if str_eq(order, "VSO") {
        if !str_eq(verb, "") { let parts = native_list_append(parts, verb) }
        if !str_eq(subj, "") { let parts = native_list_append(parts, subj) }
        if !str_eq(obj,  "") { let parts = native_list_append(parts, obj)  }
        return str_join(parts, " ")
    }

    if str_eq(order, "VOS") {
        if !str_eq(verb, "") { let parts = native_list_append(parts, verb) }
        if !str_eq(obj,  "") { let parts = native_list_append(parts, obj)  }
        if !str_eq(subj, "") { let parts = native_list_append(parts, subj) }
        return str_join(parts, " ")
    }

    if str_eq(order, "OVS") {
        if !str_eq(obj,  "") { let parts = native_list_append(parts, obj)  }
        if !str_eq(verb, "") { let parts = native_list_append(parts, verb) }
        if !str_eq(subj, "") { let parts = native_list_append(parts, subj) }
        return str_join(parts, " ")
    }

    if str_eq(order, "OSV") {
        if !str_eq(obj,  "") { let parts = native_list_append(parts, obj)  }
        if !str_eq(subj, "") { let parts = native_list_append(parts, subj) }
        if !str_eq(verb, "") { let parts = native_list_append(parts, verb) }
        return str_join(parts, " ")
    }

    // "free" and unknown: use SVO as the neutral citation order.
    if !str_eq(subj, "") { let parts = native_list_append(parts, subj) }
    if !str_eq(verb, "") { let parts = native_list_append(parts, verb) }
    if !str_eq(obj,  "") { let parts = native_list_append(parts, obj)  }
    return str_join(parts, " ")
}

// gram_build_vp: construct a verb phrase surface string.
//
// verb: main verb surface form.
// aux:  auxiliary surface form (empty if none).
// profile: language profile.
//
// In SVO/VSO/VOS languages the auxiliary precedes the main verb.
// In SOV languages the verb cluster appears at the end; we keep aux before V
// as a reasonable default for the auxiliary-final constructions in those languages.

fn gram_build_vp(verb: String, aux: String, profile: [String]) -> String {
    if str_eq(aux, "") {
        return verb
    }
    return aux + " " + verb
}

// gram_question_strategy: returns the question formation strategy for a language.
//
// "do-support"  - English: "Do you see?" — do-auxiliary inserted, verb stays base
// "particle"    - Japanese: sentence-final か appended
// "intonation"  - Mandarin, Spanish: rising intonation only, word order unchanged
// "inversion"   - French, German: subject-verb inversion

fn gram_question_strategy(profile: [String]) -> String {
    let code: String = lang_get(profile, "code")
    if str_eq(code, "en") { return "do-support" }
    if str_eq(code, "ja") { return "particle" }
    if str_eq(code, "zh") { return "intonation" }
    if str_eq(code, "es") { return "intonation" }
    if str_eq(code, "fr") { return "inversion" }
    if str_eq(code, "de") { return "inversion" }
    if str_eq(code, "ar") { return "intonation" }
    if str_eq(code, "hi") { return "particle" }
    if str_eq(code, "ru") { return "intonation" }
    if str_eq(code, "fi") { return "particle" }
    if str_eq(code, "sw") { return "intonation" }
    if str_eq(code, "la")  { return "intonation" }  // Latin: word order marks Q (VSO or -ne suffix)
    if str_eq(code, "he")  { return "intonation" }  // Modern Hebrew: rising intonation
    if str_eq(code, "grc") { return "intonation" }  // Ancient Greek: ἆρα particle or intonation
    if str_eq(code, "ang") { return "intonation" }  // Old English: hwæþer particle or intonation
    if str_eq(code, "sa")  { return "intonation" }  // Sanskrit: kim particle or intonation
    if str_eq(code, "got") { return "intonation" }  // Gothic: ibai particle or intonation
    if str_eq(code, "non") { return "intonation" }  // Old Norse: hvárr particle or intonation
    if str_eq(code, "enm") { return "do-support" }  // Middle English: do-support emerging
    if str_eq(code, "pi")  { return "intonation" }  // Pali: kim particle or intonation
    // Unknown: default to intonation (safest — never wrong, just flat)
    return "intonation"
}

// ── NP and PP assembly ────────────────────────────────────────────────────────
//
// These functions are profile-aware but the logic is the same across languages
// because we work with pre-assembled strings (Engram vocabulary supplies
// language-specific forms before these functions see them).

fn is_pronoun(word: String) -> Bool {
    if str_eq(word, "I")    { return true }
    if str_eq(word, "you")  { return true }
    if str_eq(word, "he")   { return true }
    if str_eq(word, "she")  { return true }
    if str_eq(word, "it")   { return true }
    if str_eq(word, "we")   { return true }
    if str_eq(word, "they") { return true }
    if str_eq(word, "me")   { return true }
    if str_eq(word, "him")  { return true }
    if str_eq(word, "her")  { return true }
    if str_eq(word, "us")   { return true }
    if str_eq(word, "them") { return true }
    return false
}

// build_np: assemble a noun phrase tree from a referent string.
// profile parameter reserved for future case-marking / article agreement.
fn build_np(referent: String, slots: [String]) -> String {
    if is_pronoun(referent) {
        return make_node1("NP", make_leaf("Pron", referent))
    }
    let parts: [String] = str_split(referent, " ")
    let np: Int = native_list_len(parts)
    if np == 1 {
        return make_node1("NP", make_leaf("N", referent))
    }
    if np == 2 {
        let det: String  = native_list_get(parts, 0)
        let noun: String = native_list_get(parts, 1)
        return make_node2("NP", make_leaf("Det", det), make_leaf("N", noun))
    }
    if np == 3 {
        let det: String  = native_list_get(parts, 0)
        let adj: String  = native_list_get(parts, 1)
        let noun: String = native_list_get(parts, 2)
        return make_node3("NP", make_leaf("Det", det), make_leaf("Adj", adj), make_leaf("N", noun))
    }
    return make_node1("NP", make_leaf("N", referent))
}

// build_pp: assemble a prepositional phrase tree from a "PREP NP" string.
// For postpositional languages (ja, hi, ko) the slot value is expected to be
// already pre-assembled with the postposition in the correct position by the
// caller (vocabulary lookup from Engram supplies the right surface form).
fn build_pp(loc: String) -> String {
    let parts: [String] = str_split(loc, " ")
    let n: Int = native_list_len(parts)
    if n < 2 {
        return make_leaf("PP", loc)
    }
    let prep: String = native_list_get(parts, 0)
    let np_parts: [String] = native_list_empty()
    let i: Int = 1
    while i < n {
        let np_parts = native_list_append(np_parts, native_list_get(parts, i))
        let i = i + 1
    }
    let np_str: String = str_join(np_parts, " ")
    let np_tree: String = build_np(np_str, native_list_empty())
    return make_node2("PP", make_leaf("P", prep), np_tree)
}

// ── VP tree construction ──────────────────────────────────────────────────────

fn build_vp_body(slots: [String]) -> String {
    let verb_surf: String = slots_get(slots, "verb_surf")
    let patient: String = slots_get(slots, "patient")
    let loc: String = slots_get(slots, "location")
    if !str_eq(patient, "") {
        let obj_np: String = build_np(patient, slots)
        if !str_eq(loc, "") {
            let pp: String = build_pp(loc)
            return make_node3("VP", make_leaf("V", verb_surf), obj_np, pp)
        }
        return make_node2("VP", make_leaf("V", verb_surf), obj_np)
    }
    if !str_eq(loc, "") {
        let pp: String = build_pp(loc)
        return make_node2("VP", make_leaf("V", verb_surf), pp)
    }
    return make_node1("VP", make_leaf("V", verb_surf))
}

fn build_vp_from_slots(slots: [String]) -> String {
    let aux_surf: String = slots_get(slots, "aux_surf")
    if !str_eq(aux_surf, "") {
        let verb_surf: String = slots_get(slots, "verb_surf")
        let patient: String = slots_get(slots, "patient")
        let loc: String = slots_get(slots, "location")
        if !str_eq(patient, "") {
            let obj_np: String = build_np(patient, slots)
            return make_node3("VP", make_leaf("Aux", aux_surf), make_leaf("V", verb_surf), obj_np)
        }
        return make_node2("VP", make_leaf("Aux", aux_surf), make_leaf("V", verb_surf))
    }
    return build_vp_body(slots)
}

// ── Tree generator ────────────────────────────────────────────────────────────

fn generate_tree(rule_id_str: String, slots: [String]) -> String {
    let rule: [String] = find_rule(rule_id_str)
    let n: Int = native_list_len(rule)
    if n == 0 {
        return make_leaf("ERR", "unknown-rule")
    }

    let lhs: String = native_list_get(rule, 1)

    if str_eq(rule_id_str, "S-DECL") {
        let agent: String  = slots_get(slots, "agent")
        let np_tree: String = build_np(agent, slots)
        let vp_tree: String = build_vp_from_slots(slots)
        return make_node2("S", np_tree, vp_tree)
    }

    if str_eq(rule_id_str, "S-QUEST") {
        let agent: String    = slots_get(slots, "agent")
        let np_tree: String  = build_np(agent, slots)
        let vp_tree: String  = build_vp_body(slots)
        let aux_surf: String = slots_get(slots, "aux_surf")
        return make_node3("S", make_leaf("Aux", aux_surf), np_tree, vp_tree)
    }

    if str_eq(rule_id_str, "S-IMP") {
        let vp_tree: String = build_vp_from_slots(slots)
        return make_node1("S", vp_tree)
    }

    return make_leaf(lhs, "?")
}
