// moomem 核心类型:条目 / 状态 / 配置 / 统计 / 摘要。
// 共享约定:
//   - user_id 契约校验唯一实现 validate_user_id
//   - id = "{seq 12位hex}-{rand 4 hex}",seq 持久化恢复续接
//   - created_at 为注入 Clock 的逻辑毫秒

///|
/// 条目生命周期状态。
/// Active / Unstructured 参与检索;Superseded / Deleted 保留于快照可审计但不可检索。
pub(all) enum EntryStatus {
  Active
  Superseded
  Deleted
  Unstructured
} derive(Eq)

///|
pub impl Show for EntryStatus with fn output(self, logger) {
  match self {
    Active => logger.write_string("Active")
    Superseded => logger.write_string("Superseded")
    Deleted => logger.write_string("Deleted")
    Unstructured => logger.write_string("Unstructured")
  }
}

///|
pub extend EntryStatus with Show::{to_string, output}

///|
pub extend EntryStatus with Eq::{not_equal, equal}

///|
/// 条目语义类型(由 Extractor 产出)。
pub(all) enum EntryKind {
  Fact
  Preference
  Event
  Unstructured
} derive(Eq)

///|
pub impl Show for EntryKind with fn output(self, logger) {
  match self {
    Fact => logger.write_string("Fact")
    Preference => logger.write_string("Preference")
    Event => logger.write_string("Event")
    Unstructured => logger.write_string("Unstructured")
  }
}

///|
pub extend EntryKind with Show::{to_string, output}

///|
pub extend EntryKind with Eq::{not_equal, equal}

///|
/// 最小记忆单元:一条可复用事实。
pub(all) struct MemoryEntry {
  /// "{seq 12位hex}-{rand 4 hex}",全局唯一,不含 user 信息
  id : String
  user_id : String
  content : String
  kind : EntryKind
  /// 嵌入向量,长度 = config.dim;嵌入失败时为空数组(仅 BM25 检索)
  embedding : Array[Double]
  /// BM25 分词结果(写入时即分好,恢复时不必重算)
  keywords : Array[String]
  /// 生命周期状态(add→Active;supersede/forget 就地迁移)
  mut status : EntryStatus
  /// 逻辑时钟毫秒(注入 Clock),导出可见
  created_at : Int64
  /// 覆盖者 id(状态机:Active→Superseded 时写入)
  mut superseded_by : String?
  /// 逻辑序号,持久化恢复计数器,永不复用
  seq : Int32
  /// 痕迹元数据(extractor / degraded / conflict_decision 等)
  metadata : Map[String, Json]
}

///|
/// 条目是否可被 recall 返回(Active 与 Unstructured;Superseded/Deleted 不可见)。
pub fn MemoryEntry::is_recallable(self : MemoryEntry) -> Bool {
  match self.status {
    Active => true
    Unstructured => true
    Superseded => false
    Deleted => false
  }
}

///|
/// 简要展示(Show 输出不含 embedding 全量,避免噪音)。
pub impl Show for MemoryEntry with fn output(self, logger) {
  let kind = self.kind.to_string()
  let status = self.status.to_string()
  let superseded_by = match self.superseded_by {
    Some(id) => id
    None => "none"
  }
  let keyword_count = self.keywords.length()
  let embedding_length = self.embedding.length()
  let text = "MemoryEntry::{ id: \{self.id}, user_id: \{self.user_id}, content: \{self.content}, kind: \{kind}, status: \{status}, created_at: \{self.created_at}, superseded_by: \{superseded_by}, seq: \{self.seq}, keywords: \{keyword_count}, embedding_dim: \{embedding_length} }"
  logger.write_string(text)
}

///|
pub extend MemoryEntry with Show::{to_string, output}

///|
/// forget 的目标选择:按 id 或清空该 user 全部可检索条目。
pub(all) enum ForgetTarget {
  ById(String)
  All
}

