// MemoryStore —— 唯一有状态编排者(聚合根)。
// 本文件:类型、open、索引维护、stats、close、flush。
// add 在 store_add.mbt,recall 在 store_recall.mbt,
// forget / export / import / list 在 store_records.mbt。
//
// 状态机:
//   (空)→Active          add 提取/直通成功
//   Active→Superseded    judge 判定 Replace(置 superseded_by,双索引摘除)
//   Active→Deleted       forget(软删除,快照保留可审计)
//   其余迁移不开放
// 不变量:recall 只返回 Active / Unstructured;Superseded/Deleted 物理保留。

///|
/// 记忆库聚合根:索引、条目表常驻内存;持久化仅负责快照落盘(单写者模型)。
pub struct MemoryStore {
  /// 记忆库根目录(MemoryBackend 下仅作标识)
  path : String
  /// 注入点(open 时解析为具体实现,此后不再变更)
  embedder : &Embedder
  extractor : &Extractor
  judge : &ConflictJudge
  persistence : &PersistenceBackend
  clock : &Clock
  dim : Int32
  rrf_k : Int32
  recall_candidates : Int32
  supersede_threshold : Double
  /// add 链路冲突检测每路候选窗口
  conflict_candidates : Int32
  /// SimilarityJudge Coexist 下界间距(仅在缺省 judge 构造时消费)
  coexist_band : Double
  /// recall 命中不足 top_k 时的补齐策略
  backfill : RecallBackfill
  /// 混合检索融合策略
  fusion_policy : FusionPolicy
  lexical_floor : Double
  lexical_gap : Double
  vector_weight_when_lexical : Double
  vector_weight_when_semantic : Double
  /// 全部条目(含 Superseded / Deleted,审计与导出可见)
  entries : Map[String, MemoryEntry]
  /// 每 user 的 entry id 插入序(含全部状态)
  by_user : Map[String, Array[String]]
  /// Package-private indexes (not host API)
  priv vector_index : VectorIndex
  priv keyword_index : KeywordIndex
  priv dedup : DedupIndex
  /// 逻辑时钟上界(快照 header.clock 来源)
  mut clock_high : Int64
  /// 下一个逻辑序号(持久化恢复续接,永不复用)
  mut next_seq : Int32
  /// 快照代数
  mut gen : Int32
  /// 启动恢复时截断的尾部半行数
  truncated_recovered : Int32
  /// 提取器连续失败计数(连续 3 次返回错误)
  mut extraction_failures : Int32
  /// 最近一次成功落盘的逻辑时钟
  mut last_write_at : Int64
  mut ever_flushed : Bool
  /// 最近一次快照字节数(stats 的 bytes_on_disk 逻辑值)
  mut snapshot_bytes : Int64
  mut closed : Bool
}

// ---------------------------------------------------------------------------
// open / 恢复
// ---------------------------------------------------------------------------

