///|
/// A bounded arena producing one standard Cap'n Proto stream segment.
/// Handles share the arena; scalar writes do not copy the message.
pub struct Builder {
  priv arena : Array[Byte]
  priv max_words : Int
}

///|
pub struct StructBuilder {
  priv owner : Builder
  priv data_start : Int
  priv data_words : Int
  priv pointer_count : Int
}

///|
pub struct ListBuilder {
  priv owner : Builder
  priv start : Int
  priv count : Int
  priv size_code : Int
  priv data_words : Int
  priv pointer_count : Int
}

///|
/// The default allocation limit is 64 MiB, including the root pointer.
pub fn Builder::new(max_words? : Int = 8_388_608) -> Builder raise CapnpError {
  if max_words < 1 || max_words > 268_435_454 {
    raise Limit("builder max_words must be between 1 and 268435454")
  }
  { arena: Array::make(8, 0), max_words, }
}

///|
fn b_alloc(owner : Builder, words : Int) -> Int raise CapnpError {
  let used = owner.arena.length() / 8
  if words < 0 || words > owner.max_words - used {
    raise Limit("single-segment builder allocation limit exceeded")
  }
  let start = owner.arena.length()
  for _ in 0..<(words * 8) {
    owner.arena.push(0)
  }
  start
}

///|
fn b_write(owner : Builder, offset : Int, value : UInt64, width : Int) -> Unit {
  for i in 0..> (i * 8)).to_byte()
  }
}

///|
fn b_struct_size(
  data_words : Int,
  pointer_count : Int,
) -> Unit raise CapnpError {
  if data_words < 0 ||
    data_words > 65535 ||
    pointer_count < 0 ||
    pointer_count > 65535 {
    raise Bounds(
      "struct data and pointer counts must fit unsigned 16-bit words",
    )
  }
}

///|
fn b_init_struct(
  owner : Builder,
  slot : Int,
  data_words : Int,
  pointer_count : Int,
) -> StructBuilder raise CapnpError {
  b_struct_size(data_words, pointer_count)
  let start = b_alloc(owner, data_words + pointer_count)
  let offset = if data_words + pointer_count == 0 {
    0xfffffffcUL
  } else {
    ((start - slot) / 8 - 1).to_uint64() << 2
  }
  b_write(
    owner,
    slot,
    offset | (data_words.to_uint64() << 32) | (pointer_count.to_uint64() << 48),
    8,
  )
  { owner, data_start: start, data_words, pointer_count, }
}

///|
/// Allocate a new root. Reinitializing replaces the pointer; old arena space
/// remains allocated and is still charged to the allocation limit.
pub fn Builder::init_root(
  self : Builder,
  data_words : Int,
  pointer_count : Int,
) -> StructBuilder raise CapnpError {
  b_init_struct(self, 0, data_words, pointer_count)
}

///|
/// Return a snapshot with the standard single-segment stream framing.
pub fn Builder::finish(self : Builder) -> Bytes {
  let words = (self.arena.length() / 8).to_uint64()
  Bytes::makei(self.arena.length() + 8, fn(i) {
    if i < 4 {
      0
    } else if i < 8 {
      (words >> ((i - 4) * 8)).to_byte()
    } else {
      self.arena[i - 8]
    }
  })
}

///|
pub fn Builder::allocated_words(self : Builder) -> Int {
  self.arena.length() / 8
}

///|
fn b_data(
  handle : StructBuilder,
  offset : Int,
  width : Int,
) -> Int raise CapnpError {
  let size = handle.data_words * 8
  if offset < 0 || width < 0 || offset > size || width > size - offset {
    raise Bounds("field extends beyond struct data section")
  }
  handle.data_start + offset
}

///|
fn b_pointer(handle : StructBuilder, slot : Int) -> Int raise CapnpError {
  if slot < 0 || slot >= handle.pointer_count {
    raise Bounds("pointer slot outside struct pointer section")
  }
  handle.data_start + handle.data_words * 8 + slot * 8
}

///|
pub fn StructBuilder::clear_pointer(
  self : StructBuilder,
  slot : Int,
) -> Unit raise CapnpError {
  b_write(self.owner, b_pointer(self, slot), 0, 8)
}

