///|
/// 存储后端抽象。
///
/// 引擎只依赖这个 trait,不直接碰任何 IO 原语。这不是为了「可插拔」这种
/// 架构洁癖,而是为了两件具体的事:
///
/// 1. **崩溃契约可以测**。C1「已确认的写入必不丢」要验证「fsync 之后
///    断电还在」,就必须能模拟断电。用真进程 + kill -9 只能测到「进程
///    死了数据还在」,测不出「忘了 fsync」——kill -9 不清 OS 页缓存。
///    `MemStorage` 维护「当前内容」和「已落盘内容」两份影子,
///    `power_loss()` 回滚到后者,于是断电语义可以在 `moon test` 里
///    确定性地跑,还能在 wasm-gc / js 后端上跑。
///
/// 2. **平台差异被关在一处**。`fsync` 在 POSIX 存在、Windows 没有
///    (只有 `_commit`);`ftruncate` 同理。这两个符号名不同、
///    Windows 的 CRT 里根本没有 POSIX 那个名字,链接期就会失败。
///    差异全部收在 `fs_native.mbt`,核心逻辑一行平台判断都没有。
///
/// 所有名字都是**相对路径**,由后端自己决定根目录。
pub trait Storage {
  ///|
  /// 后端就绪(native 后端在这里建目录)。可重复调用。
  fn prepare(Self) -> Unit raise LsmError

  ///|
  /// 读整个文件。不存在返回 `None`,**不是错误**——
  /// 「文件不存在」和「读失败」在恢复路径上是两种完全不同的处理。
  fn read_file(Self, String) -> Bytes? raise LsmError

  ///|
  /// 覆盖写:先截断再写。用于 SST、MANIFEST 这类一次成型的文件。
  fn write_file(Self, String, Bytes) -> Unit raise LsmError

  ///|
  /// 追加写。用于 WAL——每帧追加,不重写已有内容。
  fn append_file(Self, String, Bytes) -> Unit raise LsmError

  ///|
  /// 批量追加:语义等价于对 `chunks` 里每一段依次调用 `append_file`,
  /// 但**只打开一次文件**。
  ///
  /// 为什么需要单独一个方法:真文件系统上 `fopen`/`fclose` 的开销远大于
  /// `fwrite`。在 Windows 上实测每次追加约 5.9 ms,而且与写入字节数几乎无关
  /// —— 一个 200 条的 `putmany` 因此要 1.2 秒,其中 99% 花在反复开关文件上。
  ///
  /// 注意:这个方法**不隐含 sync**,和 `append_file` 一样。
  fn append_frames(Self, String, Array[Bytes]) -> Unit raise LsmError

  ///|
  /// 把已写入的数据刷到持久介质。返回后,这些字节必须能在断电后存活。
  ///
  /// 这是 C1 的唯一实现点。注意 `append_file` **不隐含** sync:
  /// 批量提交时调用方可以选择一次 sync 覆盖多条记录。
  fn sync_file(Self, String) -> Unit raise LsmError

  ///|
  /// 同目录内原子改名。CURRENT 的原子切换靠它。
  fn rename_file(Self, String, String) -> Unit raise LsmError

  ///|
  /// 删除文件。文件不存在不算错误(恢复路径会重复清理)。
  fn remove_file(Self, String) -> Unit raise LsmError

  ///|
  /// 文件大小。不存在返回 `None`。
  fn file_size(Self, String) -> Int32? raise LsmError

  ///|
  /// 列出**某个子目录**下的文件名(返回 basename,不含目录部分)。
  ///
  /// 参数是目录名而不是名字前缀。这个区别很重要:
  /// 前缀过滤会把 `SST-000001` 和 `SST-000001.bak` 一起捞出来,
  /// 而目录列举天然只给出「这个目录里真实存在的东西」。
  ///
  /// `dir == ""` 表示数据目录本身(只返回根下的文件,不含子目录内容)。
  ///
  /// 返回结果按字节序排序 —— 配合零填充的文件编号,
  /// 「编号升序」就等于「从旧到新」,调用方不必自己排。
  fn list_files(Self, String) -> Array[String] raise LsmError
}

