// dialogue.el — SUMMON-THROUGH-SELF, native el.  Port of dialogue.py's core.
//
// THE WHOLE DIALOGUE IS ONE OPERATION.  A fact is never merely *fetched*: the
// query is PROJECTED into the engram's self + memory geometry, LANDS in a region,
// and the reply is READ OUT / the region MATERIALIZED from wherever it landed.
//
//     project(query)  ->  land on a region  ->  read out from that region
//
//   • lands in the SELF region       -> grounded identity/presence, read out of
//                                        the real self nodes (self_region.el)
//   • lands on a memory NEIGHBORHOOD  -> MATERIALIZE it: walk the neighborhood
//                                        (engram_neighbors_json) and read out the
//                                        region's connected members
//   • lands nowhere close             -> HONEST ABSENCE (an empty region, not a
//                                        fabricated answer, not an error)
//
// CRITICAL INVARIANTS (enforced structurally, not by convention):
//   * ONE operation — there is NO intent classifier and NO separate
//     fact-retrieval branch.  Identity is nearest-region proximity, not a switch.
//   * MATERIALIZE by walking the neighborhood, never by fetching top-props.
//   * HONEST ABSENCE when the region is thin.
//   * NEGATION is SACRED: the readout is the stored prose VERBATIM, so a negated
//     memory stays negated — we never paraphrase a polarity away.
//   * NO ECHO: the old "I noted that X. That relates to Y." template is gone.
//     The summon path materializes or honestly declines — it never echoes.
//   * DIRECTIVE OVERRIDE: a meta-directive ("answer in English") overrides the
//     reply language while the content language is still auto-detected.
//
// Depends on: comprehend (parse_spec_lang, cp_tokenize), multilingual (ml_detect,
//             ml_tr, ml_term), propositions (prop_split_sentences), self_region
//             (sr_available, sr_readout), the engram + json runtime builtins.

// ── directive override ────────────────────────────────────────────────────────
// Return [target_lang, content].  target_lang is "" when no directive is present.
// A directive names an output language; we strip it and keep the remaining text
// as the content (whose OWN language is still auto-detected downstream).

fn dlg_dir_hit(low: String, phrase: String) -> Bool {
    return str_contains(low, phrase)
}

fn dlg_parse_directive(text: String) -> [String] {
    let low: String = str_to_lower(text)
    let lang: String = ""
    let phrase: String = ""
    // English target
    if dlg_dir_hit(low, "in english")   { let lang = "en"; let phrase = "in english" }
    if dlg_dir_hit(low, "em inglês")    { let lang = "en"; let phrase = "em inglês" }
    if dlg_dir_hit(low, "em ingles")    { let lang = "en"; let phrase = "em ingles" }
    if dlg_dir_hit(low, "en inglés")    { let lang = "en"; let phrase = "en inglés" }
    // Portuguese target
    if dlg_dir_hit(low, "in portuguese") { let lang = "pt"; let phrase = "in portuguese" }
    if dlg_dir_hit(low, "em português")  { let lang = "pt"; let phrase = "em português" }
    // Spanish target
    if dlg_dir_hit(low, "in spanish")   { let lang = "es"; let phrase = "in spanish" }
    if dlg_dir_hit(low, "en español")   { let lang = "es"; let phrase = "en español" }
    // Italian target
    if dlg_dir_hit(low, "in italian")   { let lang = "it"; let phrase = "in italian" }

    let content: String = text
    if !str_eq(phrase, "") {
        // strip the directive phrase (and a common "answer"/"responda" lead-in),
        // leaving the real question as content.
        let idx: Int = str_index_of(low, phrase)
        if idx >= 0 {
            let before: String = str_slice(text, 0, idx)
            let after: String = str_slice(text, idx + str_len(phrase), str_len(text))
            let content = str_trim(before + " " + after)
        }
        // trim a leading "answer"/"responda"/"reply" and stray colon/comma.
        let cl: String = str_to_lower(content)
        if str_starts_with(cl, "answer")   { let content = str_trim(str_slice(content, 6, str_len(content))) }
        if str_starts_with(cl, "responda") { let content = str_trim(str_slice(content, 8, str_len(content))) }
        if str_starts_with(cl, "reply")    { let content = str_trim(str_slice(content, 5, str_len(content))) }
        if str_starts_with(content, ":")   { let content = str_trim(str_slice(content, 1, str_len(content))) }
        if str_starts_with(content, ",")   { let content = str_trim(str_slice(content, 1, str_len(content))) }
    }
    let r: [String] = native_list_empty()
    let r = native_list_append(r, lang)
    let r = native_list_append(r, content)
    return r
}

