// engine_consolidate.mbt
// =====================================================================
// P10: 从 engine.mbt 拆出 — 同包(@lib)多文件,调用点零修改。
// 主题: consolidate 4 段 + restore + WAL 增量日志 (D6 固化 + #21 WAL 事件溯源)

// WAL:追加一条增量固化操作日志(#21)
const WAL_AUTO_LIMIT : Int = 4096   // P3-1:超过此条数自动压缩(防服务长跑 WAL 无界增长)
const WAL_AUTO_KEEP : Int = 2048    // P3-1:压缩后保留的最近条数(确定性阈值,同输入同压缩点)
fn ProphecyEngine::wal_append(self : ProphecyEngine, line : String) -> Unit {
  self.wal_log.push(line)
  // 自动压缩:仅当超过上限时裁剪到最近 WAL_AUTO_KEEP 条(幂等、确定性;对 ≤ 上限的既有行为零影响)。
  // 注意:压缩后 WAL 仅保证最近 WAL_AUTO_KEEP 条(近段增量),wal_replay 不再保证完整事件溯源;
  // 完整持久化/恢复请走 to_json/from_json(memories 全量序列化),勿依赖 wal_replay 作为唯一容灾来源。
  if self.wal_log.length() >= WAL_AUTO_LIMIT {
    self.wal_compact(WAL_AUTO_KEEP)
  }
}

// ---------------------------------------------------------------------------
// D6 固化重放
// ---------------------------------------------------------------------------

// P4 重构:consolidate 112 行拆为 4 段私有 fn
//   - consolidate_edge_decay: 边权按 recency 弹性衰减,< MIN_EDGE 或目标不存在则丢弃
//   - consolidate_trans_decay: 转移计数乘 TRANS_DECAY,< 0.5 丢弃
//   - consolidate_prune_nodes: (可选) 压 predictive_value<0.08 & use_count<=1 & 不在 context 的节点
//   - consolidate_meta_cognition: 近期 50 步命中率 → explore 参数自适应
//   快照/重算价值仍留在主 fn(属于编排不变量,F1 约束)

// 段 1:边权弹性衰减 + 边剪枝(始终执行;不依赖 prune flag)
fn ProphecyEngine::consolidate_edge_decay(self : ProphecyEngine) -> Unit {
  for mid, m in self.memories.iter2() {
    let new_edges : Map[String, Double] =
      Map::from_iter(([] : Array[(String, Double)]).iter())
    for nb, w in m.edges.iter2() {
      // 弹性遗忘:边随「节点真实时差」加速衰减(陈旧关联更快淡出,新鲜关联保持)
      let gap = self.clock - m.last_active
      let recency = if gap < 0.0 { 1.0 } else { @math.exp(-gap / REC_TAU) }
      let eff_decay = EDGE_DECAY * (0.5 + 0.5 * recency) // ∈ (0, EDGE_DECAY] ⊂ (0,1)
      let nw = w * eff_decay
      if nw >= MIN_EDGE && self.memories.contains(nb) {
        new_edges.set(nb, nw)
      }
    }
    // 对齐 D1–D8 规范:无论是否剪枝都写回衰减后的边权(否则 EDGE_DECAY 在干净网络上形同虚设)
    let m2 = m
    m2.edges = new_edges
    self.memories.set(mid, m2)
  }
}

// 段 2:转移计数衰减(对齐 D1–D8 规范:缓慢衰减,权重 < 0.5 的转移被丢弃)
fn ProphecyEngine::consolidate_trans_decay(self : ProphecyEngine) -> Unit {
  for a, d in self.transitions.iter2() {
    let newd : Map[String, Double] = Map::from_iter(([] : Array[(String, Double)]).iter())
    for dst, c in d.iter2() {
      let nc = c * TRANS_DECAY
      if nc >= 0.5 {
        newd.set(dst, nc)
      }
    }
    self.transitions.set(a, newd)
  }
}

// 段 3:(可选) 压缩零预测价值噪声节点;返回剪枝数
fn ProphecyEngine::consolidate_prune_nodes(self : ProphecyEngine) -> Int {
  let mut removed = 0
  let ids : Array[String] = []
  for mid, _ in self.memories.iter2() {
    ids.push(mid)
  }
  for mid in ids {
    match self.memories.get(mid) {
      Some(m) => {
        if m.predictive_value < 0.08 && m.use_count <= 1 &&
           !arr_contains(self.context, mid) {
          for _, other in self.memories.iter2() {
            other.edges.remove(mid)
          }
          self.transitions.remove(mid)
          for _, d in self.transitions.iter2() {
            d.remove(mid)
          }
          self.memories.remove(mid)
          removed = removed + 1
        }
      }
      None => ()
    }
  }
  removed
}

// 段 4:元认知调控 — 近期 50 步命中率 → explore 参数自适应;返回 hr 给主 fn 算 target_lr
fn ProphecyEngine::consolidate_meta_cognition(self : ProphecyEngine) -> Double {
  let recent : Array[Int] = []
  let start = if self.meta_hits.length() > 50 {
    self.meta_hits.length() - 50
  } else {
    0
  }
  for i = start; i < self.meta_hits.length(); i = i + 1 {
    recent.push(self.meta_hits[i])
  }
  let mut hr = 0.5
  if recent.length() > 0 {
    let mut s = 0
    for x in recent {
      s = s + x
    }
    hr = s.to_double() / recent.length().to_double()
  }
  self.explore = clamp01(max0(0.0, (0.6 - hr) * 2.0))
  hr
}