///|
pub impl Show for ForgetTarget with fn output(self, logger) {
  match self {
    ById(id) => logger.write_string("ById(\{id})")
    All => logger.write_string("All")
  }
}

///|
pub extend ForgetTarget with Show::{to_string, output}

///|
/// add 的返回摘要:提取条数、入库条数、重复、覆盖与降级。
pub struct AddSummary {
  /// 提取器产出的事实条数
  extracted : Int32
  /// 实际新入库条数
  inserted : Int32
  /// 与既有条目内容指纹重复而跳过的条数
  duplicates : Int32
  /// 被 Replace 裁决覆盖(置 Superseded)的旧条目数
  superseded : Int32
  /// 本次 add 是否发生降级(提取失败→raw / 嵌入失败→仅 BM25)
  degraded : Bool
  /// 人类可读备注(重复 id、冲突判定、降级原因等)
  notes : Array[String]
}

///|
/// 手写 Show(原 derive(Show) 输出格式保持不变:字段名: 值,逗号分隔)。
pub impl Show for AddSummary with fn output(self, logger) {
  logger.write_string(
    "{extracted: \{self.extracted}, inserted: \{self.inserted}, duplicates: \{self.duplicates}, superseded: \{self.superseded}, degraded: \{self.degraded}, notes: \{show_string_array(self.notes)}}",
  )
}

///|
pub extend AddSummary with Show::{to_string, output}

///|
/// stats 的返回结构。
pub struct StoreStats {
  total : Int32
  active : Int32
  superseded : Int32
  deleted : Int32
  /// status == Unstructured 的条目数(降级入库,待重提取)
  unstructured : Int32
  /// 快照逻辑字节数(MemoryBackend 下为逻辑值)
  bytes_on_disk : Int64
  /// 最近一次成功落盘的逻辑时钟;从未写盘为 None
  last_write_at : Int64?
  /// 启动恢复时截断的尾部半行数
  truncated_recovered : Int32
  /// 当前提取器模式("raw" | "llm")
  extractor_mode : String
}

///|
/// 手写 Show(原 derive(Show) 输出格式保持不变:字段名: 值,逗号分隔)。
pub impl Show for StoreStats with fn output(self, logger) {
  logger.write_string(
    "{total: \{self.total}, active: \{self.active}, superseded: \{self.superseded}, deleted: \{self.deleted}, unstructured: \{self.unstructured}, bytes_on_disk: \{self.bytes_on_disk}, last_write_at: \{show_int64_opt(self.last_write_at)}, truncated_recovered: \{self.truncated_recovered}, extractor_mode: \{self.extractor_mode}}",
  )
}

///|
pub extend StoreStats with Show::{to_string, output}

///|
/// recall 命中数不足 top_k 时的补齐策略。
pub(all) enum RecallBackfill {
  /// 不补齐:命中多少返回多少(零命中行为不受影响,恒为空列表)
  NoBackfill
  /// 按插入序倒序补最新可检索条目(v0.2.1 现行为,缺省)
  BackfillRecency
}

///|
/// 手写 Show(构造器名,与 ConflictDecision 惯例一致)。
pub impl Show for RecallBackfill with fn output(self, logger) {
  match self {
    NoBackfill => logger.write_string("NoBackfill")
    BackfillRecency => logger.write_string("BackfillRecency")
  }
}

///|
pub extend RecallBackfill with Show::{to_string, output}

///|
/// 混合检索融合策略。
pub(all) enum FusionPolicy {
  /// 等权 RRF(历史缺省;回退用)
  EqualRrf
  /// 查询自适应:词法强命中时降权向量通道
  AdaptiveLexical
}

///|
pub impl Show for FusionPolicy with fn output(self, logger) {
  match self {
    EqualRrf => logger.write_string("EqualRrf")
    AdaptiveLexical => logger.write_string("AdaptiveLexical")
  }
}

///|
pub extend FusionPolicy with Show::{to_string, output}

