// system.mbt — orchestration facade (mirrors Python `system.py`)
//
// Wires storage + modules into the four user-facing operations:
// remember / recall / evolve / stats, plus list_all.

///|
/// Build a JSON object from key/value pairs.
pub fn obj(pairs : Array[(String, Json)]) -> Map[String, Json] {
  let m : Map[String, Json] = Map([], capacity=0)
  for p in pairs {
    let (k, v) = p
    m.set(k, v)
  }
  m
}

///|
/// The memory system facade. Holds a `Storage`; because `Storage` keeps its
/// index in a `Ref` cell, copying the facade still shares the same store.
pub struct MemorySystem {
  storage : Storage
}

///|
/// Open a memory system rooted at `root`, optionally encrypted by `passphrase`.
pub fn MemorySystem::new(
  root : String,
  passphrase? : String = "",
) -> MemorySystem {
  let crypto = if passphrase == "" {
    CryptoProvider::disabled()
  } else {
    CryptoProvider::new(passphrase)
  }
  { storage: Storage::new(root, crypto~) }
}

///|
/// Write a memory: validate -> classify -> dedup -> conflict -> store.
pub fn MemorySystem::remember(
  self : MemorySystem,
  content : String,
  type_hint? : String = "",
  source? : String = "user_input",
  interactive? : Bool = true,
  conflict_policy? : String = "ask",
) -> Map[String, Json] raise @fs.IOError {
  let (ok, reason) = validate_input(content)
  if !ok {
    return obj([
      ("status", Json::string("rejected")),
      ("reason", Json::string(reason)),
    ])
  }
  let mtype = if type_hint == "" {
    classify(content)
  } else {
    MemoryType::from_str(type_hint)
  }
  let candidates = self.storage.query_by_type(mtype)
  let dup = find_duplicate(content, candidates)
  if dup is Some(_) {
    let d = dup.unwrap()
    return obj([
      ("status", Json::string("duplicate")),
      ("entry_id", Json::string(d.id)),
      ("content", Json::string(d.content)),
    ])
  }
  let entry = MemoryEntry::create(content, mtype, source~)
  let conflicts = detect_conflict(entry, candidates)
  if conflicts.length() > 0 {
    let decision = if interactive && conflict_policy == "ask" {
      self.ask_conflict(entry, conflicts)
    } else {
      conflict_policy
    }
    if decision == "cancel" {
      return obj([
        ("status", Json::string("cancelled")),
        ("conflicts", Json::array(conflicts.map(fn(c) { Json::string(c.id) }))),
      ])
    }
    if decision == "delete_existing" {
      for c in conflicts {
        ignore(self.storage.delete(c.id))
      }
    } else if decision == "merge" {
      let merged = conflicts[0].content + " | " + content
      let merged_entry = { ..conflicts[0], content: merged }
      self.storage.put(merged_entry)
      return obj([
        ("status", Json::string("merged")),
        ("entry_id", Json::string(conflicts[0].id)),
      ])
    }
  }
  self.storage.put(entry)
  obj([
    ("status", Json::string("stored")),
    ("entry_id", Json::string(entry.id)),
    ("type", Json::string(entry.mtype)),
    ("conflicts", Json::array(conflicts.map(fn(c) { Json::string(c.id) }))),
  ])
}

///|
/// Interactive conflict resolution (best-effort; falls back to "keep").
fn MemorySystem::ask_conflict(
  _self : MemorySystem,
  entry : MemoryEntry,
  conflicts : Array[MemoryEntry],
) -> String {
  println("\n[冲突检测] 新记忆与已有记忆存在潜在矛盾:")
  println("  新: " + entry.content)
  for c in conflicts {
    println("  已有(" + c.id + "): " + c.content)
  }
  let raw = @fs.read_file_to_string("CON") catch {
    _ => @fs.read_file_to_string("/dev/stdin") catch { _ => "" }
  }
  let line = raw.trim().to_owned().to_lower()
  if line == "keep" ||
    line == "delete" ||
    line == "merge" ||
    line == "cancel" ||
    line == "delete_existing" {
    if line == "delete" {
      return "delete_existing"
    }
    return line
  }
  "keep"
}

///|
/// Retrieve memories ranked by similarity x priority x recency; updates stats.
pub fn MemorySystem::recall(
  self : MemorySystem,
  query : String,
  top_k? : Int = 5,
) -> Array[Json] raise @fs.IOError {
  let now = now_ms()
  let results = graded_retrieval(self.storage, query, top_k~, now~)
  let out : Array[Json] = []
  for item in results {
    let (entry, score) = item
    let updated = {
      ..entry,
      access_count: entry.access_count + 1,
      last_accessed: now,
      priority: recompute_priority(entry, now),
    }
    self.storage.put(updated)
    out.push(
      Json::object(
        obj([
          ("id", Json::string(updated.id)),
          ("type", Json::string(updated.mtype)),
          ("content", Json::string(updated.content)),
          ("priority", Json::number(updated.priority)),
          ("score", Json::number(score)),
        ]),
      ),
    )
  }
  out
}

///|
/// Run self-evolution over the store.
pub fn MemorySystem::evolve(
  self : MemorySystem,
) -> Map[String, Json] raise @fs.IOError {
  evolve(self.storage)
}

///|
/// Aggregate statistics.
pub fn MemorySystem::stats(self : MemorySystem) -> Map[String, Json] {
  self.storage.stats()
}

///|
/// List all entries as JSON.
pub fn MemorySystem::list_all(self : MemorySystem) -> Array[Json] {
  self.storage.all_entries().map(fn(e) { e.to_json() })
}

///|
/// Return the on-disk JSON file path for an entry, or `None` if absent.
/// Exposed so storage can be inspected (e.g. by encryption-roundtrip tests).
pub fn MemorySystem::entry_path(self : MemorySystem, id : String) -> String? {
  match self.storage.get(id) {
    Some(e) => Some(join(self.storage.root, e.mtype) + SEP + e.id + ".json")
    None => None
  }
}

///|
/// Maintenance helper: rewind an entry's access clock so self-evolution's
/// deprecation path can be exercised deterministically by tests. Returns
/// `true` if the entry existed and was updated.
pub fn MemorySystem::age_entry(
  self : MemorySystem,
  id : String,
  last_accessed : Int64,
  access_count : Int,
) -> Bool raise @fs.IOError {
  match self.storage.get(id) {
    Some(e) => {
      let updated = { ..e, last_accessed, access_count }
      self.storage.put(updated)
      true
    }
    None => false
  }
}