///|
/// `_delta_log` 目录路径
pub fn log_dir(root : String) -> String {
  root + "/_delta_log"
}

///|
/// 版本号 → 日志文件名(20 位零填充,Delta 规范)
pub fn version_file_name(version : Int32) -> String {
  let s = version.to_string()
  let mut pad = ""
  let mut i = 0
  while i < 20 - s.length() {
    pad = pad + "0"
    i = i + 1
  }
  pad + s + ".json"
}

///|
/// 从日志文件名解析版本号。严格按 Delta 规范:恰好 20 位数字 + ".json",
/// 其余文件名返回 None(调用方决定如何处置)。
fn parse_log_name(name : String) -> Int32? {
  if name.char_length() != 25 || !name.has_suffix(".json") {
    return None
  }
  let mut v = 0
  let mut i = 0
  for c in name {
    if i < 20 {
      let d = c.to_int() - 48
      if d < 0 || d > 9 {
        return None
      }
      v = v * 10 + d
    }
    i = i + 1
  }
  Some(v)
}

///|
/// 读取文本文件
pub fn read_text(path : String) -> String raise DeltaError {
  @fs.read_file_to_string(path) catch {
    @fs.IOError(msg) => raise DeltaError::IoError(msg)
  }
}

///|
/// 写入文本文件
pub fn write_text(path : String, content : String) -> Unit raise DeltaError {
  @fs.write_string_to_file(path, content) catch {
    @fs.IOError(msg) => raise DeltaError::IoError(msg)
  }
}

///|
/// 写入文本文件(put-if-absent 语义):目标已存在时直接失败。
/// 这是 commit 原子性的进程内保证——MoonBit fs 层没有 O_EXCL 原语,
/// 并发写方之间的真原子性由调用方自行仲裁(当前内核为单进程模型)。
pub fn write_text_new(path : String, content : String) -> Unit raise DeltaError {
  if @fs.path_exists(path) {
    raise DeltaError::IoError(
      "refusing to overwrite existing file '\{path}' (commit must be put-if-absent)",
    )
  }
  write_text(path, content)
}

///|
/// 列出所有日志版本(升序、去重)。
/// `.json` 文件名必须严格符合 20 位零填充版本号,其余视为表目录损坏并报错。
pub fn list_versions(root : String) -> Array[Int32] raise DeltaError {
  let dir = log_dir(root)
  if !@fs.path_exists(dir) {
    raise DeltaError::NotFound("no _delta_log directory under '\{root}'")
  }
  let names = @fs.read_dir(dir) catch {
    @fs.IOError(msg) => raise DeltaError::IoError(msg)
  }
  let out : Array[Int32] = []
  for name in names {
    if name.has_suffix(".json") && !name.has_prefix(".") {
      match parse_log_name(name) {
        Some(v) =>
          if !out.contains(v) {
            out.push(v)
          }
        None =>
          raise DeltaError::ParseError(
            "invalid _delta_log file name '\{name}' under '\{root}' (expected 20-digit zero-padded '.json')",
          )
      }
    }
  }
  out.sort()
  out
}

///|
/// 读取某个版本的日志 actions
pub fn read_actions(
  root : String,
  version : Int32,
) -> Array[Action] raise DeltaError {
  let path = log_dir(root) + "/" + version_file_name(version)
  if !@fs.path_exists(path) {
    raise DeltaError::NotFound(
      "log version \{version} is missing (log gap or truncated log; this kernel replays the full JSON log and does not fall back to checkpoint state)",
    )
  }
  let text = read_text(path).replace_all(old="\r", new="")
  let out : Array[Action] = []
  for line in text.split("\n") {
    if line.char_length() == 0 {
      continue
    }
    out.push(parse_action(line, version))
  }
  out
}

///|
/// 读取 `_last_checkpoint`(不存在返回 None)
pub fn checkpoint_info(root : String) -> CheckpointInfo? raise DeltaError {
  let path = log_dir(root) + "/_last_checkpoint"
  if !@fs.path_exists(path) {
    return None
  }
  let text = read_text(path)
  let j = @json.parse(text) catch { _ => return None }
  let version = match json_field_int(j, "version") {
    Some(v) => v
    None => return None
  }
  let size = json_field_int(j, "size")
  let parts = json_field_int(j, "parts")
  Some({ version, size, parts, })
}

///|
/// 是否存在 checkpoint 文件(`.checkpoint.parquet`)
pub fn has_checkpoint_file(root : String) -> Bool {
  let dir = log_dir(root)
  if !@fs.path_exists(dir) {
    return false
  }
  let names = @fs.read_dir(dir) catch { _ => return false }
  for name in names {
    if name.has_suffix(".checkpoint.parquet") {
      return true
    }
  }
  false
}