// propositions.el - the READ primitive over the engram's OWN memories, native el.
//
// Free memory text -> structured PROPOSITIONS (triples):
//     (subject, predicate, object, modifiers, polarity, tense, source, confidence)
//
// This is comprehension turned inward: the Python reference (propositions.py) ran
// spaCy's dependency parser over each memory sentence and walked the arcs. Here
// the spaCy role is filled by the el-native parser (comprehend.el / parse_spec):
// each sentence is parsed to a meaning-spec, and the spec's roles ARE the triple.
// Nothing generates text. NEGATION IS SACRED: polarity flows straight from the
// spec's polarity field and is never dropped or inverted.
//
// Depends on: comprehend (parse_spec / parse_spec_lang), grammar (slots_get).

// ── sentence segmentation ─────────────────────────────────────────────────────
// Split on sentence-final punctuation (. ! ?) and hard newlines. Markdown/long
// memories are handled shallowly (the reference caps + ranks by query overlap;
// that ranking belongs to the dialogue layer, not here).

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

fn prop_split_sentences(text: String) -> [String] {
    let out: [String] = native_list_empty()
    let n: Int = str_len(text)
    let start: Int = 0
    let i: Int = 0
    while i < n {
        let c: String = str_slice(text, i, i + 1)
        if prop_is_boundary(c) {
            let seg: String = str_slice(text, start, i + 1)
            let trimmed: String = cp_trim_punct(seg)
            if !str_eq(trimmed, "") {
                let out = native_list_append(out, seg)
            }
            let start = i + 1
        }
        let i = i + 1
    }
    if start < n {
        let seg: String = str_slice(text, start, n)
        let trimmed: String = cp_trim_punct(seg)
        if !str_eq(trimmed, "") {
            let out = native_list_append(out, seg)
        }
    }
    return out
}

// ── spec -> proposition record ────────────────────────────────────────────────
// A proposition is a slot map (same [String] shape as the spec) with the READ
// contract keys. Modifiers fold the spec's location + iobj adjuncts.

fn prop_confidence(subject: String, predicate: String, object: String) -> String {
    if str_eq(predicate, "") { return "0.0" }
    if str_eq(subject, "") { return "0.4" }
    if str_eq(object, "") { return "0.7" }
    return "1.0"
}

fn prop_modifiers(spec: [String]) -> String {
    let loc: String = slots_get(spec, "location")
    let iobj: String = slots_get(spec, "iobj")
    let parts: [String] = native_list_empty()
    if !str_eq(loc, "") { let parts = native_list_append(parts, loc) }
    if !str_eq(iobj, "") { let parts = native_list_append(parts, "to " + iobj) }
    return str_join(parts, "; ")
}

fn prop_from_spec(spec: [String], source_id: String) -> [String] {
    let subject: String   = slots_get(spec, "agent")
    let predicate: String = slots_get(spec, "predicate")
    let object: String    = slots_get(spec, "patient")
    let polarity: String  = slots_get(spec, "polarity")
    let tense: String     = slots_get(spec, "tense")
    let mods: String      = prop_modifiers(spec)
    let conf: String      = prop_confidence(subject, predicate, object)

    let p: [String] = native_list_empty()
    let p = native_list_append(p, "subject");    let p = native_list_append(p, subject)
    let p = native_list_append(p, "predicate");  let p = native_list_append(p, predicate)
    let p = native_list_append(p, "object");     let p = native_list_append(p, object)
    let p = native_list_append(p, "modifiers");  let p = native_list_append(p, mods)
    let p = native_list_append(p, "polarity");   let p = native_list_append(p, polarity)
    let p = native_list_append(p, "tense");      let p = native_list_append(p, tense)
    let p = native_list_append(p, "source");     let p = native_list_append(p, source_id)
    let p = native_list_append(p, "confidence"); let p = native_list_append(p, conf)
    return p
}

// Extract one proposition from a single sentence (given language).
fn prop_extract_one_lang(sentence: String, lang: String, source_id: String) -> [String] {
    let spec: [String] = parse_spec_lang(sentence, lang)
    return prop_from_spec(spec, source_id)
}

fn prop_extract_one(sentence: String, source_id: String) -> [String] {
    return prop_extract_one_lang(sentence, "en", source_id)
}

// Render a proposition as a compact trace line (repr parity with propositions.py).
fn prop_repr(p: [String]) -> String {
    let neg: String = ""
    if str_eq(slots_get(p, "polarity"), "neg") { let neg = "NOT " }
    let mods: String = slots_get(p, "modifiers")
    let modstr: String = ""
    if !str_eq(mods, "") { let modstr = " [" + mods + "]" }
    let s: String = "(" + slots_get(p, "subject") + " -" + neg + slots_get(p, "predicate")
    let s = s + "-> " + slots_get(p, "object") + modstr
    let s = s + " conf=" + slots_get(p, "confidence") + ")"
    return s
}

// Extract all propositions from a memory's text (one per sentence). Returns a
// flat [String] whose entries are the prop_repr trace lines, in reading order.
fn prop_extract_lang(text: String, lang: String, source_id: String) -> [String] {
    let sents: [String] = prop_split_sentences(text)
    let m: Int = native_list_len(sents)
    let out: [String] = native_list_empty()
    let i: Int = 0
    while i < m {
        let sent: String = native_list_get(sents, i)
        let p: [String] = prop_extract_one_lang(sent, lang, source_id)
        // drop empty parses (no predicate recovered): honest partial, not noise.
        if !str_eq(slots_get(p, "predicate"), "") {
            let out = native_list_append(out, prop_repr(p))
        }
        let i = i + 1
    }
    return out
}

fn prop_extract(text: String, source_id: String) -> [String] {
    return prop_extract_lang(text, "en", source_id)
}
