// ===== sparql 全链自证 demo(收口役 2026-11-03;mooncmd bank_demo 同款叙事)=====
// 装店六账(双人物互知 + 带型年龄)→ 四查询 → 期望逐项对账 → DEMO:PASS。
// API 面 = 一行入口 eval_query(bytes, Store) → EvalOut 三臂(解集/布尔/图)。
// 数据纪律:Store::add 缺省宾语 = 字面(kind 空);IRI 宾语必须
// add_typed(..., kind="iri")(core/eval.mbt T8o 词项类)。

///|
const Q_JOIN_FILTER : String =
  #|PREFIX foaf: 
  #|SELECT ?name WHERE {
  #|  ?a foaf:age ?age .
  #|  ?a foaf:knows ?b .
  #|  ?b foaf:name ?name .
  #|  FILTER(?age > 30)
  #|}

///|
const Q_COUNT : String =
  #|PREFIX foaf: 
  #|SELECT (COUNT(?a) AS ?c) WHERE { ?a foaf:name ?name }

///|
const Q_ASK : String =
  #|PREFIX foaf: 
  #|ASK {  foaf:name "Bob" }

///|
const Q_CONSTRUCT : String =
  #|PREFIX foaf: 
  #|CONSTRUCT { ?a  ?b }
  #|WHERE {
  #|  ?a foaf:age ?age .
  #|  ?b foaf:age ?age2 .
  #|  FILTER(?age > ?age2)
  #|}

///|
fn bytes_of(text : String) -> Array[Byte] {
  let data : Array[Byte] = []
  for i in 0.. @core.Store {
  let store = @core.Store::new()
  let name_p = "http://xmlns.com/foaf/0.1/name"
  let age_p = "http://xmlns.com/foaf/0.1/age"
  let knows_p = "http://xmlns.com/foaf/0.1/knows"
  let alice = "http://example.org/alice"
  let bob = "http://example.org/bob"
  store.add(alice, name_p, "Alice")
  store.add_typed(
    alice, age_p, "31", "", "http://www.w3.org/2001/XMLSchema#integer", "",
  )
  store.add_typed(alice, knows_p, bob, "", "", "iri")
  store.add(bob, name_p, "Bob")
  store.add_typed(
    bob, age_p, "30", "", "http://www.w3.org/2001/XMLSchema#integer", "",
  )
  store.add_typed(bob, knows_p, alice, "", "", "iri")
  store
}

///|
/// 跑一炮并打印裁决三臂(解集 / 布尔 / 图);失配由调用方对账
fn run_query(
  store : @core.Store,
  title : String,
  query : String,
) -> @core.EvalOut {
  println("--- \{title}")
  match @core.eval_query(bytes_of(query)[:], store) {
    @core.EvalOk(solutions) => {
      println("[解] \{solutions.length()} 行:")
      for sol in solutions {
        let parts : Array[String] = []
        for key, value in sol {
          parts.push("?\{key} = \{value}")
        }
        println("    \{parts.join("; ")}")
      }
      @core.EvalOk(solutions)
    }
    @core.EvalBool(b) => {
      println("[解] ASK ⇒ \{b}")
      @core.EvalBool(b)
    }
    @core.EvalGraph(triples) => {
      println("[解] 图 \{triples.length()} 三元组:")
      for t in triples {
        println("    \{t.s} \{t.p} \{t.o}")
      }
      @core.EvalGraph(triples)
    }
    @core.EvalFail(span, reason) => {
      println("[解] EVAL FAIL @\{span.start}: \{reason}")
      @core.EvalFail(span, reason)
    }
  }
}

///|
/// 对账打印;回传判定(累进 ok 链)
fn report(label : String, ok : Bool) -> Bool {
  println("[判] \{label} ⇒ \{if ok { "✓" } else { "✗" }}")
  ok
}

///|
fn main {
  let store = build_store()
  println("=== sparql demo:装店六账 → 四查询全链 ===")
  let mut ok = true
  // ① join + FILTER:alice(31) 过闸,经 knows 接 bob 的 name ⇒ 恰一解 name=Bob
  let out1 = run_query(store, "① BGP join + FILTER(?age > 30)", Q_JOIN_FILTER)
  let ok1 : Bool = match out1 {
    @core.EvalOk(solutions) =>
      solutions.length() == 1 && solutions[0].get("name") is Some("Bob")
    _ => false
  }
  ok = report("① 恰一解且 name=Bob", ok1) && ok
  // ② 聚合 COUNT:两行账 ⇒ c=2
  let out2 = run_query(store, "② 聚合 COUNT(?a)", Q_COUNT)
  let ok2 : Bool = match out2 {
    @core.EvalOk(solutions) =>
      solutions.length() == 1 && solutions[0].get("c") is Some("2")
    _ => false
  }
  ok = report("② COUNT = 2", ok2) && ok
  // ③ ASK:bob 的名字是 Bob ⇒ true
  let out3 = run_query(store, "③ ASK 字面匹配", Q_ASK)
  let ok3 : Bool = match out3 {
    @core.EvalBool(b) => b
    _ => false
  }
  ok = report("③ ASK ⇒ true", ok3) && ok
  // ④ CONSTRUCT:年龄大者 seniorTo 年小者 ⇒ 恰一三元组(alice seniorTo bob)
  let out4 = run_query(store, "④ CONSTRUCT 模板出图", Q_CONSTRUCT)
  let ok4 : Bool = match out4 {
    @core.EvalGraph(triples) =>
      triples.length() == 1 &&
      triples[0].s == "http://example.org/alice" &&
      triples[0].o == "http://example.org/bob"
    _ => false
  }
  ok = report("④ seniorTo 图恰一账(alice→bob)", ok4) && ok
  if !ok {
    println("DEMO:FAIL")
    abort("demo 期望失配——看上方 [判] ✗ 行")
  }
  println("DEMO:PASS")
}