///|
/// ByteWriter: an append-only byte buffer with amortized O(1) pushes.
/// `Array[Byte].push` re-allocates as the array grows; indexing into a
/// preallocated FixedArray-backed buffer makes snapshot serialization
/// substantially faster (17 MB image: 2.4 s → sub-second).
pub struct ByteWriter {
  priv mut buf : FixedArray[Byte]
  priv mut len : Int32
}

///|
const GROW_SHIFT : Int32 = 1

///|
pub fn ByteWriter::new(capacity~ : Int32 = 256) -> ByteWriter {
  {
    buf: FixedArray::make(if capacity < 16 { 16 } else { capacity }, b'\x00'),
    len: 0,
  }
}

///|
fn ByteWriter::grow(self : ByteWriter, needed : Int32) -> Unit {
  let old_cap = self.buf.length()
  if needed <= old_cap {
    return
  }
  let mut new_cap = old_cap
  while new_cap < needed {
    new_cap = new_cap + (new_cap >> GROW_SHIFT) // 1.5x-ish growth
  }
  let next = FixedArray::make(new_cap, b'\x00')
  self.buf.blit_to(next, len=self.len, src_offset=0, dst_offset=0)
  self.buf = next
}

///|
pub fn ByteWriter::push(self : ByteWriter, b : Byte) -> Unit {
  self.grow(self.len + 1)
  self.buf[self.len] = b
  self.len += 1
}

///|
pub fn ByteWriter::push_bytes(self : ByteWriter, data : Bytes) -> Unit {
  let n = data.length()
  self.grow(self.len + n)
  self.buf.blit_from_bytes(self.len, data, 0, n)
  self.len += n
}

///|
/// Append another writer's contents.
pub fn ByteWriter::append(self : ByteWriter, other : ByteWriter) -> Unit {
  let n = other.len
  self.grow(self.len + n)
  other.buf.blit_to(self.buf, len=n, src_offset=0, dst_offset=self.len)
  self.len += n
}

///|
pub fn ByteWriter::length(self : ByteWriter) -> Int32 {
  self.len
}

///|
/// Snapshot the written region as an immutable Bytes.
pub fn ByteWriter::to_bytes(self : ByteWriter) -> Bytes {
  self.buf.unsafe_reinterpret_as_bytes().clamped_view(end=self.len).to_owned()
}

///|
/// Varint (LEB128), string, double and u32 writers matching codec.mbt.
pub fn ByteWriter::write_varint(self : ByteWriter, value : Int32) -> Unit {
  // Negative values encode as their unsigned 32-bit bit pattern, exactly
  // like codec.mbt's write_varint (the two must stay byte-compatible).
  let mut v = value.reinterpret_as_uint()
  while v >= 0x80 {
    self.push(((v & 0x7F) | 0x80).reinterpret_as_int().to_byte())
    v = v >> 7
  }
  self.push(v.reinterpret_as_int().to_byte())
}

///|
pub fn ByteWriter::write_string(self : ByteWriter, s : String) -> Unit {
  let bytes = @utf8.encode(s)
  self.write_varint(bytes.length())
  self.push_bytes(bytes)
}

///|
pub fn ByteWriter::write_double(self : ByteWriter, value : Double) -> Unit {
  let bits = value.reinterpret_as_uint64()
  for i in 0..<8 {
    self.push(((bits >> (i * 8)) & 0xFFUL).to_int().to_byte())
  }
}

///|
pub fn ByteWriter::write_u32_le(self : ByteWriter, value : UInt32) -> Unit {
  for i in 0..<4 {
    self.push(((value >> (i * 8)) & 0xFFU).to_byte())
  }
}