///|
priv struct FlateBitReader {
  bytes : BytesView
  mut byte_pos : Int
  mut bit_buf : UInt
  mut bit_count : Int
}

///|
fn FlateBitReader::new(bytes : BytesView) -> FlateBitReader {
  { bytes, byte_pos: 0, bit_buf: 0, bit_count: 0 }
}

///|
fn FlateBitReader::ensure_bits(
  self : FlateBitReader,
  count : Int,
) -> Unit raise PdfError {
  if count < 0 {
    raise InvalidFlateData
  }
  while self.bit_count < count {
    if self.byte_pos >= self.bytes.length() || self.byte_pos < 0 {
      raise InvalidFlateData
    }
    let byte = self.bytes[self.byte_pos].to_uint()
    self.bit_buf = self.bit_buf | (byte << self.bit_count)
    self.bit_count += 8
    self.byte_pos += 1
  }
}

///|
fn FlateBitReader::read_bits(
  self : FlateBitReader,
  count : Int,
) -> UInt raise PdfError {
  self.ensure_bits(count)
  let mask = pdf_flate_bit_mask(count)
  let value = self.bit_buf & mask
  self.bit_buf = self.bit_buf >> count
  self.bit_count -= count
  value
}

///|
fn FlateBitReader::peek_bits(self : FlateBitReader, count : Int) -> UInt? {
  if count < 0 {
    return None
  }
  let mut bit_buf = self.bit_buf
  let mut bit_count = self.bit_count
  let mut byte_pos = self.byte_pos
  while bit_count < count {
    if byte_pos >= self.bytes.length() || byte_pos < 0 {
      return None
    }
    let byte = self.bytes[byte_pos].to_uint()
    bit_buf = bit_buf | (byte << bit_count)
    bit_count += 8
    byte_pos += 1
  }
  Some(bit_buf & pdf_flate_bit_mask(count))
}

///|
fn FlateBitReader::drop_bits(
  self : FlateBitReader,
  count : Int,
) -> Unit raise PdfError {
  self.ensure_bits(count)
  self.bit_buf = self.bit_buf >> count
  self.bit_count -= count
}

///|
fn FlateBitReader::align_byte(self : FlateBitReader) -> Unit raise PdfError {
  let drop = self.bit_count % 8
  if drop != 0 {
    self.drop_bits(drop)
  }
}

///|
fn FlateBitReader::read_byte(self : FlateBitReader) -> Byte raise PdfError {
  self.read_bits(8).to_byte()
}

///|
let pdf_flate_bit_mask_table : Array[UInt] = [
  0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535,
]

