///|
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}

///|
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}

///|
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}

///|
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}

///|
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}

///|
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 错误直接抛出错误。
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,
  }
}

///|
/// 只接受连续有效帧;遇到首个无效帧后不搜索更后面的提交标记。
pub fn inspect_wal(
  data : Bytes,
  max_frames? : Int = 100000,
) -> WalInspection raise SqliteError {
  if max_frames < 1 {
    raise Invalid("WAL 帧上限必须为正数")
  }
  if data.is_empty() {
    return {
      header: None,
      frames: [],
      commits: [],
      committed_frames: 0,
      database_pages: None,
      stop_reason: WalEndOfFile,
      stop_offset: 0,
      trailing_bytes: 0,
    }
  }
  let header = parse_wal_header(data)
  let frames : Array[WalFrame] = []
  let commits : Array[WalCommit] = []
  let mut offset = 32
  let mut checksum = (header.checksum1, header.checksum2)
  let mut committed_frames = 0
  let mut database_pages = None
  let mut stop_reason = WalEndOfFile
  let frame_size = 24 + header.page_size
  while offset < data.length() {
    if frames.length() == max_frames {
      stop_reason = WalFrameLimit
      break
    }
    if data.length() - offset < frame_size {
      stop_reason = WalTruncatedFrame
      break
    }
    if read_u32(data, offset + 8) != header.salt1 ||
      read_u32(data, offset + 12) != header.salt2 {
      stop_reason = WalSaltMismatch
      break
    }
    let page_number = read_u32(data, offset)
    let pages = read_u32(data, offset + 4)
    if page_number == 0U || page_number == 0xffffffffU || pages == 0xffffffffU {
      stop_reason = WalInvalidFrame
      break
    }
    let next = wal_checksum(data, offset, 8, header.checksum_order, checksum)
    let next = wal_checksum(
      data,
      offset + 24,
      header.page_size,
      header.checksum_order,
      next,
    )
    if next != (read_u32(data, offset + 16), read_u32(data, offset + 20)) {
      stop_reason = WalChecksumMismatch
      break
    }
    let index = frames.length() + 1
    frames.push({
      index,
      byte_offset: offset,
      page_number,
      database_pages: pages,
      checksum1: next.0,
      checksum2: next.1,
    })
    if pages != 0U {
      commits.push({ frame_index: index, database_pages: pages, })
      committed_frames = index
      database_pages = Some(pages)
    }
    checksum = next
    offset = offset + frame_size
  }
  {
    header: Some(header),
    frames,
    commits,
    committed_frames,
    database_pages,
    stop_reason,
    stop_offset: offset,
    trailing_bytes: data.length() - offset,
  }
}