// LSB-first bit sink for the encode pipeline. DEFLATE packs bits LSB-first
// into bytes, but Huffman codes are written MSB-first.

///|
priv struct BitWriter {
  out : Array[Byte]
  mut bitbuf : Int32
  mut bit_count : Int32 // bits currently held in `bitbuf`, not yet flushed to a byte
  mut direct_output : FixedArray[Byte]?
  growable : Bool
  mut direct_pos : Int32
  mut overflowed : Bool
}

///|
fn BitWriter::BitWriter() -> BitWriter {
  {
    out: [],
    bitbuf: 0,
    bit_count: 0,
    direct_output: None,
    direct_pos: 0,
    growable: false,
    overflowed: false,
  }
}

///|
/// Create a bit writer that emits directly into `output`. Writes past its end
/// are recorded so the caller can report capacity exhaustion after encoding.
fn BitWriter::into(output : FixedArray[Byte]) -> BitWriter {
  {
    out: [],
    bitbuf: 0,
    bit_count: 0,
    direct_output: Some(output),
    direct_pos: 0,
    growable: false,
    overflowed: false,
  }
}

///|
/// Reusable, geometrically growing output for complete in-memory streams.
/// Unlike the caller-owned fixed buffer this can expand without recompressing.
fn BitWriter::growing(output : FixedArray[Byte]) -> BitWriter {
  { ..BitWriter::into(output), growable: true, }
}

///|
/// Keep capacity arithmetic separate so Int-boundary growth can be checked
/// without allocating gigabyte-sized buffers.
fn bitwriter_growth_capacity(current : Int32, required : Int32) -> Int32 {
  let doubled = if current <= 0x3fff_ffff { current * 2 } else { required }
  required.max(doubled).max(64)
}

///|
fn BitWriter::grow(
  self : BitWriter,
  output : FixedArray[Byte],
  required : Int32,
) -> FixedArray[Byte] {
  let grown = FixedArray::make_and_blit(
    output,
    allocate_len=bitwriter_growth_capacity(output.length(), required),
    init=b'\x00',
    len=self.direct_pos,
  )
  self.direct_output = Some(grown)
  grown
}

///|
/// Emit one whole byte to the configured destination. The normal writer grows
/// `out`; a direct writer keeps counting after capacity exhaustion so all block
/// planning code can remain allocation-free and branch-free at call sites.
#inline
fn BitWriter::write_byte(self : BitWriter, byte : Byte) -> Unit {
  if self.direct_output is Some(output) {
    if self.direct_pos < output.length() {
      output[self.direct_pos] = byte
    } else if self.growable {
      let grown = self.grow(output, self.direct_pos + 1)
      grown[self.direct_pos] = byte
    } else {
      self.overflowed = true
    }
    self.direct_pos = self.direct_pos + 1
  } else {
    self.out.push(byte)
  }
}

///|
/// Write a byte-aligned payload. Preserve the direct writer's overflow count
/// even if a previous header or payload already exhausted the output buffer.
fn BitWriter::write_bytes(
  self : BitWriter,
  input : BytesView,
  offset : Int32,
  length : Int32,
) -> Unit {
  if self.direct_output is Some(output) {
    let output = if self.growable && length > output.length() - self.direct_pos {
      self.grow(output, self.direct_pos + length)
    } else {
      output
    }
    let count = length.min((output.length() - self.direct_pos).max(0))
    if count > 0 {
      output.blit_from_bytes(
        self.direct_pos,
        input.data(),
        input.start_offset() + offset,
        count,
      )
    }
    self.direct_pos = self.direct_pos + length
    if count < length {
      self.overflowed = true
    }
  } else {
    append_payload(self.out, input[offset:offset + length])
  }
}

///|
/// Append a byte-aligned payload to the growing array sink. Core has no bulk
/// append of bytes to an array of bytes: stage them in a fixed array, which
/// appends as one block.
#cfg(not(target="js"))
fn append_payload(out : Array[Byte], payload : BytesView) -> Unit {
  let staged = FixedArray::make(payload.length(), b'\x00')
  staged.blit_from_bytesview(0, payload)
  out.append(staged)
}

///|
/// On js the staged block gains little (the array sink itself is the cost:
/// 5 to 7 % at level 0), and once it has run, V8 runs the decoders of the
/// same process 9 to 17 % slower (measured with node 25; they share no code
/// with this function). Keep the byte iterator there.
#cfg(target="js")
fn append_payload(out : Array[Byte], payload : BytesView) -> Unit {
  out.push_iter(payload.iter())
}

///|
/// Write the first `length` bytes of `bytes`, exactly as `length` calls of
/// `write_byte` would: a direct writer keeps counting past its capacity and
/// records the overflow. Only the 64-bit token writer hands bytes over in bulk.
#cfg(any(target="native", target="wasm"))
fn BitWriter::write_array(
  self : BitWriter,
  bytes : FixedArray[Byte],
  length : Int32,
) -> Unit {
  if self.direct_output is Some(output) {
    let output = if self.growable && length > output.length() - self.direct_pos {
      self.grow(output, self.direct_pos + length)
    } else {
      output
    }
    let count = length.min((output.length() - self.direct_pos).max(0))
    if count > 0 {
      bytes.blit_to(output, len=count, dst_offset=self.direct_pos)
    }
    self.direct_pos = self.direct_pos + length
    if count < length {
      self.overflowed = true
    }
  } else {
    self.out.append(bytes[:length])
  }
}

///|
fn BitWriter::write_bit(self : BitWriter, bit : Int32) -> Unit {
  self.bitbuf = self.bitbuf | (bit << self.bit_count)
  self.bit_count = self.bit_count + 1
  if self.bit_count == 8 {
    self.write_byte((self.bitbuf & 0xFF).to_byte())
    self.bitbuf = 0
    self.bit_count = 0
  }
}

///|
/// Write the low `n` bits of `value`, LSB-first. Batched: the bits are shifted
/// into the accumulator in one step and complete bytes flushed whole, instead
/// of looping one bit at a time.
fn BitWriter::write_bits(self : BitWriter, value : Int32, n : Int32) -> Unit {
  self.bitbuf = self.bitbuf | ((value & ((1 << n) - 1)) << self.bit_count)
  self.bit_count = self.bit_count + n
  while self.bit_count >= 8 {
    self.write_byte((self.bitbuf & 0xFF).to_byte())
    self.bitbuf = self.bitbuf >> 8
    self.bit_count = self.bit_count - 8
  }
}

///|
/// Write an already-bit-reversed Huffman code, LSB-first.
fn BitWriter::write_rcode(self : BitWriter, code : UInt32, n : Int32) -> Unit {
  self.write_bits(code.reinterpret_as_int(), n)
}

///|
/// Pad any sub-byte remainder in `bitbuf` out to a whole byte (zero-filling the
/// high bits) and emit it. Required before a byte-aligned stored block and at
/// the very end of a stream
fn BitWriter::flush(self : BitWriter) -> Unit {
  if self.bit_count > 0 {
    self.write_byte((self.bitbuf & 0xFF).to_byte())
    self.bitbuf = 0
    self.bit_count = 0
  }
}