///|
/// Bounded MCAP LZ4 adapter. Decode into the declared, prechecked chunk size.
fn decode_lz4_frame(bytes : Bytes, expected : Int) -> Bytes raise McapError {
  let input = cursor(bytes, default_limits())
  if input.u32() != 0x184d2204U {
    raise Invalid("invalid LZ4 standard frame magic")
  }
  let flags = input.take(1)[0].to_int()
  let bd = input.take(1)[0].to_int()
  if flags >> 6 != 1 || (flags & 2) != 0 || (bd & 0x8f) != 0 {
    raise Invalid("reserved LZ4 frame bits")
  }
  let independent = (flags & 0x20) != 0
  let block_checksum = (flags & 0x10) != 0
  let content_checksum = (flags & 4) != 0
  let block_max = match (bd >> 4) & 7 {
    4 => 65536
    5 => 262144
    6 => 1048576
    7 => 4194304
    _ => raise Invalid("invalid LZ4 maximum block size")
  }
  if (flags & 8) != 0 && input.u64() != expected.to_uint64() {
    raise Invalid("LZ4 content size differs from Chunk")
  }
  if (flags & 1) != 0 && input.u32() != 0U {
    raise Invalid("LZ4 external dictionary is unsupported")
  }
  let header_end = input.pos
  let header_crc = input.take(1)[0].to_uint()
  if ((@checksum.xxh32(bytes[4:header_end]) >> 8) & 255U) != header_crc {
    raise Invalid("LZ4 header checksum mismatch")
  }
  let out : FixedArray[Byte] = FixedArray::make(expected, 0)
  let mut written = 0
  while true {
    let raw_size = input.u32()
    if raw_size == 0U {
      break
    }
    let raw = (raw_size & 0x80000000U) != 0U
    let size = bounded_length(
      (raw_size & 0x7fffffffU).to_uint64(),
      block_max,
      "LZ4 block",
    )
    let block = input.take(size)
    if block_checksum && input.u32() != @checksum.xxh32(block[:]) {
      raise Invalid("LZ4 block checksum mismatch")
    }
    if raw {
      if size > expected - written {
        raise Invalid("LZ4 output exceeds declared size")
      }
      for byte in block {
        out[written] = byte
        written += 1
      }
    } else {
      written = decode_lz4_block(block, out, written, block_max, independent)
    }
  }
  if written != expected {
    raise Invalid("LZ4 output size mismatch")
  }
  let result = Bytes::makei(written, fn(i) { out[i] })
  if content_checksum && input.u32() != @checksum.xxh32(result[:]) {
    raise Invalid("LZ4 content checksum mismatch")
  }
  input.finish()
  result
}

///|
fn lz4_length(
  input : Cursor,
  nibble : Int,
  maximum : Int,
) -> Int raise McapError {
  let mut length = nibble
  if length > maximum {
    raise Invalid("LZ4 sequence exceeds output bound")
  }
  if nibble == 15 {
    while true {
      let byte = input.take(1)[0].to_int()
      if byte > maximum - length {
        raise Invalid("LZ4 extended length exceeds output bound")
      }
      length += byte
      if byte != 255 {
        break
      }
    }
  }
  length
}

///|
fn decode_lz4_block(
  bytes : Bytes,
  out : FixedArray[Byte],
  start : Int,
  block_max : Int,
  independent : Bool,
) -> Int raise McapError {
  let input = cursor(bytes, default_limits())
  let mut written = start
  let end = start + block_max.min(out.length() - start)
  while input.pos < bytes.length() {
    let token = input.take(1)[0].to_int()
    let literals = input.take(lz4_length(input, token >> 4, end - written))
    for byte in literals {
      out[written] = byte
      written += 1
    }
    if input.pos == bytes.length() {
      break
    }
    let distance = input.u16()
    if distance == 0 ||
      distance > written - (if independent { start } else { 0 }) {
      raise Invalid("LZ4 match references unavailable history")
    }
    if end - written < 4 {
      raise Invalid("LZ4 match exceeds output bound")
    }
    let count = lz4_length(input, token & 15, end - written - 4) + 4
    for j = 0; j < count; j = j + 1 {
      out[written] = out[written - distance]
      written += 1
    }
  }
  written
}