///|
/// Binary reader for deserialization
pub struct BinaryReader {
  data : Bytes
  mut offset : Int
}

///|
/// Create a new binary reader
pub fn BinaryReader::new(data : Bytes) -> BinaryReader {
  { data, offset: 0 }
}

///|
/// Get current read position
pub fn BinaryReader::position(self : BinaryReader) -> Int {
  self.offset
}

///|
/// Ensure n bytes are available, abort with message if not.
fn BinaryReader::ensure(self : BinaryReader, n : Int) -> Unit {
  if self.offset + n > self.data.length() {
    abort(
      "BinaryReader: read out of bounds (offset=" +
      self.offset.to_string() +
      ", need=" +
      n.to_string() +
      ", len=" +
      self.data.length().to_string() +
      ")",
    )
  }
}

///|
/// Read a u32 value (little-endian)
pub fn BinaryReader::read_u32(self : BinaryReader) -> UInt {
  self.ensure(4)
  let value = read_u32_le(self.data, self.offset)
  self.offset = self.offset + 4
  value
}

///|
/// Read an i32 value (little-endian)
pub fn BinaryReader::read_i32(self : BinaryReader) -> Int {
  self.read_u32().reinterpret_as_int()
}

///|
/// Read a u64 value (little-endian)
pub fn BinaryReader::read_u64(self : BinaryReader) -> UInt64 {
  self.ensure(8)
  let value = read_u64_le(self.data, self.offset)
  self.offset = self.offset + 8
  value
}

///|
/// Read an i64 value (little-endian)
pub fn BinaryReader::read_i64(self : BinaryReader) -> Int64 {
  self.read_u64().reinterpret_as_int64()
}

///|
/// Read a f64 value
pub fn BinaryReader::read_f64(self : BinaryReader) -> Double {
  self.read_u64().reinterpret_as_double()
}

///|
/// Read a f32 value (little-endian)
pub fn BinaryReader::read_f32(self : BinaryReader) -> Float {
  Float::reinterpret_from_uint(self.read_u32())
}

///|
/// Read a single byte
pub fn BinaryReader::read_byte(self : BinaryReader) -> Byte {
  self.ensure(1)
  let b = self.data[self.offset]
  self.offset = self.offset + 1
  b
}

///|
/// Read the byte at the current position without advancing the cursor.
/// Used by version-aware decoders that need to disambiguate two on-disk
/// layouts that share a `version` tag but differ in their entry encoding.
pub fn BinaryReader::peek_byte(self : BinaryReader) -> Byte {
  self.ensure(1)
  self.data[self.offset]
}

///|
/// Read len bytes. Aborts if not enough data remaining.
pub fn BinaryReader::read_bytes(self : BinaryReader, len : Int) -> Bytes {
  self.ensure(len)
  let result : FixedArray[Byte] = FixedArray::make(len, b'\x00')
  for i in 0.. Bool {
  self.offset < self.data.length()
}

///|
/// Get remaining bytes count
pub fn BinaryReader::remaining(self : BinaryReader) -> Int {
  self.data.length() - self.offset
}

///|
/// Skip n bytes. Aborts if not enough data remaining.
pub fn BinaryReader::skip(self : BinaryReader, n : Int) -> Unit {
  self.ensure(n)
  self.offset = self.offset + n
}