// ── identity landing (a region proximity, not a classifier switch) ────────────
// The query lands in the SELF region when it takes an identity/presence shape.
// Cross-lingual forms are included because the engram's lexical probe is
// English-leaning.  This is the SELF attractor of the single operation.

fn dlg_is_identity(content: String) -> Bool {
    let low: String = str_to_lower(str_trim(content))
    if str_contains(low, "who are you")   { return true }
    if str_contains(low, "what are you")  { return true }
    if str_contains(low, "who i am")      { return true }
    if str_contains(low, "your name")     { return true }
    if str_contains(low, "about yourself") { return true }
    if str_contains(low, "are you conscious") { return true }
    if str_contains(low, "are you there") { return true }
    // cross-lingual identity question-forms
    if str_contains(low, "quem é você")   { return true }
    if str_contains(low, "quem es voce")  { return true }
    if str_contains(low, "quién eres")    { return true }
    if str_contains(low, "quien eres")    { return true }
    if str_contains(low, "chi sei")       { return true }
    if str_contains(low, "qui es-tu")     { return true }
    if str_contains(low, "wer bist du")   { return true }
    return false
}

// ── readout helpers ───────────────────────────────────────────────────────────

fn dlg_first_sentence(content: String) -> String {
    let sents: [String] = prop_split_sentences(content)
    let n: Int = native_list_len(sents)
    let i: Int = 0
    while i < n {
        let s: String = str_trim(native_list_get(sents, i))
        // drop a leading markdown heading marker for a clean read-out line
        if str_starts_with(s, "# ") { let s = str_trim(str_slice(s, 2, str_len(s))) }
        if str_len(s) > 0 { return s }
        let i = i + 1
    }
    return str_trim(content)
}

// strip trailing/leading punctuation from a token.
fn dlg_clean_tok(w: String) -> String {
    let s: String = str_trim(w)
    let s = str_strip_suffix(s, ".")
    let s = str_strip_suffix(s, ",")
    let s = str_strip_suffix(s, "?")
    let s = str_strip_suffix(s, "!")
    let s = str_strip_suffix(s, ":")
    let s = str_strip_suffix(s, ";")
    return str_trim(s)
}

// closed-class across the supported languages (union) — a word we must NOT treat
// as a retrieval topic.  Also drops the meta verbs of a request ("tell", "prove",
// "show") so the TOPIC, not the speech act, is what projects into memory.
fn dlg_is_stop(w: String) -> Bool {
    if ml_stop_en(w) { return true }
    if ml_stop_es(w) { return true }
    if ml_stop_pt(w) { return true }
    if ml_stop_it(w) { return true }
    if str_eq(w, "tell")  { return true }
    if str_eq(w, "show")  { return true }
    if str_eq(w, "about") { return true }
    if str_eq(w, "sobre") { return true }
    if str_eq(w, "acerca") { return true }
    return false
}

// The CONTENT TERMS the query projects into memory: content words only, cleaned,
// cross-lingually mapped to the engram's English vocabulary, ≥3 chars.  This is
// the geometry probe — the speech-act verbs and function words are stripped so a
// PP topic ("tell me ABOUT Lisbon") projects on "lisbon", not "tell"/"me".
fn dlg_content_terms(content: String, lang: String) -> [String] {
    let toks: [String] = cp_tokenize(content)
    let n: Int = native_list_len(toks)
    let out: [String] = native_list_empty()
    let i: Int = 0
    while i < n {
        let w: String = str_to_lower(dlg_clean_tok(native_list_get(toks, i)))
        if str_len(w) >= 3 {
            if !dlg_is_stop(w) {
                let out = native_list_append(out, ml_term(w, lang))
            }
        }
        let i = i + 1
    }
    return out
}