///|
/// 内存后端。
///
/// 两份影子:`files` 是「当前内容」(含尚未落盘的),`durable` 是
/// 「已 fsync 的内容」。正常读写只看 `files`;`power_loss()` 把
/// `files` 回滚到 `durable`,于是「断电」变成一次确定性的内存操作。
///
/// 这个模型刻意比真磁盘更严格:真磁盘上未 fsync 的页**可能**会幸存
/// (OS 迟早会写下去),也可能不会。这里一律当作「不会」,
/// 也就是**只允许更悲观的崩溃**。测试因此不会假阳性。
pub struct MemStorage {
  files : Map[String, Bytes]
  durable : Map[String, Bytes]
  mut syncs : Int32
  mut fail_sync : Bool
  mut fail_write : Bool
}

///|
pub fn MemStorage::new() -> MemStorage {
  {
    files: Map([]),
    durable: Map([]),
    syncs: 0,
    fail_sync: false,
    fail_write: false,
  }
}

///|
/// 已经发生的 `sync_file` 次数。用来断言「批量写只 sync 一次」这类行为。
pub fn MemStorage::syncs(self : MemStorage) -> Int32 {
  self.syncs
}

///|
/// 让下一次 `sync_file` 失败。用来测「fsync 失败时不能对外确认」。
pub fn MemStorage::set_fail_sync(self : MemStorage, v : Bool) -> Unit {
  self.fail_sync = v
}

///|
/// 让下一次 `append_file` / `write_file` 失败。
pub fn MemStorage::set_fail_write(self : MemStorage, v : Bool) -> Unit {
  self.fail_write = v
}

///|
/// 模拟断电:所有未 sync 的改动消失,已 sync 的回到 sync 时的样子。
///
/// 注意顺序:先删掉「只在 files 里、durable 里没有」的文件(写了一半
/// 就断电,文件本身都不该存在),再把 durable 的内容覆盖回去。
pub fn MemStorage::power_loss(self : MemStorage) -> Unit {
  let names : Array[String] = []
  for k, _ in self.files {
    names.push(k)
  }
  for k in names {
    if self.durable.get(k) is None {
      ignore(self.files.remove(k))
    }
  }
  for k, v in self.durable {
    self.files.set(k, v)
  }
}

///|
/// 直接设定「磁盘上已有的内容」,同时写进两份影子。
///
/// 用于构造恢复场景:手工摆出「WAL 尾部有半条记录」「MANIFEST 指向
/// 不存在的 SST」这类状态,比用引擎自己写出这种状态可靠得多。
pub fn MemStorage::plant(
  self : MemStorage,
  name : String,
  data : Bytes,
) -> Unit {
  self.files.set(name, data)
  self.durable.set(name, data)
}

///|
/// 只改「已落盘内容」,不动当前视图。用于构造「磁盘上有一份旧数据,
/// 而当前视图是新的」这种不一致状态。
pub fn MemStorage::plant_durable(
  self : MemStorage,
  name : String,
  data : Bytes,
) -> Unit {
  self.durable.set(name, data)
}

///|
/// 当前所有文件名,测试断言用。
pub fn MemStorage::names(self : MemStorage) -> Array[String] {
  let out : Array[String] = []
  for k, _ in self.files {
    out.push(k)
  }
  out.sort_by(fn(a, b) { compare_strings(a, b) })
  out
}

///|
fn MemStorage::guard_write(
  self : MemStorage,
  name : String,
) -> Unit raise LsmError {
  if self.fail_write {
    raise LsmError::io("MemStorage: 注入的写入失败 (\{name})")
  }
}

///|
/// 显式声明「trait 方法同时作为 `MemStorage` 的普通方法可用」。
///
/// 不写这行的后果(新版工具链):`[0079] implicit_impl_as_method` ——
/// `impl` 里的方法被**隐式**提升为类型自身的普通方法,这个隐式行为已废弃。
/// 本地 0.1.20260827 没有这条检查,所以本地零警告、CI 全红。
pub extend MemStorage with Storage::{
  list_files,
  rename_file,
  append_frames,
  prepare,
  write_file,
  append_file,
  file_size,
  read_file,
  remove_file,
  sync_file,
}