pub fn ProphecyEngine::consolidate(self : ProphecyEngine, prune : Bool) -> Json {
  // 0. 约束契约:固化前对记忆/转移/角色转移做快照,支持回滚
  //    (F1:三者须一致回滚,否则记忆与转移表错位导致预测失真)
  self.snapshot = Some(copy_memories(self.memories))
  self.snap_trans = Some(copy_trans(self.transitions))
  self.snap_role = Some(copy_trans(self.role_trans))
  // 1-2. 衰减(始终执行)
  self.consolidate_edge_decay()
  self.consolidate_trans_decay()
  // 3. 重算预测价值
  self._recompute_values()
  // 4. (可选) 剪枝
  let removed = if prune { self.consolidate_prune_nodes() } else { 0 }
  // 5. 元认知(同时返回 hr 给 target_lr 用)
  let hr = self.consolidate_meta_cognition()
  // 自适应学习率:近期命中率驱动(高→更快适应,低→更稳)
  let target_lr = if hr > 0.8 {
    HEBB_LR * 1.2
  } else if hr < 0.5 {
    HEBB_LR * 0.6
  } else {
    HEBB_LR
  }
  self.hebb_lr = if target_lr < 0.05 {
    0.05
  } else if target_lr > 0.95 {
    0.95
  } else {
    target_lr
  }
  self.invalidate_pred_cache()
  self.rebuild_role_members()
  self.fed_upd = self.fed_upd + 1
  self.stats_evolutions = self.stats_evolutions + 1
  self.mark_tm_idf_dirty()   // S1:剪枝可能删 TM 节点,IDF 表需重算(fuzzy_match 惰性触发)
  self.mark_term_index_dirty()   // P4-2:剪枝可能删术语节点,索引需重建
  obj([
    ("pruned", num_json(removed.to_double())),
    ("nodes", num_json(self.memories.length().to_double())),
    ("edges", num_json(self.edges_count().to_double())),
    ("explore", num_json(r4(self.explore))),
    ("recent_hit_rate", num_json(r4(hr))),
  ])
}

pub fn ProphecyEngine::restore(self : ProphecyEngine) -> Json {
  match self.snapshot {
    Some(s) => {
      self.memories = s
      match self.snap_trans {
        Some(st) => self.transitions = st
        None => ()
      }
      match self.snap_role {
        Some(sr) => self.role_trans = sr
        None => ()
      }
      self.snapshot = None
      self.snap_trans = None
      self.snap_role = None
      self.invalidate_pred_cache()
      self.rebuild_role_members()
      self.rebuild_tm_features()
      self.rebuild_term_index()   // P4-2:回滚后重建术语首字符索引(幂等)
      // P1:restore 可能回滚掉 snapshot 之后的 TM 增删;若期间发生过 rebuild(脏标记已清),
      // IDF/postings/tm_count 仍是回滚前口径 → 置脏强制下次 fuzzy_match 重建(幂等,输出确定性不变)
      self.mark_tm_idf_dirty()
      obj([("ok", true.to_json()), ("nodes", num_json(self.memories.length().to_double()))])
    }
    None => obj([("ok", false.to_json()), ("reason", str_json("no snapshot"))])
  }
}

// ---- #21 中/长期:WAL 增量固化 / 主动学习 / 联邦 / 蒸馏 ----
const WAL_SEP : String = "\u0001"

// 从 WAL 重放重建引擎(事件溯源:仅重放 remember/observe 原子事件)
pub fn ProphecyEngine::wal_replay(self : ProphecyEngine) -> ProphecyEngine {
  let e = ProphecyEngine::make()
  for line in self.wal_log {
    let parts = line.split(WAL_SEP).to_array()
    // 记录格式: op␁mid␁text␁mtype(␁ = WAL_SEP);text/mtype 已做 wal_escape_field 转义
    if parts.length() >= 4 {
      let op = parts[0].to_owned()
      let text = wal_unescape_field(parts[2].to_owned())
      let mtype = wal_unescape_field(parts[3].to_owned())
      // P6 加固:只接受已知的 op,未知 op 直接跳过,防止被污染 WAL 破坏状态
      if op == "observe" {
        let _ = e.observe(text, mtype)
      } else if op == "remember" {
        let _ = e.remember(text, mtype, [])
      }
    }
  }
  e
}

pub fn ProphecyEngine::wal_export(self : ProphecyEngine) -> Array[String] {
  self.wal_log
}

pub fn ProphecyEngine::wal_compact(self : ProphecyEngine, keep : Int) -> Unit {
  if self.wal_log.length() > keep {
    let start = self.wal_log.length() - keep
    let kept : Array[String] = []
    for i = start; i < self.wal_log.length(); i = i + 1 {
      kept.push(self.wal_log[i])
    }
    self.wal_log = kept
  }
}

pub fn ProphecyEngine::wal_clear(self : ProphecyEngine) -> Unit {
  self.wal_log = []
}

pub fn ProphecyEngine::wal_len(self : ProphecyEngine) -> Int {
  self.wal_log.length()
}