// Does this landed node lexically overlap the query's content terms?  This is the
// RELEVANCE FLOOR: activation always returns the store's most salient nodes, so
// without this a query about nothing would "land" on the self/top node.  A node
// that shares no content term with the query is "nowhere close" -> honest absence.
fn dlg_node_matches(node: String, terms: [String]) -> Bool {
    let hay: String = str_to_lower(json_get_string(node, "content") + " " + json_get_string(node, "label"))
    let n: Int = native_list_len(terms)
    let i: Int = 0
    while i < n {
        let t: String = native_list_get(terms, i)
        if str_len(t) >= 3 {
            if str_contains(hay, t) { return true }
        }
        let i = i + 1
    }
    return false
}

// MATERIALIZE the landed region: read out the landed fact, then WALK the
// neighborhood and read out its connected members (real edges, not top-props).
fn dlg_materialize(top_node: String, reply_lang: String) -> String {
    let id: String = json_get_string(top_node, "id")
    let content: String = json_get_string(top_node, "content")
    let lead: String = dlg_first_sentence(content)

    let nb: String = engram_neighbors_json(id, 2, "both")
    let m: Int = json_array_len(nb)
    let parts: [String] = native_list_empty()
    let parts = native_list_append(parts, lead)
    let added: Int = 0
    let i: Int = 0
    while i < m {
        if added < 3 {
            let rec: String = json_array_get(nb, i)
            let node: String = json_get_raw(rec, "node")
            let nc: String = json_get_string(node, "content")
            if !str_eq(nc, "") {
                let sent: String = dlg_first_sentence(nc)
                if !str_eq(sent, "") {
                    let parts = native_list_append(parts, sent)
                    let added = added + 1
                }
            }
        }
        let i = i + 1
    }
    // The readout is the region's OWN prose, verbatim — negation SACRED, no echo.
    return str_join(parts, " ")
}

// ── THE single operation ──────────────────────────────────────────────────────

fn dlg_respond(text: String) -> String {
    // directive override: reply language may differ from content language.
    let dir: [String] = dlg_parse_directive(text)
    let target_lang: String = native_list_get(dir, 0)
    let content: String = native_list_get(dir, 1)

    let content_lang: String = ml_detect(content)
    let reply_lang: String = content_lang
    if !str_eq(target_lang, "") { let reply_lang = target_lang }

    // comprehend the content (SACRED polarity carried in the spec).
    let spec: [String] = parse_spec_lang(content, content_lang)

    // ── PROJECT + LAND: SELF region ───────────────────────────────────────────
    // Identity/presence shape lands in the self region; read out the REAL self
    // nodes (self_region.el), never a template.  Same single operation — this is
    // just the self attractor winning the landing.
    if dlg_is_identity(content) {
        if sr_available() {
            // read out the REAL self nodes when replying in their own language
            // (the soul's prose is English); for another reply language we cannot
            // translate real content without an LLM, so we answer with the
            // localized SACRED identity anchor — honest, in-language, no fabrication.
            if str_eq(reply_lang, "en") { return sr_readout("en") }
            return ml_tr("identity", reply_lang)
        }
        // self region thin — honest localized identity (logged fallback shape).
        return ml_tr("identity", reply_lang)
    }

    // ── PROJECT into MEMORY geometry ──────────────────────────────────────────
    let terms: [String] = dlg_content_terms(content, content_lang)
    let qterm: String = str_join(terms, " ")
    let act: String = engram_activate_json(qterm, 12)
    let n: Int = json_array_len(act)

    // ── LAND: the highest-activation node that ACTUALLY overlaps the query's
    //    content terms (the relevance floor).  Activation always returns the most
    //    salient nodes, so we walk the ranked list and take the first that is
    //    genuinely "close"; if none is, the query landed nowhere. ───────────────
    let landing: String = ""
    let i: Int = 0
    while i < n {
        if str_eq(landing, "") {
            let rec: String = json_array_get(act, i)
            let node: String = json_get_raw(rec, "node")
            if dlg_node_matches(node, terms) {
                let landing = node
            }
        }
        let i = i + 1
    }

    // ── HONEST ABSENCE: nothing close — an empty region, not a fabricated answer,
    //    not an "I noted that" echo. ────────────────────────────────────────────
    if str_eq(landing, "") {
        return ml_tr("no_memory", reply_lang)
    }

    // ── MATERIALIZE the landing by WALKING its neighborhood. ──────────────────
    return dlg_materialize(landing, reply_lang)
}