///|
/// 打开或创建记忆库。
/// 目录不存在则创建;存在则加载快照并重建索引与计数器。
pub fn MemoryStore::open(
  path : String,
  config? : Config,
) -> Result[MemoryStore, MoomemError] {
  let cfg = match config {
    Some(c) => c
    None => Config::default()
  }
  // 配置校验
  if cfg.dim <= 0 {
    return Err(MoomemError::InvalidOperation("config.dim must be positive"))
  }
  if cfg.rrf_k <= 0 {
    return Err(MoomemError::InvalidOperation("config.rrf_k must be positive"))
  }
  if cfg.recall_candidates < 0 {
    return Err(
      MoomemError::InvalidOperation("config.recall_candidates must be >= 0"),
    )
  }
  if cfg.supersede_threshold <= 0.0 || cfg.supersede_threshold > 1.0 {
    return Err(
      MoomemError::InvalidOperation(
        "config.supersede_threshold must be in (0, 1]",
      ),
    )
  }
  if cfg.conflict_candidates < 1 {
    return Err(
      MoomemError::InvalidOperation("config.conflict_candidates must be >= 1"),
    )
  }
  if cfg.coexist_band < 0.0 || cfg.coexist_band >= 1.0 {
    return Err(
      MoomemError::InvalidOperation("config.coexist_band must be in [0, 1)"),
    )
  }
  if cfg.lexical_floor < 0.0 {
    return Err(
      MoomemError::InvalidOperation("config.lexical_floor must be >= 0"),
    )
  }
  if cfg.lexical_gap < 1.0 {
    return Err(MoomemError::InvalidOperation("config.lexical_gap must be >= 1"))
  }
  if cfg.vector_weight_when_lexical <= 0.0 ||
    cfg.vector_weight_when_lexical > 1.0 {
    return Err(
      MoomemError::InvalidOperation(
        "config.vector_weight_when_lexical must be in (0, 1]",
      ),
    )
  }
  if cfg.vector_weight_when_semantic <= 0.0 ||
    cfg.vector_weight_when_semantic > 1.0 {
    return Err(
      MoomemError::InvalidOperation(
        "config.vector_weight_when_semantic must be in (0, 1]",
      ),
    )
  }
  // 解析持久化后端
  let persistence : &PersistenceBackend = match cfg.persistence {
    Some(p) => p
    None =>
      match default_backend(path) {
        Ok(b) => b
        Err(e) => return Err(e)
      }
  }
  // 加载快照(None = 空库)
  let loaded : String? = match persistence.load() {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let restored_entries : Array[MemoryEntry] = []
  let mut restored_gen = 0
  let mut restored_clock = 0L
  let mut truncated = 0
  match loaded {
    Some(text) =>
      match parse_snapshot_text(text) {
        Ok(parsed) => {
          restored_gen = parsed.gen
          restored_clock = parsed.clock
          truncated = parsed.truncated_recovered
          for e in parsed.entries {
            restored_entries.push(e)
          }
        }
        Err(e) => return Err(e)
      }
    None => ()
  }
  // 解析注入点(缺省全部确定性、离线)
  let embedder : &Embedder = match cfg.embedder {
    Some(e) => e
    None =>
      match HashingEmbedder::new(cfg.dim) {
        Ok(h) => h as &Embedder
        Err(e) => return Err(e)
      }
  }
  let extractor : &Extractor = match cfg.extractor {
    Some(x) => x
    None => RawExtractor::new() as &Extractor
  }
  let judge : &ConflictJudge = match cfg.judge {
    Some(j) => j
    None =>
      SimilarityJudge::new(
        cfg.supersede_threshold,
        embedder,
        coexist_band=cfg.coexist_band,
      )
      as &ConflictJudge
  }
  let clock : &Clock = match cfg.clock {
    Some(c) => c
    None => LogicalClock::new(start=restored_clock) as &Clock
  }
  // 构造 store 并重放恢复条目
  let store = MemoryStore::{
    path,
    embedder,
    extractor,
    judge,
    persistence,
    clock,
    dim: cfg.dim,
    rrf_k: cfg.rrf_k,
    recall_candidates: cfg.recall_candidates,
    supersede_threshold: cfg.supersede_threshold,
    conflict_candidates: cfg.conflict_candidates,
    coexist_band: cfg.coexist_band,
    backfill: cfg.backfill,
    fusion_policy: cfg.fusion_policy,
    lexical_floor: cfg.lexical_floor,
    lexical_gap: cfg.lexical_gap,
    vector_weight_when_lexical: cfg.vector_weight_when_lexical,
    vector_weight_when_semantic: cfg.vector_weight_when_semantic,
    entries: Map([]),
    by_user: Map([]),
    vector_index: VectorIndex::new(),
    keyword_index: KeywordIndex::new(),
    dedup: DedupIndex::new(),
    clock_high: restored_clock,
    next_seq: 1,
    gen: restored_gen,
    truncated_recovered: truncated,
    extraction_failures: 0,
    // 从快照恢复时:已有落盘历史(header.clock 即上次成功落盘的逻辑时钟)
    last_write_at: restored_clock,
    ever_flushed: match loaded {
      Some(_) => true
      None => false
    },
    snapshot_bytes: match loaded {
      Some(t) => t.length().to_int64()
      None => 0L
    },
    closed: false,
  }
  store.restore_entries(restored_entries)
  Ok(store)
}

///|
/// 重放恢复的条目:重建 entries/by_user/双索引/去重表/计数器。
/// Active / Unstructured 入索引;Superseded / Deleted 不入。
fn MemoryStore::restore_entries(
  self : MemoryStore,
  restored : Array[MemoryEntry],
) -> Unit {
  let mut max_seq = 0
  let mut max_created = 0L
  for e in restored {
    self.entries.set(e.id, e)
    match self.by_user.get(e.user_id) {
      Some(arr) => arr.push(e.id)
      None => self.by_user.set(e.user_id, [e.id])
    }
    if e.seq > max_seq {
      max_seq = e.seq
    }
    if e.created_at > max_created {
      max_created = e.created_at
    }
    if e.is_recallable() {
      self.index_entry(e)
    }
  }
  self.next_seq = max_seq + 1
  if max_created > self.clock_high {
    self.clock_high = max_created
  }
}

///|
/// 将可检索条目写入双索引与去重表。
fn MemoryStore::index_entry(self : MemoryStore, e : MemoryEntry) -> Unit {
  // 嵌入维度与当前配置不符(配置变更后恢复)时仅入关键词索引
  if e.embedding.length() == self.dim {
    self.vector_index.upsert(e.user_id, e.id, e.embedding)
  }
  self.keyword_index.upsert(e.user_id, e.id, e.keywords)
  self.dedup.register(e.user_id, e.content, e.id)
}

///|
/// 将条目从双索引与去重表摘除(supersede / forget 共用)。
fn MemoryStore::deindex_entry(self : MemoryStore, e : MemoryEntry) -> Unit {
  self.vector_index.remove(e.user_id, e.id)
  self.keyword_index.remove(e.user_id, e.id)
  self.dedup.unregister(e.user_id, e.content)
}

// ---------------------------------------------------------------------------
// stats
// ---------------------------------------------------------------------------

///|
/// 运行统计:各状态条目数、快照字节数(逻辑值)、最近写入、恢复截断计数。
pub fn MemoryStore::stats(
  self : MemoryStore,
) -> Result[StoreStats, MoomemError] {
  if self.closed {
    return Err(MoomemError::InvalidOperation("store is closed"))
  }
  let mut total = 0
  let mut active = 0
  let mut superseded = 0
  let mut deleted = 0
  let mut unstructured = 0
  for kv in self.entries.iter() {
    let e = kv.1
    total = total + 1
    match e.status {
      Active => active = active + 1
      Superseded => superseded = superseded + 1
      Deleted => deleted = deleted + 1
      Unstructured => unstructured = unstructured + 1
    }
  }
  Ok(StoreStats::{
    total,
    active,
    superseded,
    deleted,
    unstructured,
    bytes_on_disk: self.snapshot_bytes,
    last_write_at: if self.ever_flushed {
      Some(self.last_write_at)
    } else {
      None
    },
    truncated_recovered: self.truncated_recovered,
    extractor_mode: self.extractor.mode(),
  })
}

// ---------------------------------------------------------------------------
// close(单写者)
// ---------------------------------------------------------------------------

///|
/// 安全关闭:flush 后拒绝一切后续操作。重复 close 为幂等 Ok。
pub fn MemoryStore::close(self : MemoryStore) -> Result[Unit, MoomemError] {
  if self.closed {
    return Ok(())
  }
  // 空库且从未落盘时不产生文件
  if self.entries.length() > 0 || self.ever_flushed {
    match self.flush() {
      Ok(_) => ()
      Err(e) => return Err(e)
    }
  }
  self.closed = true
  Ok(())
}

// ---------------------------------------------------------------------------
// flush(双槽快照全量重写)
// ---------------------------------------------------------------------------

///|
fn MemoryStore::flush(self : MemoryStore) -> Result[Unit, MoomemError] {
  let all : Array[MemoryEntry] = []
  for kv in self.entries.iter() {
    all.push(kv.1)
  }
  all.sort_by((a, b) => a.seq - b.seq)
  let text = build_snapshot_text(self.gen + 1, self.clock_high, all)
  match self.persistence.save(text) {
    Ok(_) => {
      self.gen = self.gen + 1
      self.last_write_at = self.clock_high
      self.ever_flushed = true
      self.snapshot_bytes = text.length().to_int64()
      Ok(())
    }
    Err(e) => Err(e)
  }
}

///|
test "dangling ids are skipped by recall, list, forget, and backfill" {
  let backend = MemoryBackend::new()
  let store = match
    MemoryStore::open("memory://dangling", config=Config::{
      ..Config::default(),
      persistence: Some(backend),
      lexical_floor: 0.0,
      lexical_gap: 1.0,
    }) {
    Ok(store) => store
    Err(_) => abort("open")
  }
  ignore(store.add("dangling", "alpha word"))
  store.keyword_index.upsert("dangling", "missing-id", tokenize("alpha"))
  let hits = match store.recall("dangling", "alpha", top_k=5) {
    Ok(hits) => hits
    Err(_) => []
  }
  inspect(hits.length() >= 1, content="true")
  match store.by_user.get("dangling") {
    Some(ids) => ids.push("also-missing")
    None => ()
  }
  ignore(store.recall("dangling", "alpha", top_k=5))
  let listed = match store.list_entries("dangling") {
    Ok(entries) => entries
    Err(_) => []
  }
  inspect(listed.length() >= 1, content="true")
  let removed = match store.forget("dangling", All) {
    Ok(n) => n
    Err(_) => -1
  }
  inspect(removed >= 1, content="true")
  let backend2 = MemoryBackend::new()
  let store2 = match
    MemoryStore::open("memory://dangling-2", config=Config::{
      ..Config::default(),
      persistence: Some(backend2),
    }) {
    Ok(store) => store
    Err(_) => abort("open2")
  }
  ignore(store2.add("dangling", "alpha word"))
  ignore(store2.by_user.remove("dangling"))
  let again = match store2.recall("dangling", "alpha", top_k=5) {
    Ok(hits) => hits
    Err(_) => []
  }
  inspect(again.length() >= 1, content="true")
  let gone = match store2.forget("nobody", All) {
    Ok(n) => n
    Err(_) => -1
  }
  inspect(gone, content="0")
  ignore(store.close())
  ignore(store2.close())
}