///|
/// Produces one framed section per call. The caller owns the output sink.
/// Errors do not advance the writer; successfully emitted sections are immutable.
pub struct Writer {
  limits : Limits
  header : Header
  mut position : Int
  mut count : Int
  mut started : Bool
}

///|
pub fn Writer::new(
  header : Header,
  limits? : Limits = Limits::default(),
) -> Writer {
  { limits, header, position: 0, count: 0, started: false, }
}

///|
pub fn Writer::start(self : Writer) -> Bytes raise CarError {
  if self.started {
    raise InvalidState("writer already started")
  }
  let bytes = self.header.encode(limits=self.limits)
  budget("archive", bytes.length(), self.limits.max_archive)
  self.position = bytes.length()
  self.started = true
  bytes
}

///|
pub fn Writer::write(
  self : Writer,
  block : Block,
) -> (Bytes, Location) raise CarError {
  if !self.started {
    raise InvalidState("writer must start before blocks")
  }
  if self.count >= self.limits.max_blocks {
    raise LimitExceeded(
      "blocks",
      self.limits.max_blocks.to_uint64(),
      self.count.to_uint64() + 1UL,
    )
  }
  let bytes = block.encode(limits=self.limits)
  if bytes.length() > self.limits.max_archive - self.position {
    raise LimitExceeded(
      "archive",
      self.limits.max_archive.to_uint64(),
      self.position.to_uint64() + bytes.length().to_uint64(),
    )
  }
  let location = Location::{
    section_offset: self.position,
    data_offset: self.position + bytes.length() - block.data.length(),
    data_length: block.data.length(),
    section_length: bytes.length(),
  }
  self.position += bytes.length()
  self.count += 1
  (bytes, location)
}

///|
pub fn Writer::position(self : Writer) -> Int {
  self.position
}

///|
/// Write blocks in caller order. Equal roots and block order yield equal bytes.
pub fn encode(
  header : Header,
  blocks : Array[Block],
  limits? : Limits = Limits::default(),
) -> Bytes raise CarError {
  let writer = Writer::new(header, limits~)
  let buffer = @buffer.Buffer()
  buffer.write_bytes(writer.start())
  for block in blocks {
    buffer.write_bytes(writer.write(block).0)
  }
  buffer.to_bytes()
}