///|
/// 打开 MemoryStore 的配置。所有注入点均为 Option,None 时使用缺省确定性实现。
pub(all) struct Config {
  /// None → HashingEmbedder(dim)
  embedder : &Embedder?
  /// None → RawExtractor(raw 直通,kind=unstructured)
  extractor : &Extractor?
  /// None → SimilarityJudge(supersede_threshold, embedder)
  judge : &ConflictJudge?
  /// None → native: FsBackend(path);wasm/js: MemoryBackend
  persistence : &PersistenceBackend?
  /// None → LogicalClock(从快照 header.clock 续接)
  clock : &Clock?
  /// 嵌入维度,缺省 256
  dim : Int32
  /// RRF 融合常数 k,缺省 60
  rrf_k : Int32
  /// 每路召回候选数;0 = 自动(min(top_k*2, 50))
  recall_candidates : Int32
  /// 冲突判定 Replace 阈值,缺省 0.82
  supersede_threshold : Double
  /// add 链路冲突检测每路候选窗口,缺省 8(合法域 >= 1)
  conflict_candidates : Int32
  /// SimilarityJudge 的 Coexist 下界间距,缺省 0.15(合法域 [0, 1))
  coexist_band : Double
  /// recall 补齐策略,缺省 BackfillRecency
  backfill : RecallBackfill
  /// 混合检索融合策略,缺省 AdaptiveLexical
  fusion_policy : FusionPolicy
  /// adaptive:BM25 top1 下限(>=0)
  lexical_floor : Double
  /// adaptive:top1/top2 倍差(>=1)
  lexical_gap : Double
  /// adaptive:词法强时向量权重,∈(0,1]
  vector_weight_when_lexical : Double
  /// adaptive:词法弱时向量权重,∈(0,1]
  vector_weight_when_semantic : Double
}

///|
/// 缺省配置(全部注入点走确定性缺省实现)。
pub fn Config::default() -> Config {
  Config::{
    embedder: None,
    extractor: None,
    judge: None,
    persistence: None,
    clock: None,
    dim: DEFAULT_DIM,
    rrf_k: DEFAULT_RRF_K,
    recall_candidates: 0,
    supersede_threshold: DEFAULT_SUPERSEDE_THRESHOLD,
    conflict_candidates: CONFLICT_CANDIDATES,
    coexist_band: COEXIST_BAND,
    backfill: BackfillRecency,
    fusion_policy: FusionPolicy::AdaptiveLexical,
    lexical_floor: DEFAULT_LEXICAL_FLOOR,
    lexical_gap: DEFAULT_LEXICAL_GAP,
    vector_weight_when_lexical: DEFAULT_VECTOR_WEIGHT_LEXICAL,
    vector_weight_when_semantic: DEFAULT_VECTOR_WEIGHT_SEMANTIC,
  }
}

// ---------------------------------------------------------------------------
// 常量
// ---------------------------------------------------------------------------

///|
pub const DEFAULT_DIM : Int32 = 256

///|
pub const DEFAULT_RRF_K : Int32 = 60

///|
pub const DEFAULT_SUPERSEDE_THRESHOLD : Double = 0.82

///|
pub const DEFAULT_RECALL_TOP_K : Int32 = 5

///|
pub const MAX_RECALL_TOP_K : Int32 = 50

///|
pub const MAX_USER_ID_LEN : Int32 = 64

///|
/// add 链路冲突检测的每路候选窗口
pub const CONFLICT_CANDIDATES : Int32 = 8

///|
/// SimilarityJudge 的 Coexist 下界间距:[threshold-0.15, threshold)
pub const COEXIST_BAND : Double = 0.15

///|
/// 提取器连续失败降级上限
pub const MAX_EXTRACTION_FAILURES : Int32 = 3

///|
/// adaptive 词法强判定:BM25 top1 下限(独立开发集校准;偏低以覆盖短轮次)
pub const DEFAULT_LEXICAL_FLOOR : Double = 0.8

///|
/// adaptive 词法强判定:top1 ≥ top2 × gap
pub const DEFAULT_LEXICAL_GAP : Double = 1.1

