///|
/// A growable byte sink with seekable overwrite support.
///
/// `ByteOutput` tracks the current write position separately from the highest
/// position written, so callers may seek backward to patch bytes and then
/// obtain only the initialized prefix with `to_bytes`.
pub struct ByteOutput {
  data : Array[Byte]
  mut position : Int
  mut written_length : Int
} derive(Debug, Eq, ToJson)

///|
/// Creates an empty output buffer.
///
/// `initial_size` preallocates zero-filled capacity but does not count as
/// written output. Raises `PdfError::InvalidReadLength` for negative sizes.
pub fn byte_output_new(initial_size? : Int = 0) -> ByteOutput raise PdfError {
  if initial_size < 0 {
    raise InvalidReadLength(initial_size)
  }
  let data : Array[Byte] = [ for _ in 0.. 0 ]
  { data, position: 0, written_length: 0, }
}

///|
/// Returns the current write position.
pub fn ByteOutput::position(self : ByteOutput) -> Int {
  self.position
}

///|
/// Returns the highest written byte position.
///
/// This is the length of the byte sequence returned by `to_bytes`.
pub fn ByteOutput::length(self : ByteOutput) -> Int {
  self.written_length
}

///|
/// Moves the write position.
///
/// Seeking beyond the current length is allowed; unwritten gaps are filled with
/// zero when data is later written. Negative positions raise
/// `PdfError::InvalidCursorPosition`.
pub fn ByteOutput::seek(
  self : ByteOutput,
  position : Int,
) -> Unit raise PdfError {
  if position < 0 {
    raise InvalidCursorPosition(position)
  }
  self.position = position
}

///|
fn ByteOutput::ensure_capacity(self : ByteOutput, length : Int) -> Unit {
  while self.data.length() < length {
    self.data.push(0)
  }
}

///|
/// Writes one byte at the current position and advances by one.
///
/// Raises `PdfError::InvalidByte` if `value` is outside the byte range
/// `0..=255`.
pub fn ByteOutput::write_byte(
  self : ByteOutput,
  value : Int,
) -> Unit raise PdfError {
  let byte = pdf_byte_of_int(value)
  self.ensure_capacity(self.position + 1)
  self.data[self.position] = byte
  self.position += 1
  if self.position > self.written_length {
    self.written_length = self.position
  }
}

///|
/// Seeks to `position`, writes one byte, and advances by one.
///
/// Raises the same errors as `seek` and `write_byte`.
pub fn ByteOutput::write_byte_at(
  self : ByteOutput,
  position : Int,
  value : Int,
) -> Unit raise PdfError {
  self.seek(position)
  self.write_byte(value)
}

///|
/// Writes a byte view at the current position and advances past it.
///
/// Raises `PdfError::InvalidReadLength` if the computed end position overflows.
pub fn ByteOutput::write_view(
  self : ByteOutput,
  bytes : BytesView,
) -> Unit raise PdfError {
  let end = self.position + bytes.length()
  if end < self.position {
    raise InvalidReadLength(bytes.length())
  }
  self.ensure_capacity(end)
  for byte in bytes {
    self.data[self.position] = byte
    self.position += 1
  }
  if self.position > self.written_length {
    self.written_length = self.position
  }
}

///|
/// Seeks to `position`, writes a byte view, and advances past it.
///
/// Raises the same errors as `seek` and `write_view`.
pub fn ByteOutput::write_view_at(
  self : ByteOutput,
  position : Int,
  bytes : BytesView,
) -> Unit raise PdfError {
  self.seek(position)
  self.write_view(bytes)
}

///|
/// Writes a slice of a byte view at the current position.
///
/// Raises `PdfError::InvalidCursorPosition` for a negative offset,
/// `PdfError::InvalidReadLength` for a negative or overflowing length, and
/// `PdfError::EndOfInput` if the requested slice exceeds the input view.
pub fn ByteOutput::write_view_slice(
  self : ByteOutput,
  bytes : BytesView,
  offset : Int,
  length : Int,
) -> Unit raise PdfError {
  if offset < 0 {
    raise InvalidCursorPosition(offset)
  }
  if length < 0 {
    raise InvalidReadLength(length)
  }
  let end = offset + length
  if end < offset {
    raise InvalidReadLength(length)
  }
  if end > bytes.length() {
    raise EndOfInput
  }
  self.write_view(bytes[offset:end])
}

///|
/// Writes `length` copies of `value` and advances past them.
///
/// The default value is zero. Raises `PdfError::InvalidReadLength` for invalid
/// lengths and `PdfError::InvalidByte` for values outside `0..=255`.
pub fn ByteOutput::write_repeated_byte(
  self : ByteOutput,
  length : Int,
  value? : Int = 0,
) -> Unit raise PdfError {
  if length < 0 {
    raise InvalidReadLength(length)
  }
  let byte = pdf_byte_of_int(value)
  let end = self.position + length
  if end < self.position {
    raise InvalidReadLength(length)
  }
  self.ensure_capacity(end)
  for index in self.position.. self.written_length {
    self.written_length = self.position
  }
}

///|
/// Encodes an ASCII string and writes the resulting bytes.
///
/// The underlying `@ascii.encode` function is used intentionally; callers
/// should pass bytes directly for non-ASCII PDF payloads.
pub fn ByteOutput::write_ascii(
  self : ByteOutput,
  text : String,
) -> Unit raise PdfError {
  self.write_view(@ascii.encode(text))
}

///|
/// Returns the written prefix as owned PDF bytes.
///
/// Bytes beyond `length` that were only preallocated are not included.
pub fn ByteOutput::to_bytes(self : ByteOutput) -> PdfBytes {
  Bytes::from_array(self.data[:self.written_length])
}