///|
/// SSTable 的写入与读取:`data block* | filter block | index block | footer(48)`
///
/// 磁盘布局见 `docs/design/format.md` §3.1。
///
/// **这一层只处理 `Bytes`,不碰文件系统。** 这不是偷懒,是有意的分层:
/// - 核心(memtable / block / sstable)保持 backend 无关,
///   CI 会在 wasm-gc / js 上编译它,证明没有偷偷依赖 native 特性
/// - 文件 IO 放在边界层(`fs` / dbcli),换平台或换存储介质只改那一层
/// - 测试不需要临时目录、不需要清理、不会因为 Windows 文件锁而随机失败
///
/// 代价是一次性把整个 SST 放进内存。对 v1 的规模(几十 MB)可以接受,
/// 后面引入 mmap 或分块读时,只需替换 `SstReader` 的取字节方式。

///|
/// footer 固定 48 字节(§3.5)。
pub const SST_FOOTER_SIZE : Int32 = 48

///|
/// 当前写出的 SST 版本。读到更大的版本必须拒绝打开(§5.2)。
///
/// **2** = data block / index block 尾部带 CRC32C(ADR-0010)。
/// 写端永远写这个版本,读端同时认 1(老库)与 2。
pub const SST_VERSION : Int32 = 2

///|
/// 唯一仍然支持的旧版本:block 没有尾部 CRC。
///
/// 留着它是为了「升级不炸库」—— 读到 v1 文件照常能读,
/// 但**不做内容校验**。下一次 compaction 会把数据重写成 v2。
pub const SST_VERSION_V1 : Int32 = 1

///|
/// 文件末尾的魔数 `"MSST"`。
const SST_MAGIC : Bytes = b"MSST"

///|
/// 解析出来的 footer。
pub struct SstFooter {
  filter_offset : Int32
  filter_size : Int32
  index_offset : Int32
  index_size : Int32
  smallest_seq : UInt64
  version : Int32
}