///|
fn pdf_flate_bit_mask(count : Int) -> UInt {
  if count >= 0 && count < pdf_flate_bit_mask_table.length() {
    pdf_flate_bit_mask_table[count]
  } else {
    let mut mask : UInt = 0
    for _ in 0.. UInt {
  let mut current = value
  let mut result : UInt = 0
  for _ in 0..> 1
  }
  result
}

///|
let pdf_flate_length_base : Array[Int] = [
  3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83,
  99, 115, 131, 163, 195, 227, 258,
]

///|
let pdf_flate_length_extra : Array[Int] = [
  0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5,
  5, 0,
]

///|
let pdf_flate_distance_base : Array[Int] = [
  1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769,
  1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577,
]

///|
let pdf_flate_distance_extra : Array[Int] = [
  0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
  12, 12, 13, 13,
]

///|
priv struct FlateHuffmanTable {
  max_bits : Int
  sym : Array[Int]
  len : Array[Int]
}

///|
priv struct FlateHuffmanTreeNode {
  weight : Int
  symbol : Int
  left : Int
  right : Int
  mut active : Bool
}

///|
fn pdf_flate_pick_huffman_node(nodes : Array[FlateHuffmanTreeNode]) -> Int {
  let mut picked = -1
  for index, node in nodes {
    if node.active &&
      (
        picked < 0 ||
        node.weight < nodes[picked].weight ||
        (
          node.weight == nodes[picked].weight &&
          node.symbol < nodes[picked].symbol
        )
      ) {
      picked = index
    }
  }
  picked
}

///|
fn pdf_flate_huffman_code_lengths(
  frequencies : ArrayView[Int],
  max_bits : Int,
) -> Array[Int] raise PdfError {
  let lengths : Array[Int] = Array::make(frequencies.length(), 0)
  let nodes : Array[FlateHuffmanTreeNode] = []
  for symbol, frequency in frequencies {
    if frequency < 0 {
      raise InvalidFlateData
    }
    if frequency > 0 {
      nodes.push({
        weight: frequency,
        symbol,
        left: -1,
        right: -1,
        active: true,
      })
    }
  }
  if nodes.length() == 0 {
    raise InvalidFlateData
  }
  if nodes.length() == 1 {
    lengths[nodes[0].symbol] = 1
    return lengths
  }
  let mut active = nodes.length()
  while active > 1 {
    let first = pdf_flate_pick_huffman_node(nodes)
    nodes[first].active = false
    let second = pdf_flate_pick_huffman_node(nodes)
    nodes[second].active = false
    nodes.push({
      weight: nodes[first].weight + nodes[second].weight,
      symbol: if nodes[first].symbol < nodes[second].symbol {
        nodes[first].symbol
      } else {
        nodes[second].symbol
      },
      left: first,
      right: second,
      active: true,
    })
    active -= 1
  }
  let root = pdf_flate_pick_huffman_node(nodes)
  let stack : Array[(Int, Int)] = [(root, 0)]
  while stack.length() > 0 {
    let (node_index, depth) = stack.pop().unwrap()
    let node = nodes[node_index]
    if node.left < 0 && node.right < 0 {
      guard depth <= max_bits else { raise InvalidFlateData }
      lengths[node.symbol] = if depth == 0 { 1 } else { depth }
    } else {
      let child_depth = depth + 1
      guard child_depth <= max_bits else { raise InvalidFlateData }
      if node.left >= 0 {
        stack.push((node.left, child_depth))
      }
      if node.right >= 0 {
        stack.push((node.right, child_depth))
      }
    }
  }
  lengths
}

///|
fn pdf_flate_canonical_codes(lengths : ArrayView[Int]) -> Array[Int] {
  let mut max_bits = 0
  for length in lengths {
    if length > max_bits {
      max_bits = length
    }
  }
  let counts : Array[Int] = Array::make(max_bits + 1, 0)
  for length in lengths {
    if length > 0 {
      counts[length] += 1
    }
  }
  let next_code : Array[Int] = Array::make(max_bits + 1, 0)
  let mut code = 0
  for bits in 1..<=max_bits {
    code = (code + counts[bits - 1]) << 1
    next_code[bits] = code
  }
  let codes : Array[Int] = Array::make(lengths.length(), 0)
  for symbol, length in lengths {
    if length > 0 {
      codes[symbol] = next_code[length]
      next_code[length] += 1
    }
  }
  codes
}

///|
fn pdf_flate_build_huffman(
  lengths : ArrayView[Int],
) -> FlateHuffmanTable raise PdfError {
  let mut max_bits = 0
  for length in lengths {
    if length < 0 || length > 15 {
      raise InvalidFlateData
    }
    if length > max_bits {
      max_bits = length
    }
  }
  if max_bits == 0 {
    raise InvalidFlateData
  }
  let counts : Array[Int] = Array::make(max_bits + 1, 0)
  for length in lengths {
    if length > 0 {
      counts[length] += 1
    }
  }
  let next_code : Array[Int] = Array::make(max_bits + 1, 0)
  let mut code = 0
  for bits in 1..<=max_bits {
    code = (code + counts[bits - 1]) << 1
    next_code[bits] = code
  }
  let size = 1 << max_bits
  let sym : Array[Int] = Array::make(size, -1)
  let len_table : Array[Int] = Array::make(size, 0)
  for symbol, length in lengths {
    if length == 0 {
      continue
    }
    let current = next_code[length]
    next_code[length] = current + 1
    let reversed = pdf_flate_reverse_bits(current.reinterpret_as_uint(), length).reinterpret_as_int()
    let fill = 1 << (max_bits - length)
    for i in 0.. Int raise PdfError {
  match reader.peek_bits(table.max_bits) {
    Some(bits) => {
      let index = bits.reinterpret_as_int()
      let length = table.len[index]
      if length > 0 {
        reader.drop_bits(length)
        return table.sym[index]
      }
    }
    None => ()
  }
  let mut code : UInt = 0
  for length in 1..<=table.max_bits {
    let bit = reader.read_bits(1)
    code = code | (bit << (length - 1))
    let index = code.reinterpret_as_int()
    if table.len[index] == length {
      return table.sym[index]
    }
  }
  raise InvalidFlateData
}

///|
fn pdf_flate_fixed_literal_lengths() -> Array[Int] {
  let lengths : Array[Int] = []
  for i in 0..<=287 {
    if i <= 143 {
      lengths.push(8)
    } else if i <= 255 {
      lengths.push(9)
    } else if i <= 279 {
      lengths.push(7)
    } else {
      lengths.push(8)
    }
  }
  lengths
}

///|
fn pdf_flate_fixed_distance_lengths() -> Array[Int] {
  let lengths : Array[Int] = []
  for _ in 0..<=31 {
    lengths.push(5)
  }
  lengths
}

///|
let pdf_flate_fixed_literal_table_value : FlateHuffmanTable = try! pdf_flate_build_huffman(
  pdf_flate_fixed_literal_lengths(),
)

///|
let pdf_flate_fixed_distance_table_value : FlateHuffmanTable = try! pdf_flate_build_huffman(
  pdf_flate_fixed_distance_lengths(),
)

///|
fn pdf_flate_fixed_literal_table() -> FlateHuffmanTable {
  pdf_flate_fixed_literal_table_value
}

///|
fn pdf_flate_fixed_distance_table() -> FlateHuffmanTable {
  pdf_flate_fixed_distance_table_value
}

///|
fn pdf_flate_check_dynamic_length_symbol(symbol : Int) -> Unit raise PdfError {
  if symbol < 0 || symbol > 18 {
    raise InvalidFlateData
  }
}

///|
fn pdf_flate_push_repeated_lengths(
  lengths : Array[Int],
  total : Int,
  repeat : Int,
  value : Int,
) -> Unit raise PdfError {
  for _ in 0..= total {
      raise InvalidFlateData
    }
    lengths.push(value)
  }
}

///|
fn pdf_flate_read_dynamic_tables(
  reader : FlateBitReader,
) -> (FlateHuffmanTable, FlateHuffmanTable) raise PdfError {
  let hlit = reader.read_bits(5).reinterpret_as_int() + 257
  let hdist = reader.read_bits(5).reinterpret_as_int() + 1
  let hclen = reader.read_bits(4).reinterpret_as_int() + 4
  let order : ReadOnlyArray[Int] = [
    16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
  ]
  let code_lengths : Array[Int] = Array::make(19, 0)
  for i in 0.. Unit raise PdfError {
  let mut done = false
  while !done {
    let symbol = pdf_flate_decode_symbol(reader, literal_table)
    if symbol < 256 {
      output.push(symbol.to_byte())
    } else if symbol == 256 {
      done = true
    } else {
      let length_index = symbol - 257
      if length_index < 0 || length_index >= pdf_flate_length_base.length() {
        raise InvalidFlateData
      }
      let extra_length = pdf_flate_length_extra[length_index]
      let length = pdf_flate_length_base[length_index] +
        reader.read_bits(extra_length).reinterpret_as_int()
      let distance_symbol = pdf_flate_decode_symbol(reader, distance_table)
      if distance_symbol < 0 ||
        distance_symbol >= pdf_flate_distance_base.length() {
        raise InvalidFlateData
      }
      let distance = pdf_flate_distance_base[distance_symbol] +
        reader
        .read_bits(pdf_flate_distance_extra[distance_symbol])
        .reinterpret_as_int()
      if distance <= 0 || distance > output.length() {
        raise InvalidFlateData
      }
      for _ in 0.. (PdfBytes, Int) raise PdfError {
  let reader = FlateBitReader::new(bytes)
  let output : Array[Byte] = []
  let mut finished = false
  while !finished {
    let final_block = reader.read_bits(1)
    let block_type = reader.read_bits(2)
    match block_type {
      0 => {
        reader.align_byte()
        let length = reader.read_bits(16).reinterpret_as_int()
        let nlength = reader.read_bits(16)
        if (length.reinterpret_as_uint() ^ nlength) != 0xFFFF {
          raise InvalidFlateData
        }
        for _ in 0.. {
        let literal = pdf_flate_fixed_literal_table()
        let distance = pdf_flate_fixed_distance_table()
        pdf_flate_decode_huffman_block(reader, literal, distance, output)
      }
      2 => {
        let (literal, distance) = pdf_flate_read_dynamic_tables(reader)
        pdf_flate_decode_huffman_block(reader, literal, distance, output)
      }
      _ => raise InvalidFlateData
    }
    finished = final_block == 1
  }
  (Bytes::from_array(output), reader.byte_pos)
}

///|
fn pdf_deflate_decode_raw(bytes : BytesView) -> PdfBytes raise PdfError {
  let (decoded, _) = pdf_deflate_decode_raw_prefix(bytes)
  decoded
}

///|
fn pdf_flate_adler32(data : BytesView) -> (Int, Int) {
  let mod_adler = 65521
  let mut s1 = 1
  let mut s2 = 0
  for byte in data {
    s1 = (s1 + byte.to_int()) % mod_adler
    s2 = (s2 + s1) % mod_adler
  }
  (s1, s2)
}

///|
fn pdf_flate_check_adler32(
  compressed : BytesView,
  decoded : BytesView,
) -> Unit raise PdfError {
  let offset = compressed.length() - 4
  let (s1, s2) = pdf_flate_adler32(decoded)
  if compressed[offset].to_int() != ((s2 >> 8) & 0xFF) ||
    compressed[offset + 1].to_int() != (s2 & 0xFF) ||
    compressed[offset + 2].to_int() != ((s1 >> 8) & 0xFF) ||
    compressed[offset + 3].to_int() != (s1 & 0xFF) {
    raise InvalidFlateData
  }
}

///|
fn pdf_flate_push_u16_le(output : Array[Byte], value : Int) -> Unit {
  output.push((value & 0xFF).to_byte())
  output.push(((value >> 8) & 0xFF).to_byte())
}

///|
fn pdf_flate_push_stored_block(
  output : Array[Byte],
  data : BytesView,
  offset : Int,
  length : Int,
  final_block : Bool,
) -> Unit {
  output.push(if final_block { 1 } else { 0 })
  pdf_flate_push_u16_le(output, length)
  pdf_flate_push_u16_le(output, 0xFFFF - length)
  for i in 0.. FlateBitWriter {
  { output, current_byte: 0, bit_count: 0 }
}

///|
fn FlateBitWriter::write_bits(
  self : FlateBitWriter,
  value : UInt,
  width : Int,
) -> Unit {
  for bit_index in 0..> bit_index) & 1) == 1 {
      self.current_byte = self.current_byte | (1 << self.bit_count)
    }
    self.bit_count += 1
    if self.bit_count == 8 {
      self.output.push(self.current_byte.to_byte())
      self.current_byte = 0
      self.bit_count = 0
    }
  }
}

///|
fn FlateBitWriter::finish(self : FlateBitWriter) -> Unit {
  if self.bit_count > 0 {
    self.output.push(self.current_byte.to_byte())
    self.current_byte = 0
    self.bit_count = 0
  }
}

///|
fn pdf_flate_fixed_literal_code(symbol : Int) -> (UInt, Int) {
  if symbol <= 143 {
    ((0x30 + symbol).reinterpret_as_uint(), 8)
  } else if symbol <= 255 {
    ((0x190 + symbol - 144).reinterpret_as_uint(), 9)
  } else if symbol <= 279 {
    ((symbol - 256).reinterpret_as_uint(), 7)
  } else {
    ((0xC0 + symbol - 280).reinterpret_as_uint(), 8)
  }
}

///|
fn pdf_flate_write_fixed_literal(writer : FlateBitWriter, symbol : Int) -> Unit {
  let (code, width) = pdf_flate_fixed_literal_code(symbol)
  writer.write_bits(pdf_flate_reverse_bits(code, width), width)
}

///|
fn pdf_flate_length_code(length : Int) -> (Int, Int, Int) raise PdfError {
  for index, base in pdf_flate_length_base {
    let extra_width = pdf_flate_length_extra[index]
    let max = base + ((1 << extra_width) - 1)
    if length >= base && length <= max {
      break (257 + index, length - base, extra_width)
    }
  } nobreak {
    raise InvalidFlateData
  }
}

///|
fn pdf_flate_distance_code(distance : Int) -> (Int, Int, Int) raise PdfError {
  for index, base in pdf_flate_distance_base {
    let extra_width = pdf_flate_distance_extra[index]
    let max = base + ((1 << extra_width) - 1)
    if distance >= base && distance <= max {
      break (index, distance - base, extra_width)
    }
  } nobreak {
    raise InvalidFlateData
  }
}

///|
fn pdf_flate_write_fixed_distance(
  writer : FlateBitWriter,
  distance : Int,
) -> Unit raise PdfError {
  let (symbol, extra, extra_width) = pdf_flate_distance_code(distance)
  writer.write_bits(pdf_flate_reverse_bits(symbol.reinterpret_as_uint(), 5), 5)
  writer.write_bits(extra.reinterpret_as_uint(), extra_width)
}

///|
fn pdf_flate_write_fixed_length_distance(
  writer : FlateBitWriter,
  length : Int,
  distance : Int,
) -> Unit raise PdfError {
  let (symbol, extra, extra_width) = pdf_flate_length_code(length)
  pdf_flate_write_fixed_literal(writer, symbol)
  writer.write_bits(extra.reinterpret_as_uint(), extra_width)
  pdf_flate_write_fixed_distance(writer, distance)
}

///|
fn pdf_flate_write_canonical_symbol(
  writer : FlateBitWriter,
  symbol : Int,
  codes : ArrayView[Int],
  lengths : ArrayView[Int],
) -> Unit raise PdfError {
  guard symbol >= 0 && symbol < lengths.length() && lengths[symbol] > 0 else {
    raise InvalidFlateData
  }
  let width = lengths[symbol]
  writer.write_bits(
    pdf_flate_reverse_bits(codes[symbol].reinterpret_as_uint(), width),
    width,
  )
}

///|
fn pdf_flate_push_fixed_literal_block(
  output : Array[Byte],
  data : BytesView,
  final_block : Bool,
) -> Unit {
  let writer = FlateBitWriter::new(output)
  writer.write_bits(if final_block { 1 } else { 0 }, 1)
  writer.write_bits(1, 2)
  for byte in data {
    pdf_flate_write_fixed_literal(writer, byte.to_int())
  }
  pdf_flate_write_fixed_literal(writer, 256)
  writer.finish()
}

///|
fn pdf_flate_repeated_previous_length(data : BytesView, position : Int) -> Int {
  if position <= 0 || position >= data.length() {
    0
  } else {
    let previous = data[position - 1]
    let mut length = 0
    while position + length < data.length() &&
          data[position + length] == previous &&
          length < 258 {
      length += 1
    }
    length
  }
}

///|
fn pdf_flate_push_fixed_repeat_block(
  output : Array[Byte],
  data : BytesView,
  final_block : Bool,
) -> Unit raise PdfError {
  let writer = FlateBitWriter::new(output)
  writer.write_bits(if final_block { 1 } else { 0 }, 1)
  writer.write_bits(1, 2)
  let mut index = 0
  while index < data.length() {
    let repeated = pdf_flate_repeated_previous_length(data, index)
    if repeated >= 3 {
      pdf_flate_write_fixed_length_distance(writer, repeated, 1)
      index += repeated
    } else {
      pdf_flate_write_fixed_literal(writer, data[index].to_int())
      index += 1
    }
  }
  pdf_flate_write_fixed_literal(writer, 256)
  writer.finish()
}

///|
fn pdf_flate_match_key(data : BytesView, position : Int) -> Int {
  (data[position].to_int() << 8) | data[position + 1].to_int()
}

///|
fn pdf_flate_remember_match_prefix(
  table : Array[Int],
  previous : Array[Int],
  data : BytesView,
  position : Int,
) -> Unit {
  if position + 1 < data.length() {
    let key = pdf_flate_match_key(data, position)
    previous[position] = table[key]
    table[key] = position
  }
}

///|
fn pdf_flate_previous_match_length(
  data : BytesView,
  source : Int,
  position : Int,
) -> Int {
  let mut length = 0
  while position + length < data.length() &&
        length < 258 &&
        data[source + length] == data[position + length] {
    length += 1
  }
  length
}

///|
fn pdf_flate_best_previous_match(
  data : BytesView,
  last_by_prefix : ArrayView[Int],
  previous_by_position : ArrayView[Int],
  position : Int,
  max_probes : Int,
) -> (Int, Int) {
  let mut match_length = 0
  let mut match_distance = 0
  if position + 2 < data.length() {
    let mut candidate = last_by_prefix[pdf_flate_match_key(data, position)]
    let mut probes = 0
    while candidate >= 0 && probes < max_probes {
      let distance = position - candidate
      if distance > 0 && distance <= 32768 {
        let length = pdf_flate_previous_match_length(data, candidate, position)
        if length > match_length {
          match_length = length
          match_distance = distance
          if length == 258 {
            break
          }
        }
      }
      candidate = previous_by_position[candidate]
      probes += 1
    }
  }
  (match_length, match_distance)
}

///|
priv enum PdfFlateToken {
  PdfFlateLiteral(Int)
  PdfFlateLengthDistance(Int, Int)
}

///|
fn pdf_flate_match_tokens(
  data : BytesView,
  max_probes : Int,
) -> Array[PdfFlateToken] {
  let tokens : Array[PdfFlateToken] = []
  let last_by_prefix : Array[Int] = Array::make(65536, -1)
  let previous_by_position : Array[Int] = Array::make(data.length(), -1)
  let mut index = 0
  while index < data.length() {
    let (match_length, match_distance) = pdf_flate_best_previous_match(
      data, last_by_prefix, previous_by_position, index, max_probes,
    )
    if match_length >= 3 {
      tokens.push(PdfFlateLengthDistance(match_length, match_distance))
      let next_index = index + match_length
      while index < next_index {
        pdf_flate_remember_match_prefix(
          last_by_prefix, previous_by_position, data, index,
        )
        index += 1
      }
    } else {
      tokens.push(PdfFlateLiteral(data[index].to_int()))
      pdf_flate_remember_match_prefix(
        last_by_prefix, previous_by_position, data, index,
      )
      index += 1
    }
  }
  tokens
}

///|
fn pdf_flate_push_fixed_match_block(
  output : Array[Byte],
  data : BytesView,
  final_block : Bool,
  max_probes : Int,
) -> Unit raise PdfError {
  let writer = FlateBitWriter::new(output)
  writer.write_bits(if final_block { 1 } else { 0 }, 1)
  writer.write_bits(1, 2)
  let last_by_prefix : Array[Int] = Array::make(65536, -1)
  let previous_by_position : Array[Int] = Array::make(data.length(), -1)
  let mut index = 0
  while index < data.length() {
    let (match_length, match_distance) = pdf_flate_best_previous_match(
      data, last_by_prefix, previous_by_position, index, max_probes,
    )
    if match_length >= 3 {
      pdf_flate_write_fixed_length_distance(
        writer, match_length, match_distance,
      )
      let next_index = index + match_length
      while index < next_index {
        pdf_flate_remember_match_prefix(
          last_by_prefix, previous_by_position, data, index,
        )
        index += 1
      }
    } else {
      pdf_flate_write_fixed_literal(writer, data[index].to_int())
      pdf_flate_remember_match_prefix(
        last_by_prefix, previous_by_position, data, index,
      )
      index += 1
    }
  }
  pdf_flate_write_fixed_literal(writer, 256)
  writer.finish()
}

///|
priv struct PdfFlateCodeLengthToken {
  symbol : Int
  extra : Int
  extra_width : Int
}

///|
fn pdf_flate_last_nonzero_length(lengths : ArrayView[Int]) -> Int {
  let mut index = lengths.length() - 1
  while index >= 0 {
    if lengths[index] != 0 {
      return index
    }
    index -= 1
  }
  -1
}

///|
fn pdf_flate_push_code_length_token(
  output : Array[PdfFlateCodeLengthToken],
  symbol : Int,
  extra? : Int = 0,
  extra_width? : Int = 0,
) -> Unit {
  output.push({ symbol, extra, extra_width })
}

///|
fn pdf_flate_code_length_tokens(
  lengths : ArrayView[Int],
) -> Array[PdfFlateCodeLengthToken] {
  let tokens : Array[PdfFlateCodeLengthToken] = []
  let mut index = 0
  while index < lengths.length() {
    let value = lengths[index]
    let mut run = 1
    while index + run < lengths.length() && lengths[index + run] == value {
      run += 1
    }
    if value == 0 {
      let mut remaining = run
      while remaining >= 11 {
        let count = if remaining > 138 { 138 } else { remaining }
        pdf_flate_push_code_length_token(
          tokens,
          18,
          extra=count - 11,
          extra_width=7,
        )
        remaining -= count
      }
      if remaining >= 3 {
        pdf_flate_push_code_length_token(
          tokens,
          17,
          extra=remaining - 3,
          extra_width=3,
        )
      } else {
        for _ in 0..= 3 {
        let count = if remaining > 6 { 6 } else { remaining }
        pdf_flate_push_code_length_token(
          tokens,
          16,
          extra=count - 3,
          extra_width=2,
        )
        remaining -= count
      }
      for _ in 0.. Array[Int] {
  let frequencies : Array[Int] = Array::make(19, 0)
  for token in tokens {
    frequencies[token.symbol] += 1
  }
  frequencies
}

///|
fn pdf_flate_code_length_order() -> Array[Int] {
  [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]
}

///|
fn pdf_flate_code_length_order_count(
  code_length_lengths : ArrayView[Int],
) -> Int {
  let order = pdf_flate_code_length_order()
  let mut count = order.length()
  while count > 4 && code_length_lengths[order[count - 1]] == 0 {
    count -= 1
  }
  count
}

///|
fn pdf_flate_count_dynamic_frequencies(
  tokens : ArrayView[PdfFlateToken],
) -> (Array[Int], Array[Int]) raise PdfError {
  let literal_frequencies : Array[Int] = Array::make(286, 0)
  let distance_frequencies : Array[Int] = Array::make(30, 0)
  literal_frequencies[256] = 1
  let mut has_distance = false
  for token in tokens {
    match token {
      PdfFlateLiteral(byte) => literal_frequencies[byte] += 1
      PdfFlateLengthDistance(length, distance) => {
        let (length_symbol, _, _) = pdf_flate_length_code(length)
        let (distance_symbol, _, _) = pdf_flate_distance_code(distance)
        literal_frequencies[length_symbol] += 1
        distance_frequencies[distance_symbol] += 1
        has_distance = true
      }
    }
  }
  if !has_distance {
    distance_frequencies[0] = 1
  }
  (literal_frequencies, distance_frequencies)
}

///|
fn pdf_flate_trim_length_count(lengths : ArrayView[Int], minimum : Int) -> Int {
  let last = pdf_flate_last_nonzero_length(lengths)
  let count = last + 1
  if count < minimum {
    minimum
  } else {
    count
  }
}

///|
fn pdf_flate_write_code_length_tokens(
  writer : FlateBitWriter,
  tokens : ArrayView[PdfFlateCodeLengthToken],
  codes : ArrayView[Int],
  lengths : ArrayView[Int],
) -> Unit raise PdfError {
  for token in tokens {
    pdf_flate_write_canonical_symbol(writer, token.symbol, codes, lengths)
    writer.write_bits(token.extra.reinterpret_as_uint(), token.extra_width)
  }
}

///|
fn pdf_flate_write_dynamic_tokens(
  writer : FlateBitWriter,
  tokens : ArrayView[PdfFlateToken],
  literal_codes : ArrayView[Int],
  literal_lengths : ArrayView[Int],
  distance_codes : ArrayView[Int],
  distance_lengths : ArrayView[Int],
) -> Unit raise PdfError {
  for token in tokens {
    match token {
      PdfFlateLiteral(byte) =>
        pdf_flate_write_canonical_symbol(
          writer, byte, literal_codes, literal_lengths,
        )
      PdfFlateLengthDistance(length, distance) => {
        let (length_symbol, length_extra, length_extra_width) = pdf_flate_length_code(
          length,
        )
        pdf_flate_write_canonical_symbol(
          writer, length_symbol, literal_codes, literal_lengths,
        )
        writer.write_bits(
          length_extra.reinterpret_as_uint(),
          length_extra_width,
        )
        let (distance_symbol, distance_extra, distance_extra_width) = pdf_flate_distance_code(
          distance,
        )
        pdf_flate_write_canonical_symbol(
          writer, distance_symbol, distance_codes, distance_lengths,
        )
        writer.write_bits(
          distance_extra.reinterpret_as_uint(),
          distance_extra_width,
        )
      }
    }
  }
  pdf_flate_write_canonical_symbol(writer, 256, literal_codes, literal_lengths)
}

///|
fn pdf_flate_push_dynamic_match_block(
  output : Array[Byte],
  data : BytesView,
  final_block : Bool,
  max_probes : Int,
) -> Unit raise PdfError {
  guard data.length() > 0 else { raise InvalidFlateData }
  let tokens = pdf_flate_match_tokens(data, max_probes)
  let (literal_frequencies, distance_frequencies) = pdf_flate_count_dynamic_frequencies(
    tokens,
  )
  let literal_lengths = pdf_flate_huffman_code_lengths(literal_frequencies, 15)
  let distance_lengths = pdf_flate_huffman_code_lengths(
    distance_frequencies, 15,
  )
  let literal_count = pdf_flate_trim_length_count(literal_lengths, 257)
  let distance_count = pdf_flate_trim_length_count(distance_lengths, 1)
  let header_lengths : Array[Int] = []
  for length in literal_lengths[:literal_count] {
    header_lengths.push(length)
  }
  for length in distance_lengths[:distance_count] {
    header_lengths.push(length)
  }
  let code_length_tokens = pdf_flate_code_length_tokens(header_lengths)
  let code_length_frequencies = pdf_flate_code_length_frequencies(
    code_length_tokens,
  )
  let code_length_lengths = pdf_flate_huffman_code_lengths(
    code_length_frequencies, 7,
  )
  let code_length_codes = pdf_flate_canonical_codes(code_length_lengths)
  let literal_codes = pdf_flate_canonical_codes(literal_lengths)
  let distance_codes = pdf_flate_canonical_codes(distance_lengths)
  let writer = FlateBitWriter::new(output)
  writer.write_bits(if final_block { 1 } else { 0 }, 1)
  writer.write_bits(2, 2)
  writer.write_bits((literal_count - 257).reinterpret_as_uint(), 5)
  writer.write_bits((distance_count - 1).reinterpret_as_uint(), 5)
  let code_length_count = pdf_flate_code_length_order_count(code_length_lengths)
  writer.write_bits((code_length_count - 4).reinterpret_as_uint(), 4)
  let order = pdf_flate_code_length_order()
  for index in 0.. Unit {
  let (s1, s2) = pdf_flate_adler32(data)
  output.push(((s2 >> 8) & 0xFF).to_byte())
  output.push((s2 & 0xFF).to_byte())
  output.push(((s1 >> 8) & 0xFF).to_byte())
  output.push((s1 & 0xFF).to_byte())
}

///|
fn pdf_flate_default_header() -> Array[Byte] {
  [0x78, 0x01]
}

///|
fn pdf_flate_header_for_level(level : Int) -> Array[Byte] raise PdfError {
  guard level >= 0 && level <= 9 else { raise InvalidFlateData }
  let compression_hint = if level <= 1 {
    0
  } else if level <= 5 {
    1
  } else if level == 6 {
    2
  } else {
    3
  }
  let cmf = 0x78
  let flg_without_check = compression_hint << 6
  let remainder = ((cmf << 8) + flg_without_check) % 31
  let fcheck = if remainder == 0 { 0 } else { 31 - remainder }
  [cmf.to_byte(), (flg_without_check + fcheck).to_byte()]
}

///|
fn pdf_flate_output_with_header(header : ArrayView[Byte]) -> Array[Byte] {
  let output : Array[Byte] = []
  for byte in header {
    output.push(byte)
  }
  output
}

///|
fn pdf_flate_encode_stored_view_with_header(
  data : BytesView,
  header : ArrayView[Byte],
) -> PdfBytes {
  let output = pdf_flate_output_with_header(header)
  if data.length() == 0 {
    pdf_flate_push_stored_block(output, data, 0, 0, true)
  } else {
    let mut offset = 0
    while offset < data.length() {
      let remaining = data.length() - offset
      let length = if remaining > 65535 { 65535 } else { remaining }
      pdf_flate_push_stored_block(
        output,
        data,
        offset,
        length,
        offset + length == data.length(),
      )
      offset += length
    }
  }
  pdf_flate_push_adler32(output, data)
  Bytes::from_array(output)
}

///|
fn pdf_flate_encode_fixed_literal_view_with_header(
  data : BytesView,
  header : ArrayView[Byte],
) -> PdfBytes {
  let output = pdf_flate_output_with_header(header)
  pdf_flate_push_fixed_literal_block(output, data, true)
  pdf_flate_push_adler32(output, data)
  Bytes::from_array(output)
}

///|
fn pdf_flate_encode_fixed_repeat_view_with_header(
  data : BytesView,
  header : ArrayView[Byte],
) -> PdfBytes raise PdfError {
  let output = pdf_flate_output_with_header(header)
  pdf_flate_push_fixed_repeat_block(output, data, true)
  pdf_flate_push_adler32(output, data)
  Bytes::from_array(output)
}

///|
fn pdf_flate_encode_fixed_match_view_with_header(
  data : BytesView,
  header : ArrayView[Byte],
  max_probes : Int,
) -> PdfBytes raise PdfError {
  let output = pdf_flate_output_with_header(header)
  pdf_flate_push_fixed_match_block(output, data, true, max_probes)
  pdf_flate_push_adler32(output, data)
  Bytes::from_array(output)
}

///|
fn pdf_flate_encode_dynamic_match_view_with_header(
  data : BytesView,
  header : ArrayView[Byte],
  max_probes : Int,
) -> PdfBytes raise PdfError {
  let output = pdf_flate_output_with_header(header)
  pdf_flate_push_dynamic_match_block(output, data, true, max_probes)
  pdf_flate_push_adler32(output, data)
  Bytes::from_array(output)
}

///|
fn pdf_flate_shortest(a : PdfBytes, b : PdfBytes) -> PdfBytes {
  if b.length() < a.length() {
    b
  } else {
    a
  }
}

///|
fn pdf_flate_shortest_result(
  current : PdfBytes,
  candidate : Result[PdfBytes, Error],
) -> PdfBytes {
  match candidate {
    Ok(bytes) => pdf_flate_shortest(current, bytes)
    Err(_) => current
  }
}

///|
fn pdf_flate_encode_shortest_view_with_header(
  data : BytesView,
  header : ArrayView[Byte],
  max_probes : Int,
) -> PdfBytes {
  let stored = pdf_flate_encode_stored_view_with_header(data, header)
  let fixed = pdf_flate_encode_fixed_literal_view_with_header(data, header)
  let mut shortest = pdf_flate_shortest(stored, fixed)
  let repeat : Result[PdfBytes, Error] = try? pdf_flate_encode_fixed_repeat_view_with_header(
    data, header,
  )
  shortest = pdf_flate_shortest_result(shortest, repeat)
  let matched : Result[PdfBytes, Error] = try? pdf_flate_encode_fixed_match_view_with_header(
    data, header, max_probes,
  )
  shortest = pdf_flate_shortest_result(shortest, matched)
  let dynamic : Result[PdfBytes, Error] = try? pdf_flate_encode_dynamic_match_view_with_header(
    data, header, max_probes,
  )
  pdf_flate_shortest_result(shortest, dynamic)
}

///|
fn pdf_flate_encode_fast_view_with_header(
  data : BytesView,
  header : ArrayView[Byte],
  max_probes : Int,
) -> PdfBytes {
  let stored = pdf_flate_encode_stored_view_with_header(data, header)
  let matched : Result[PdfBytes, Error] = try? pdf_flate_encode_fixed_match_view_with_header(
    data, header, max_probes,
  )
  pdf_flate_shortest_result(stored, matched)
}

///|
fn pdf_flate_max_probes_for_level(level : Int) -> Int {
  if level <= 1 {
    8
  } else if level <= 3 {
    16
  } else if level <= 5 {
    32
  } else if level == 6 {
    64
  } else if level <= 8 {
    128
  } else {
    256
  }
}

///|
fn pdf_flate_encode_view_with_level_pure(
  data : BytesView,
  level : Int,
) -> PdfBytes raise PdfError {
  let header = pdf_flate_header_for_level(level)
  if level == 0 {
    pdf_flate_encode_stored_view_with_header(data, header)
  } else if level <= 3 {
    pdf_flate_encode_fast_view_with_header(
      data,
      header,
      pdf_flate_max_probes_for_level(level),
    )
  } else {
    pdf_flate_encode_shortest_view_with_header(
      data,
      header,
      pdf_flate_max_probes_for_level(level),
    )
  }
}

///|
fn pdf_flate_encode_view_pure(data : BytesView) -> PdfBytes {
  pdf_flate_encode_shortest_view_with_header(
    data,
    pdf_flate_default_header(),
    64,
  )
}

///|
#cfg(target="native")
#borrow(data, status)
extern "C" fn pdf_flate_zlib_encode_ffi(
  data : PdfBytes,
  level : Int,
  status : Ref[Int],
) -> PdfBytes = "pdflite_zlib_compress"

///|
#cfg(target="native")
#borrow(data, status)
extern "C" fn pdf_flate_zlib_decode_ffi(
  data : PdfBytes,
  status : Ref[Int],
) -> PdfBytes = "pdflite_zlib_decompress"

///|
#cfg(target="native")
#coverage.skip
fn pdf_flate_native_payload(
  result : PdfBytes,
  status : Ref[Int],
) -> PdfBytes raise PdfError {
  if status.val != 1 {
    raise InvalidFlateData
  }
  result
}

///|
#cfg(target="native")
#coverage.skip
fn pdf_flate_encode_bytes_native(
  data : PdfBytes,
  level : Int,
) -> PdfBytes raise PdfError {
  let _ = pdf_flate_header_for_level(level)
  let status = Ref::{ val: 0 }
  try
    pdf_flate_native_payload(
      pdf_flate_zlib_encode_ffi(data, level, status),
      status,
    )
  catch {
    _ => pdf_flate_encode_view_with_level_pure(data, level)
  } noraise {
    bytes => bytes
  }
}

///|
/// Encode borrowed bytes as a zlib-wrapped Flate stream.
///
/// `level` must be in the zlib range `0..9`; invalid levels raise
/// `InvalidFlateData`. On non-native targets this uses the pure MoonBit encoder.
#cfg(not(target="native"))
pub fn pdf_flate_encode_view_with_level(
  data : BytesView,
  level : Int,
) -> PdfBytes raise PdfError {
  pdf_flate_encode_view_with_level_pure(data, level)
}

///|
/// Encode borrowed bytes as a zlib-wrapped Flate stream.
///
/// `level` must be in the zlib range `0..9`; invalid levels raise
/// `InvalidFlateData`. On the native target this tries the zlib FFI first and
/// falls back to the pure MoonBit encoder if the native call fails.
#cfg(target="native")
#coverage.skip
pub fn pdf_flate_encode_view_with_level(
  data : BytesView,
  level : Int,
) -> PdfBytes raise PdfError {
  pdf_flate_encode_bytes_native(data.to_owned(), level)
}

///|
/// Encode borrowed bytes with the default compression level.
///
/// This non-native implementation uses the pure MoonBit encoder.
#cfg(not(target="native"))
pub fn pdf_flate_encode_view(data : BytesView) -> PdfBytes {
  pdf_flate_encode_view_pure(data)
}

///|
/// Encode borrowed bytes with the default compression level.
///
/// On the native target this tries zlib at level `6` and falls back to the pure
/// MoonBit encoder if the native call fails.
#cfg(target="native")
#coverage.skip
pub fn pdf_flate_encode_view(data : BytesView) -> PdfBytes {
  try pdf_flate_encode_bytes_native(data.to_owned(), 6) catch {
    _ => pdf_flate_encode_view_pure(data)
  } noraise {
    bytes => bytes
  }
}

///|
/// Encode owned bytes as a zlib-wrapped Flate stream.
///
/// `level` must be in the zlib range `0..9`; invalid levels raise
/// `InvalidFlateData`. This non-native implementation uses the pure MoonBit
/// encoder.
#cfg(not(target="native"))
pub fn pdf_flate_encode_with_level(
  data : PdfBytes,
  level : Int,
) -> PdfBytes raise PdfError {
  pdf_flate_encode_view_with_level_pure(data, level)
}

///|
/// Encode owned bytes as a zlib-wrapped Flate stream.
///
/// `level` must be in the zlib range `0..9`; invalid levels raise
/// `InvalidFlateData`. On the native target this uses zlib FFI with a pure
/// MoonBit fallback.
#cfg(target="native")
#coverage.skip
pub fn pdf_flate_encode_with_level(
  data : PdfBytes,
  level : Int,
) -> PdfBytes raise PdfError {
  pdf_flate_encode_bytes_native(data, level)
}

///|
/// Encode owned bytes with the default compression level.
///
/// This non-native implementation uses the pure MoonBit encoder.
#cfg(not(target="native"))
pub fn pdf_flate_encode(data : PdfBytes) -> PdfBytes {
  pdf_flate_encode_view_pure(data)
}

///|
/// Encode owned bytes with the default compression level.
///
/// On the native target this tries zlib at level `6` and falls back to the pure
/// MoonBit encoder if the native call fails.
#cfg(target="native")
#coverage.skip
pub fn pdf_flate_encode(data : PdfBytes) -> PdfBytes {
  try pdf_flate_encode_bytes_native(data, 6) catch {
    _ => pdf_flate_encode_view_pure(data)
  } noraise {
    bytes => bytes
  }
}

///|
fn pdf_flate_decode_view_pure(data : BytesView) -> PdfBytes raise PdfError {
  if data.length() == 0 {
    Bytes::new(0)
  } else {
    if data.length() < 6 {
      raise InvalidFlateData
    }
    let cmf = data[0].to_int()
    let flg = data[1].to_int()
    if (cmf & 0x0F) != 8 || ((cmf << 8) + flg) % 31 != 0 || (flg & 0x20) != 0 {
      raise InvalidFlateData
    }
    let decoded = pdf_deflate_decode_raw(data[2:data.length() - 4])
    pdf_flate_check_adler32(data, decoded)
    decoded
  }
}

///|
#cfg(target="native")
#coverage.skip
fn pdf_flate_decode_bytes_native(data : PdfBytes) -> PdfBytes raise PdfError {
  if data.length() == 0 {
    Bytes::new(0)
  } else {
    let status = Ref::{ val: 0 }
    try
      pdf_flate_native_payload(pdf_flate_zlib_decode_ffi(data, status), status)
    catch {
      _ => pdf_flate_decode_view_pure(data)
    } noraise {
      bytes => bytes
    }
  }
}

///|
/// Decode a zlib-wrapped Flate stream from a borrowed byte view.
///
/// Empty input decodes to empty output. Malformed headers, unsupported preset
/// dictionaries, invalid Deflate data, and Adler-32 mismatches raise
/// `InvalidFlateData`.
#cfg(not(target="native"))
pub fn pdf_flate_decode_view(data : BytesView) -> PdfBytes raise PdfError {
  pdf_flate_decode_view_pure(data)
}

///|
/// Decode a zlib-wrapped Flate stream from a borrowed byte view.
///
/// On the native target this tries zlib FFI first and falls back to the pure
/// MoonBit decoder if the native call fails. Invalid compressed data raises
/// `InvalidFlateData`.
#cfg(target="native")
#coverage.skip
pub fn pdf_flate_decode_view(data : BytesView) -> PdfBytes raise PdfError {
  if data.length() == 0 {
    Bytes::new(0)
  } else {
    let status = Ref::{ val: 0 }
    try
      pdf_flate_native_payload(
        pdf_flate_zlib_decode_ffi(data.to_owned(), status),
        status,
      )
    catch {
      _ => pdf_flate_decode_view_pure(data)
    } noraise {
      bytes => bytes
    }
  }
}

///|
/// Decode a zlib-wrapped Flate stream from owned bytes.
///
/// Empty input decodes to empty output. Malformed compressed data raises
/// `InvalidFlateData`.
#cfg(not(target="native"))
pub fn pdf_flate_decode(data : PdfBytes) -> PdfBytes raise PdfError {
  pdf_flate_decode_view_pure(data)
}

///|
/// Decode a zlib-wrapped Flate stream from owned bytes.
///
/// On the native target this uses zlib FFI with a pure MoonBit fallback.
#cfg(target="native")
#coverage.skip
pub fn pdf_flate_decode(data : PdfBytes) -> PdfBytes raise PdfError {
  pdf_flate_decode_bytes_native(data)
}

///|
fn pdf_flate_decode_view_prefix(
  data : BytesView,
) -> (PdfBytes, Int) raise PdfError {
  if data.length() == 0 {
    (Bytes::new(0), 0)
  } else {
    if data.length() < 6 {
      raise InvalidFlateData
    }
    let cmf = data[0].to_int()
    let flg = data[1].to_int()
    if (cmf & 0x0F) != 8 || ((cmf << 8) + flg) % 31 != 0 || (flg & 0x20) != 0 {
      raise InvalidFlateData
    }
    let (decoded, deflate_length) = pdf_deflate_decode_raw_prefix(data[2:])
    let encoded_length = 2 + deflate_length + 4
    if encoded_length > data.length() {
      raise InvalidFlateData
    }
    pdf_flate_check_adler32(data[:encoded_length], decoded)
    (decoded, encoded_length)
  }
}