///|
pub fn StructBuilder::clear_data(
  self : StructBuilder,
  byte_offset : Int,
  byte_length : Int,
) -> Unit raise CapnpError {
  let start = b_data(self, byte_offset, byte_length)
  for i in 0.. StructBuilder raise CapnpError {
  b_init_struct(self.owner, b_pointer(self, slot), data_words, pointer_count)
}

///|
fn b_count(length : Int) -> Unit raise CapnpError {
  if length < 0 || length > 536_870_911 {
    raise Bounds("list element count must fit unsigned 29 bits")
  }
}

///|
fn b_init_list(
  owner : Builder,
  slot : Int,
  element_size : Int,
  length : Int,
) -> ListBuilder raise CapnpError {
  b_count(length)
  let words = match element_size {
    0 => 0
    1 => (length + 63) / 64
    2 => (length + 7) / 8
    3 => (length + 3) / 4
    4 => (length + 1) / 2
    5 | 6 => length
    _ =>
      raise TypeMismatch(
        "list element size must be 0..6; use init_struct_list for composite lists",
      )
  }
  let start = b_alloc(owner, words)
  let offset = ((start - slot) / 8 - 1).to_uint64()
  b_write(
    owner,
    slot,
    1UL |
    (offset << 2) |
    (element_size.to_uint64() << 32) |
    (length.to_uint64() << 35),
    8,
  )
  {
    owner,
    start,
    count: length,
    size_code: element_size,
    data_words: 0,
    pointer_count: 0,
  }
}

///|
fn b_init_struct_list(
  owner : Builder,
  slot : Int,
  length : Int,
  data_words : Int,
  pointer_count : Int,
) -> ListBuilder raise CapnpError {
  b_count(length)
  b_struct_size(data_words, pointer_count)
  let stride = data_words + pointer_count
  if stride > 0 &&
    length > (owner.max_words - owner.arena.length() / 8 - 1) / stride {
    raise Limit("composite list exceeds single-segment allocation limit")
  }
  let words = length * stride
  if words > 536_870_911 {
    raise Limit("composite list word count exceeds wire limit")
  }
  let tag = b_alloc(owner, words + 1)
  let offset = ((tag - slot) / 8 - 1).to_uint64()
  b_write(
    owner,
    slot,
    1UL | (offset << 2) | (7UL << 32) | (words.to_uint64() << 35),
    8,
  )
  b_write(
    owner,
    tag,
    (length.to_uint64() << 2) |
    (data_words.to_uint64() << 32) |
    (pointer_count.to_uint64() << 48),
    8,
  )
  {
    owner,
    start: tag + 8,
    count: length,
    size_code: 7,
    data_words,
    pointer_count,
  }
}

///|
/// Element size is the standard wire code: 0 void, 1 bit, 2 byte, 3 uint16,
/// 4 uint32, 5 uint64, 6 pointer. Struct lists use init_struct_list.
pub fn StructBuilder::init_list(
  self : StructBuilder,
  slot : Int,
  element_size : Int,
  length : Int,
) -> ListBuilder raise CapnpError {
  b_init_list(self.owner, b_pointer(self, slot), element_size, length)
}

///|
pub fn StructBuilder::init_struct_list(
  self : StructBuilder,
  slot : Int,
  length : Int,
  data_words : Int,
  pointer_count : Int,
) -> ListBuilder raise CapnpError {
  b_init_struct_list(
    self.owner,
    b_pointer(self, slot),
    length,
    data_words,
    pointer_count,
  )
}

///|
fn b_blob(
  owner : Builder,
  slot : Int,
  bytes : Bytes,
  text : Bool,
) -> Unit raise CapnpError {
  let max_length = if text { 536_870_910 } else { 536_870_911 }
  if bytes.length() > max_length {
    raise Limit("text or data exceeds list count limit")
  }
  let count = bytes.length() + (if text { 1 } else { 0 })
  let list = b_init_list(owner, slot, 2, count)
  for i in 0.. Unit raise CapnpError {
  b_blob(self.owner, b_pointer(self, slot), @utf8.encode(value), true)
}

///|
pub fn StructBuilder::set_data(
  self : StructBuilder,
  slot : Int,
  value : Bytes,
) -> Unit raise CapnpError {
  b_blob(self.owner, b_pointer(self, slot), value, false)
}