///|
/// adaptive:词法强时向量通道权重(≤0.05 时 recall 走 BM25 单路)
pub const DEFAULT_VECTOR_WEIGHT_LEXICAL : Double = 0.05

///|
/// adaptive:词法弱时向量通道权重(近均衡;BM25 top 由 protect_bm25_topk 兜底)
pub const DEFAULT_VECTOR_WEIGHT_SEMANTIC : Double = 0.45

// ---------------------------------------------------------------------------
// user_id 契约:唯一实现,所有入口调用
// ---------------------------------------------------------------------------

///|
/// 校验 user_id:非空、≤64 字符、字符集 [A-Za-z0-9_\-.],且不为 "." / ".."。
pub fn validate_user_id(user_id : String) -> Result[Unit, MoomemError] {
  if user_id.length() == 0 {
    return Err(MoomemError::InvalidUserId("must not be empty"))
  }
  if user_id.length() > MAX_USER_ID_LEN {
    return Err(
      MoomemError::InvalidUserId(
        "must not exceed \{MAX_USER_ID_LEN} characters (got \{user_id.length()})",
      ),
    )
  }
  for c in user_id.iter() {
    if !is_valid_user_id_char(c) {
      return Err(
        MoomemError::InvalidUserId(
          "illegal character '\{c}' (allowed set: [A-Za-z0-9_-.]) in '\{user_id}'",
        ),
      )
    }
  }
  if user_id == "." || user_id == ".." {
    return Err(
      MoomemError::InvalidUserId(
        "must not be path component reference '\{user_id}'",
      ),
    )
  }
  Ok(())
}

///|
fn is_valid_user_id_char(c : Char) -> Bool {
  (c >= 'a' && c <= 'z') ||
  (c >= 'A' && c <= 'Z') ||
  (c >= '0' && c <= '9') ||
  c == '_' ||
  c == '-' ||
  c == '.'
}

// ---------------------------------------------------------------------------
// 包内工具:稳定哈希 / 十六进制 / 条目 id 生成
// ---------------------------------------------------------------------------

///|
/// Array[String] 的展示格式(与原 derive(Show) 一致:[a, b, c])。
fn show_string_array(arr : Array[String]) -> String {
  let buf = StringBuilder()
  buf.write_char('[')
  for i, s in arr {
    if i > 0 {
      buf.write_string(", ")
    }
    buf.write_string(s)
  }
  buf.write_char(']')
  buf.to_string()
}

///|
/// Int64? 的展示格式(与原 derive(Show) 一致:Some(v) / None)。
fn show_int64_opt(v : Int64?) -> String {
  match v {
    Some(x) => "Some(\{x})"
    None => "None"
  }
}

///|
/// FNV-1a 32bit 稳定哈希(三后端确定性一致,用于嵌入分桶与内容指纹)。
fn fnv1a(s : String) -> Int32 {
  let mut h : Int32 = 0x0811c9dc5
  for c in s.iter() {
    h = (h ^ c.to_int()) * 0x01000193
  }
  h
}

///|
/// 非负整数的十六进制小写串,左侧补零至 width 位。
fn to_hex_padded(value : Int32, width : Int32) -> String {
  let mut s = value.to_string(radix=16)
  while s.length() < width {
    s = "0" + s
  }
  s
}

///|
/// 条目 id:"{seq 12位hex}-{rand 4 hex}"。
/// rand 部分由 (content, seq, created_at) 哈希派生——确定性且三后端一致,
/// 测试可精确断言;seq 保证全局唯一与恢复续接。
fn generate_entry_id(
  seq : Int32,
  content : String,
  created_at : Int64,
) -> String {
  let seq_hex = to_hex_padded(seq, 12)
  let seed = fnv1a("\{content}:\{seq}:\{created_at.to_string()}") & 0xFFFF
  let rand_hex = to_hex_padded(seed, 4)
  "\{seq_hex}-\{rand_hex}"
}