///|
/// WAL 累计 checksum 的字节序,由 header magic 决定;其他 WAL 数值仍按大端存储。
pub(all) enum WalChecksumOrder {
  LittleEndianChecksum
  BigEndianChecksum
} derive(Debug, Eq)

///|
pub extend WalChecksumOrder with @debug.Debug::{to_repr}

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

///|
/// 已校验的 32 字节 WAL 头;page_size 是实际页长,salt 与 checksum 用于逐帧验证,不提供在线锁或恢复协议。
pub(all) struct WalHeader {
  checksum_order : WalChecksumOrder
  version : UInt
  page_size : Int
  checkpoint_sequence : UInt
  salt1 : UInt
  salt2 : UInt
  checksum1 : UInt
  checksum2 : UInt
} derive(Debug, Eq)

///|
pub extend WalHeader with @debug.Debug::{to_repr}

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

///|
/// 内存 WAL 中的一帧;index 从 1 开始,byte_offset 指向帧头,database_pages 非零表示提交边界。
pub(all) struct WalFrame {
  index : Int
  byte_offset : Int
  page_number : UInt
  database_pages : UInt
  checksum1 : UInt
  checksum2 : UInt
} derive(Debug, Eq)

///|
pub extend WalFrame with @debug.Debug::{to_repr}

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

///|
/// 完整有效提交边界;frame_index 从 1 开始,database_pages 是该提交的逻辑页数。
pub(all) struct WalCommit {
  frame_index : Int
  database_pages : UInt
} derive(Debug, Eq)

///|
pub extend WalCommit with @debug.Debug::{to_repr}

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

///|
/// WAL 扫描停止原因;异常尾部与完整提交分开记录。WalFrameLimit 不能被有效前缀策略当作最新快照成功。
pub(all) enum WalStopReason {
  WalEndOfFile
  WalTruncatedFrame
  WalSaltMismatch
  WalChecksumMismatch
  WalInvalidFrame
  WalFrameLimit
} derive(Debug, Eq)

///|
pub extend WalStopReason with @debug.Debug::{to_repr}

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

///|
/// 内存 WAL 连续有效帧和提交报告;异常尾部可返回报告而非抛错,构造快照时再应用尾部策略。空 WAL 没有 header。
pub(all) struct WalInspection {
  header : WalHeader?
  frames : Array[WalFrame]
  commits : Array[WalCommit]
  committed_frames : Int
  database_pages : UInt?
  stop_reason : WalStopReason
  stop_offset : Int
  trailing_bytes : Int
} derive(Debug)

///|
pub extend WalInspection with @debug.Debug::{to_repr}

///|
fn wal_word(data : Bytes, offset : Int, order : WalChecksumOrder) -> UInt {
  let mut value = 0U
  for i = 0; i < 4; i = i + 1 {
    let index = match order {
      BigEndianChecksum => offset + i
      LittleEndianChecksum => offset + 3 - i
    }
    value = (value << 8) | data[index].to_uint()
  }
  value
}

///|
// 累计 checksum 使用模 2^32 无符号加法,存储字段始终为大端。
fn wal_checksum(
  data : Bytes,
  offset : Int,
  count : Int,
  order : WalChecksumOrder,
  initial : (UInt, UInt),
) -> (UInt, UInt) {
  let mut s0 = initial.0
  let mut s1 = initial.1
  for i = offset; i < offset + count; i = i + 8 {
    s0 = s0 + wal_word(data, i, order) + s1
    s1 = s1 + wal_word(data, i + 4, order) + s0
  }
  (s0, s1)
}

///|
/// 验证 WAL header;头部不完整、格式或 checksum 错误直接抛出错误。
/// 验证 32 字节 WAL header、magic、版本、页长及 checksum;不扫描帧。
pub fn parse_wal_header(data : Bytes) -> WalHeader raise SqliteError {
  require_range(data, 0, 32)
  let checksum_order = match read_u32(data, 0) {
    0x377f0682U => LittleEndianChecksum
    0x377f0683U => BigEndianChecksum
    _ => raise Invalid("WAL magic 不匹配")
  }
  let version = read_u32(data, 4)
  if version != 3007000U {
    raise Unsupported("未知 WAL 格式版本:\{version}")
  }
  let page_size = bounded_int(read_u32(data, 8).to_uint64(), "WAL page_size")
  if page_size < 512 || page_size > 65536 || (page_size & (page_size - 1)) != 0 {
    raise Invalid("无效 WAL page_size")
  }
  let checksum1 = read_u32(data, 24)
  let checksum2 = read_u32(data, 28)
  if wal_checksum(data, 0, 24, checksum_order, (0U, 0U)) !=
    (checksum1, checksum2) {
    raise Invalid("WAL header checksum 不匹配")
  }
  {
    checksum_order,
    version,
    page_size,
    checkpoint_sequence: read_u32(data, 12),
    salt1: read_u32(data, 16),
    salt2: read_u32(data, 20),
    checksum1,
    checksum2,
  }
}

///|
/// 只接受连续有效帧;遇到首个无效帧后不搜索更后面的提交标记。
/// 顺序验证完整内存 WAL,返回连续有效帧、提交边界和停止原因。默认最多 100000 帧;异常尾部与帧预算在报告中区分。
pub fn inspect_wal(
  data : Bytes,
  max_frames? : Int = 100000,
) -> WalInspection raise SqliteError {
  // 字节入口复用范围校验,旧报告的 Int 字段保留原有契约。
  let report = inspect_wal_source(BytesSource::new(data), max_frames~)
  {
    header: report.header,
    frames: report.frames.map(frame => {
      index: frame.index,
      byte_offset: frame.byte_offset.to_int(),
      page_number: frame.page_number,
      database_pages: frame.database_pages,
      checksum1: frame.checksum1,
      checksum2: frame.checksum2,
    }),
    commits: report.commits.map(commit => {
      frame_index: commit.frame_index,
      database_pages: commit.database_pages,
    }),
    committed_frames: report.committed_frames,
    database_pages: report.database_pages,
    stop_reason: report.stop_reason,
    stop_offset: report.stop_offset.to_int(),
    trailing_bytes: report.trailing_bytes.to_int(),
  }
}