///|
pub impl Storage for MemStorage with fn prepare(_self) -> Unit raise LsmError {

}

///|
pub impl Storage for MemStorage with fn read_file(self, name) -> Bytes? raise LsmError {
  self.files.get(name)
}

///|
pub impl Storage for MemStorage with fn write_file(self, name, data) -> Unit raise LsmError {
  self.guard_write(name)
  self.files.set(name, data)
}

///|
pub impl Storage for MemStorage with fn append_file(self, name, data) -> Unit raise LsmError {
  self.guard_write(name)
  let merged = match self.files.get(name) {
    Some(old) => {
      let b = Buffer::Buffer()
      b.write_bytes(old[:])
      b.write_bytes(data[:])
      b.to_bytes()
    }
    None => data
  }
  self.files.set(name, merged)
}

///|
/// 批量追加。内存后端没有「打开文件」的成本,所以这里刻意**退化成一次
/// 拼接**而不是循环调用 `append_file`:N 次追加 N 次拼接是 O(N²) 的字节拷贝,
/// 而先算总长再一次性写是 O(N)。
///
/// 语义必须与循环调用完全一致(同样的失败注入、同样的结果字节),
/// 否则内存后端上的测试就不能代表真文件系统的行为了。
pub impl Storage for MemStorage with fn append_frames(self, name, chunks) -> Unit raise LsmError {
  self.guard_write(name)
  if chunks.length() == 0 {
    return
  }
  let b = Buffer::Buffer()
  match self.files.get(name) {
    Some(old) => b.write_bytes(old[:])
    None => ()
  }
  for c in chunks {
    b.write_bytes(c[:])
  }
  self.files.set(name, b.to_bytes())
}

///|
pub impl Storage for MemStorage with fn sync_file(self, name) -> Unit raise LsmError {
  if self.fail_sync {
    raise LsmError::io("MemStorage: 注入的 fsync 失败 (\{name})")
  }
  self.syncs = self.syncs + 1
  match self.files.get(name) {
    Some(v) => self.durable.set(name, v)
    None => ignore(self.durable.remove(name))
  }
}

///|
/// 改名同时作用于两份影子:本模型假设调用方在 rename 之后会 fsync
/// 所在目录(native 后端确实这么做),所以 rename 本身是立即持久的。
pub impl Storage for MemStorage with fn rename_file(self, from, to) -> Unit raise LsmError {
  match self.files.get(from) {
    Some(v) => {
      self.files.set(to, v)
      ignore(self.files.remove(from))
      self.durable.set(to, v)
      ignore(self.durable.remove(from))
    }
    None => raise LsmError::io("MemStorage: 改名源不存在 (\{from})")
  }
}

///|
pub impl Storage for MemStorage with fn remove_file(self, name) -> Unit raise LsmError {
  ignore(self.files.remove(name))
  ignore(self.durable.remove(name))
}

///|
pub impl Storage for MemStorage with fn file_size(self, name) -> Int32? raise LsmError {
  match self.files.get(name) {
    Some(v) => Some(v.length())
    None => None
  }
}

///|
pub impl Storage for MemStorage with fn list_files(self, dir) -> Array[String] raise LsmError {
  // 内存后端的 key 就是完整相对路径(`wal/000001.wal`),
  // 所以「列目录」= 挑出以 `dir + "/"` 开头、且后面没有更多分隔符的 key,
  // 再去掉目录前缀返回 basename。这样它的行为与真文件系统一致。
  let out : Array[String] = []
  let prefix = if dir == "" { "" } else { dir + "/" }
  for k, _ in self.files {
    if !has_prefix(k, prefix) {
      continue
    }
    let rest = k[prefix.length():].to_owned()
    if !contains_slash(rest) {
      out.push(rest)
    }
  }
  out.sort_by(fn(a, b) { compare_strings(a, b) })
  out
}

///|
/// `s` 里是否含路径分隔符。用来区分「这一层的文件」和「子目录里的文件」。
fn contains_slash(s : String) -> Bool {
  let b = @utf8.encode(s)
  for i in 0.. Bool {
  let sb = @utf8.encode(s)
  let pb = @utf8.encode(prefix)
  if pb.length() > sb.length() {
    return false
  }
  for i in 0..