///|
/// 比较 `data[pos..]` 处的若干字节是否等于 `want`。
fn bytes_match(data : Bytes, pos : Int32, want : Bytes) -> Bool {
  if pos < 0 || pos + want.length() > data.length() {
    return false
  }
  for i in 0.. Int32? {
  match read_fixed64(data, pos) {
    Some(v) => if v > 0x7FFFFFFFUL { None } else { Some(v.to_int()) }
    None => None
  }
}

///|
/// 解码索引条目里的 block handle:`offset(varint64) + size(varint64)`。
fn decode_handle(v : Bytes) -> (Int32, Int32)? {
  let (off, p1) = match read_varint64(v, 0, v.length()) {
    Some(pair) => pair
    None => return None
  }
  let (size, _) = match read_varint64(v, p1, v.length()) {
    Some(pair) => pair
    None => return None
  }
  if off > 0x7FFFFFFFUL || size > 0x7FFFFFFFUL {
    return None
  }
  Some((off.to_int(), size.to_int()))
}

///|
/// 编码一个 block handle。
fn encode_handle(offset : Int32, size : Int32) -> Bytes {
  let b = Buffer::Buffer()
  write_varint64(b, offset.to_uint64())
  write_varint64(b, size.to_uint64())
  b.to_bytes()
}

///|
/// 按文件版本挑解码器。v2 校验尾部 CRC,v1 不校验。
///
/// 把版本判断收在这一处,是为了不让「哪条路径用哪个解码器」散在五个调用点 ——
/// 漏一处就会把 v1 的 block 当 v2 解,读出来的 `num_restarts` 是 CRC 的字节。
fn decode_block_ver(raw : Bytes, version : Int32) -> Block? {
  if version >= SST_VERSION {
    decode_block(raw)
  } else {
    decode_block_v1(raw)
  }
}

///|
/// filter 也一样按版本挑解码器(v2 有尾部 CRC)。
fn decode_bloom_ver(raw : Bytes, version : Int32) -> BloomFilter? {
  if version >= SST_VERSION {
    decode_bloom(raw)
  } else {
    decode_bloom_v1(raw)
  }
}

///|
/// 逐条追加、最后 `finish()` 出整个 SST 字节。
///
/// 使用契约:`add` 的 key 必须**严格升序**(与 `BlockBuilder` 一致)。
/// SST 内部按 key 有序,这是「二分查 index」和「归并 compaction」的前提。
///
/// **已知取舍**:`keys` 把所有 key 留在内存里,因为 bloom 需要在
/// `finish` 时知道元素个数才能定位图大小。1 万条 × 10 字节 = 100 KB,
/// 可以接受;到百万级需要改成「两遍写」或「可增长的 bloom」。
/// 这条记在 `PLAN.md` 的技术债里,不是忘了。
pub struct SstWriter {
  out : Buffer
  block : BlockBuilder
  index : BlockBuilder
  mut count : Int32
  mut block_count : Int32
  mut smallest_seq : UInt64
  keys : Array[Bytes]
  /// 要写出的格式版本。生产路径恒为 `SST_VERSION`;
  /// 只有兼容性测试会把它设成 `SST_VERSION_V1` 来造老文件。
  version : Int32
}

///|
pub fn SstWriter::new() -> SstWriter {
  SstWriter::with_version(SST_VERSION)
}

///|
/// 按指定版本造 writer。生产路径只有 `new()`(v2);
/// 兼容性测试用 `with_version(SST_VERSION_V1)` 造 v1 老文件。
fn SstWriter::with_version(version : Int32) -> SstWriter {
  {
    out: Buffer::Buffer(),
    block: BlockBuilder::new(),
    index: BlockBuilder::new(),
    count: 0,
    block_count: 0,
    smallest_seq: 0xFFFFFFFFFFFFFFFFUL,
    keys: [],
    version,
  }
}

///|
/// 已写入的条目数。
pub fn SstWriter::entry_count(self : SstWriter) -> Int32 {
  self.count
}

///|
/// 已封盘的 data block 数。
pub fn SstWriter::block_count(self : SstWriter) -> Int32 {
  self.block_count
}

///|
/// 追加一条。key 必须严格大于上一条。
pub fn SstWriter::add(self : SstWriter, entry : Entry) -> Unit raise LsmError {
  self.block.add(entry)
  self.keys.push(entry.key)
  self.count = self.count + 1
  if entry.seq < self.smallest_seq {
    self.smallest_seq = entry.seq
  }
  if self.block.should_finish() {
    self.flush_block()
  }
}

///|
/// 把当前 block 落进 `out`,并在索引里记一条 `last_key -> (offset, size)`。
fn SstWriter::flush_block(self : SstWriter) -> Unit raise LsmError {
  if self.block.is_empty() {
    return
  }
  let raw = if self.version >= SST_VERSION {
    self.block.finish()
  } else {
    self.block.finish_v1()
  }
  let offset = self.out.length()
  self.out.write_bytes(raw[:])
  // 注意顺序:last_key 必须在 reset 之前取
  self.index.add({
    key: self.block.last_key(),
    value: encode_handle(offset, raw.length()),
    seq: 0UL,
    deleted: false,
  })
  self.block.reset()
  self.block_count = self.block_count + 1
}

///|
/// 产出完整 SST 字节。
///
/// 顺序严格按 §3.1:data blocks → filter → index → footer。
/// filter 要等所有 key 都写完才能建(需要知道元素个数来定位图大小),
/// 所以它只能在 `finish` 里生成。
pub fn SstWriter::finish(self : SstWriter) -> Bytes raise LsmError {
  self.flush_block()
  let bloom = BloomFilter::new(self.keys.length())
  for k in self.keys {
    bloom.put(k)
  }
  let filter = if self.version >= SST_VERSION {
    bloom.encode()
  } else {
    bloom.encode_v1()
  }
  let filter_offset = self.out.length()
  self.out.write_bytes(filter[:])
  let index_raw = if self.version >= SST_VERSION {
    self.index.finish()
  } else {
    self.index.finish_v1()
  }
  let index_offset = self.out.length()
  self.out.write_bytes(index_raw[:])
  write_fixed64(self.out, filter_offset.to_uint64())
  write_fixed64(self.out, filter.length().to_uint64())
  write_fixed64(self.out, index_offset.to_uint64())
  write_fixed64(self.out, index_raw.length().to_uint64())
  // 空 SST 没有 seq,写 0 而不是 0xFFFF...,避免读端把「未初始化」当真实值
  write_fixed64(self.out, if self.count == 0 { 0UL } else { self.smallest_seq })
  write_fixed32(self.out, self.version)
  self.out.write_bytes(SST_MAGIC[:])
  self.out.to_bytes()
}

///|
/// 一个已打开的 SST。持有整个文件字节与解析好的 index / filter。
pub struct SstReader {
  data : Bytes
  footer : SstFooter
  index : Block
  bloom : BloomFilter
  /// 文件覆盖的 key 区间 `[lo, hi]`(闭区间,且是**真实下界/上界**)。
  ///
  /// `None` 表示「拿不到边界」—— 空文件、或首块解码失败。
  /// 拿到 `None` 时所有范围判断都必须放行,不能猜:
  /// 猜错下界的后果是「数据明明在,却被跳过」,这是不可接受的错误方向。
  range : (Bytes, Bytes)?
}

///|
/// 打开一个 SST。任何结构性问题返回 `None`,不 panic。
///
/// 校验顺序刻意从「最便宜、最能早退」开始:长度 → magic → version → 区域边界。
/// 先查 magic 再查 offset 很重要 —— 如果文件根本不是 SST,
/// 那些 offset 是垃圾值,拿它们做边界检查只是白算。
pub fn open_sst(data : Bytes) -> SstReader? {
  let n = data.length()
  if n < SST_FOOTER_SIZE {
    return None
  }
  let f = n - SST_FOOTER_SIZE
  if !bytes_match(data, f + 44, SST_MAGIC) {
    return None
  }
  let version = match read_fixed32(data, f + 40) {
    Some(v) => v
    None => return None
  }
  if version != SST_VERSION && version != SST_VERSION_V1 {
    // §5.2:读到不认识的版本必须拒绝打开,绝不猜怎么读
    return None
  }
  let filter_offset = match read_offset(data, f) {
    Some(v) => v
    None => return None
  }
  let filter_size = match read_offset(data, f + 8) {
    Some(v) => v
    None => return None
  }
  let index_offset = match read_offset(data, f + 16) {
    Some(v) => v
    None => return None
  }
  let index_size = match read_offset(data, f + 24) {
    Some(v) => v
    None => return None
  }
  let smallest_seq = match read_fixed64(data, f + 32) {
    Some(v) => v
    None => return None
  }
  // 两个区域都必须完整落在 footer 之前,且 filter 在 index 之前
  if filter_offset + filter_size > f || index_offset + index_size > f {
    return None
  }
  if index_offset < filter_offset + filter_size {
    return None
  }
  let filter_raw = data[filter_offset:filter_offset + filter_size].to_owned()
  let bloom = match decode_bloom_ver(filter_raw, version) {
    Some(b) => b
    None => return None
  }
  let index_raw = data[index_offset:index_offset + index_size].to_owned()
  let index = match decode_block_ver(index_raw, version) {
    Some(b) => b
    None => return None
  }
  // 文件的 key 上界 = 最后一个 index 条目的 key(index 存的是每块的 last_key)。
  // 下界 = 首块的第一条 key。两者都必须**真实**成立,否则范围判断会误杀数据。
  let hi = match index.last() {
    Some(e) => e.key
    None => b""
  }
  let lo = if hi.length() == 0 {
    b""
  } else {
    match index.first() {
      Some(ie) =>
        match decode_handle(ie.value) {
          Some((off, size)) =>
            if off + size > data.length() {
              b""
            } else {
              match decode_block_ver(data[off:off + size].to_owned(), version) {
                Some(b) =>
                  match b.first() {
                    Some(e) => e.key
                    None => b""
                  }
                None => b""
              }
            }
          None => b""
        }
      None => b""
    }
  }
  // 只有上下界都拿到(且不为空)才启用范围判断
  let range = if hi.length() == 0 || lo.length() == 0 {
    None
  } else {
    Some((lo, hi))
  }
  Some({
    data,
    footer: {
      filter_offset,
      filter_size,
      index_offset,
      index_size,
      smallest_seq,
      version,
    },
    index,
    bloom,
    range,
  })
}

///|
pub fn SstReader::footer(self : SstReader) -> SstFooter {
  self.footer
}

///|
/// data block 个数(等于索引条目数)。
pub fn SstReader::block_count(self : SstReader) -> Int32 raise LsmError {
  self.index.entries().length()
}

///|
/// 按 key 查找。命中墓碑时返回 `deleted = true` 的条目,
/// **调用方必须自己判断**(上层要知道「这里有一个删除」,而不是「查不到」)。
///
/// 三步:bloom 快速否定 → index 定位 block → block 内二分。
pub fn SstReader::get(self : SstReader, key : Bytes) -> Entry? {
  let (h1, h2) = bloom_hashes(key)
  self.get_hashed(key, h1, h2)
}

///|
/// 同 `get`,但复用调用方算好的双哈希。
///
/// 一次点查要探 N 个 SST 的 filter,用的是同一条 key ——
/// 让每个文件各算一遍 FNV 是纯浪费。N 个文件时哈希成本从 N 次降到 1 次。
pub fn SstReader::get_hashed(
  self : SstReader,
  key : Bytes,
  h1 : UInt64,
  h2 : UInt64,
) -> Entry? {
  // 先比 key 范围,再问 bloom。
  //
  // 顺序是有讲究的:范围判断是两次 `memcmp`,bloom 是 7 次随机位访问。
  // L2 里的文件按 key 有序且互不重叠,一次未命中在 77 个文件里
  // 有 76 个能被这一步直接排除 —— bloom 从探 77 次降到 1 次。
  match self.range {
    Some((lo, hi)) =>
      if compare_bytes(key, lo) < 0 || compare_bytes(key, hi) > 0 {
        return None
      }
    None => ()
  }
  if !self.bloom.may_contain_hashes(h1, h2) {
    return None
  }
  let idx = match self.index.seek(key) {
    Some(e) => e
    None => return None
  }
  let (off, size) = match decode_handle(idx.value) {
    Some(p) => p
    None => return None
  }
  if off + size > self.data.length() {
    return None
  }
  let blk = match
    decode_block_ver(self.data[off:off + size].to_owned(), self.footer.version) {
    Some(b) => b
    None => return None
  }
  blk.get(key)
}

///|
/// 按 key 顺序解出全部条目。用于全扫描、校验与测试。
pub fn SstReader::entries(self : SstReader) -> Array[Entry] raise LsmError {
  let out : Array[Entry] = []
  for ie in self.index.entries() {
    let (off, size) = match decode_handle(ie.value) {
      Some(p) => p
      None =>
        raise LsmError::corruption(
          "sst: 索引条目里的 block handle 解码失败",
        )
    }
    if off + size > self.data.length() {
      raise LsmError::corruption("sst: data block 越界")
    }
    let blk = match
      decode_block_ver(
        self.data[off:off + size].to_owned(),
        self.footer.version,
      ) {
      Some(b) => b
      None => raise LsmError::corruption("sst: data block 解码失败")
    }
    for e in blk.entries() {
      out.push(e)
    }
  }
  out
}

///|
/// 只解出 `[start, end)` 区间里的条目,顺序与 `entries()` 一致。
///
/// **这是 `scan` 的性能关键。** `entries()` 会把整个文件解出来 ——
/// 一个 1 MiB 的 SST 大约 1 万条,扫 500 条也要付 1 万条的解码成本。
/// 数据量一大,`scan` 就退化成 O(整个库),而调用方往往只要一小段。
///
/// 做法是拿索引做**块级剪枝**:索引里每条记录的是该 block 的 `last_key`,
/// 于是
///
/// - 二分找到第一个 `last_key >= start` 的 block —— 它之前的所有 block
///   最大 key 都小于 start,整块可以跳过
/// - 往后逐块解,块内逐条过滤(只有首尾两块可能部分越界)
/// - 遇到 `last_key >= end` 的 block,解完它就停 —— block 之间按 key
///   严格递增,后面每一块的 first_key 都更大,必然全部越界
///
/// 块内过滤而不是块内二分:一个 block 只有 4 KiB / 一百多条,
/// 解出来再线性筛比再写一套「块内定位 + 向后遍历」简单得多,
/// 而且这部分成本与块大小同阶,不是瓶颈。
///
/// **不接 `limit`**:单看一个文件,无法判断「前 N 条」是不是全局的前 N 条
/// (多路归并要跨文件比较)。限制由调用方在归并之后施加。
pub fn SstReader::scan_range(
  self : SstReader,
  start : Bytes,
  end : Bytes,
) -> Array[Entry] raise LsmError {
  let out : Array[Entry] = []
  // 整个文件与查询区间不相交就直接返回空,连索引二分都不用做。
  // 窄区间扫描时这一步能排除掉绝大多数文件。
  match self.range {
    Some((lo, hi)) =>
      if compare_bytes(hi, start) < 0 || compare_bytes(lo, end) >= 0 {
        return out
      }
    None => ()
  }
  let ies = self.index.entries()
  // 二分:第一个 last_key >= start 的 block
  let mut lo = 0
  let mut hi = ies.length()
  while lo < hi {
    let mid = (lo + hi) / 2
    if compare_bytes(ies[mid].key, start) < 0 {
      lo = mid + 1
    } else {
      hi = mid
    }
  }
  let mut i = lo
  while i < ies.length() {
    let ie = ies[i]
    let (off, size) = match decode_handle(ie.value) {
      Some(p) => p
      None =>
        raise LsmError::corruption(
          "sst: 索引条目里的 block handle 解码失败",
        )
    }
    if off + size > self.data.length() {
      raise LsmError::corruption("sst: data block 越界")
    }
    let blk = match
      decode_block_ver(
        self.data[off:off + size].to_owned(),
        self.footer.version,
      ) {
      Some(b) => b
      None => raise LsmError::corruption("sst: data block 解码失败")
    }
    for e in blk.entries() {
      if compare_bytes(e.key, start) >= 0 && compare_bytes(e.key, end) < 0 {
        out.push(e)
      }
    }
    // 这一块的 last_key 已经 >= end,后面的块必然全部越界
    if compare_bytes(ie.key, end) >= 0 {
      break
    }
    i = i + 1
  }
  out
}

///|
/// SST 上的**惰性游标**:只在被推进到新块时才解一块。
///
/// 与 `scan_range` 只差一处,但那一处是数量级的:`scan_range` 会把整个
/// 区间的条目一次全解出来,游标则是「要一条解一块」。
/// `Db::scan` 带 `limit` 时,差别就是「要 10 条只解 1 块」对
/// 「先把整个区间解完,再丢掉后面 99%」。
///
/// 为什么不是「给 `scan_range` 加个 limit 参数」:单看一个文件无法判断
/// 「前 N 条」是不是全局的前 N 条(多路归并要跨文件比较),
/// 所以限制只能由归并层施加,而限制一旦在归并层,来源就必须能被惰性推进。
priv struct SstCursor {
  reader : SstReader
  /// 索引条目(每个 block 的 last_key 与 handle)。构造时取一次,
  /// 之后每解一块只查一次数组,不重新解索引。
  ies : Array[Entry]
  start : Bytes
  end : Bytes
  /// 下一个要解的 block 下标。解完一块就 +1。
  mut block_i : Int32
  /// 当前块里**已经按区间过滤过**的条目。块消费完才换下一块。
  mut block : Array[Entry]
  mut pos : Int32
  /// 没有更多块了。**注意**:为 true 时 `block` 里可能还有没消费完的条目,
  /// 所以判断顺序必须是「先看 pos,再看 done」。
  mut done : Bool
  /// 解码失败过。
  ///
  /// `peek` 不能抛错 —— 归并器是纯函数,`MergeIterator::next` 的签名里
  /// 没有 `raise`,为了这个改动去改它的签名会波及 compaction 与全部归并测试。
  /// 所以这里只记标志,由 `Db::scan` 在返回前检查。
  mut failed : Bool
}

///|
/// 这个文件是否**可能**与 `[start, end)` 相交。
///
/// `range` 为 `None`(拿不到边界)时一律放行 —— 猜错下界的后果是
/// 「数据明明在却被跳过」,比多扫一个文件严重得多。
///
/// 为什么要单独暴露这一步:`cursor` 内部也会做同样的判断,但那是
/// **单个文件内部**的事。归并器每取一行都要遍历所有来源,来源数直接
/// 乘在热路径上 —— 55 个文件里只有 1 个可能相交时,把另外 54 个
/// 放进归并器就是 54 倍的无谓开销。
fn SstReader::overlaps(self : SstReader, start : Bytes, end : Bytes) -> Bool {
  match self.range {
    Some((lo, hi)) =>
      compare_bytes(hi, start) >= 0 && compare_bytes(lo, end) < 0
    None => true
  }
}

///|
/// 在一个 SST 上开游标,范围 `[start, end)`。
///
/// 与 `scan_range` 一样先做**文件级剪枝**(整个文件与区间不相交就直接空游标),
/// 再用索引二分定位第一个可能相交的 block。区别只是定位完不立刻解块。
fn SstReader::cursor(self : SstReader, start : Bytes, end : Bytes) -> SstCursor {
  // `entries()` 会抛 corruption,而游标的 `peek` 不能抛(见 `failed` 字段的说明)。
  // 所以这里把错误就地转成「空且失败」的游标,由 `Db::scan` 统一检查。
  let mut idx_failed = false
  let ies = self.index.entries() catch {
    _ => {
      idx_failed = true
      []
    }
  }
  let cur = {
    reader: self,
    ies,
    start,
    end,
    block_i: 0,
    block: [],
    pos: 0,
    done: false,
    failed: false,
  }
  if idx_failed {
    cur.failed = true
    cur.done = true
    return cur
  }
  match self.range {
    Some((lo, hi)) =>
      if compare_bytes(hi, start) < 0 || compare_bytes(lo, end) >= 0 {
        cur.done = true
        return cur
      }
    None => ()
  }
  // 二分:第一个 last_key >= start 的 block
  let mut lo = 0
  let mut hi = cur.ies.length()
  while lo < hi {
    let mid = (lo + hi) / 2
    if compare_bytes(cur.ies[mid].key, start) < 0 {
      lo = mid + 1
    } else {
      hi = mid
    }
  }
  cur.block_i = lo
  if lo >= cur.ies.length() {
    cur.done = true
  }
  cur
}

///|
/// 解下一块,并把块内落在区间外的条目滤掉。
///
/// 块内线性过滤而不是块内二分:一个 block 只有 4 KiB / 一百多条,
/// 解出来再筛比再写一套「块内定位 + 向后遍历」简单,成本与块大小同阶。
fn SstCursor::load_next(self : SstCursor) -> Unit {
  self.block = []
  self.pos = 0
  if self.block_i >= self.ies.length() {
    self.done = true
    return
  }
  let ie = self.ies[self.block_i]
  self.block_i = self.block_i + 1
  let (off, size) = match decode_handle(ie.value) {
    Some(p) => p
    None => {
      self.failed = true
      self.done = true
      return
    }
  }
  if off + size > self.reader.data.length() {
    self.failed = true
    self.done = true
    return
  }
  let blk = match
    decode_block_ver(
      self.reader.data[off:off + size].to_owned(),
      self.reader.footer.version,
    ) {
    Some(b) => b
    None => {
      self.failed = true
      self.done = true
      return
    }
  }
  let mut entries_failed = false
  let es = blk.entries() catch {
    _ => {
      entries_failed = true
      []
    }
  }
  if entries_failed {
    self.failed = true
    self.done = true
    return
  }
  let kept : Array[Entry] = []
  for e in es {
    if compare_bytes(e.key, self.start) >= 0 &&
      compare_bytes(e.key, self.end) < 0 {
      kept.push(e)
    }
  }
  self.block = kept
  // 这一块的 last_key 已经 >= end,后面的块必然全部越界。
  // 这里置 done 而不是 return:当前块还得让调用方读完。
  if compare_bytes(ie.key, self.end) >= 0 {
    self.done = true
  }
}

///|
/// 看当前条目,不推进。
///
/// 块用完才解下一块 —— 这是「惰性」的全部内容。
fn SstCursor::peek(self : SstCursor) -> Entry? {
  let mut out : Entry? = None
  let mut go = true
  while go {
    if self.pos < self.block.length() {
      out = Some(self.block[self.pos])
      go = false
    } else if self.done {
      go = false
    } else {
      self.load_next()
    }
  }
  out
}

///|
fn SstCursor::advance(self : SstCursor) -> Unit {
  self.pos = self.pos + 1
}