///|
/// Shared bounded byte cursor for Valve archive and resource containers.
priv struct ValveBytes {
  data : Bytes
  mut pos : Int32
}

///|
fn valve_bytes(data : Bytes) -> ValveBytes {
  { data, pos: 0, }
}

///|
fn ValveBytes::remaining(self : ValveBytes) -> Int32 {
  self.data.length() - self.pos
}

///|
fn ValveBytes::take(
  self : ValveBytes,
  count : Int32,
) -> Bytes raise BinaryError {
  if count < 0 || count > self.remaining() {
    raise InvalidBinary(self.pos, "Truncated Valve byte range")
  }
  let result = self.data[self.pos:self.pos + count].to_owned()
  self.pos += count
  result
}

///|
fn ValveBytes::uint(
  self : ValveBytes,
  width : Int32,
) -> UInt64 raise BinaryError {
  if width < 1 || width > 8 || width > self.remaining() {
    raise InvalidBinary(self.pos, "Truncated integer")
  }
  let mut n = 0UL
  for i in 0.. Int32 raise BinaryError {
  let n = self.uint(4)
  if n > limit.to_uint64() {
    raise InvalidBinary(self.pos - 4, "Count exceeds resource budget")
  }
  n.to_int()
}

///|
fn ValveBytes::cstring(
  self : ValveBytes,
  limit : Int32,
) -> String raise BinaryError {
  let start = self.pos
  while self.pos < self.data.length() && self.data[self.pos] != b'\x00' {
    self.pos += 1
    if self.pos - start > limit {
      raise InvalidBinary(start, "String exceeds byte budget")
    }
  }
  if self.pos == self.data.length() {
    raise InvalidBinary(start, "Unterminated string")
  }
  let result = @utf8.decode(self.data[start:self.pos], ignore_bom=false) catch {
    _ => raise InvalidBinary(start, "Invalid UTF-8")
  }
  self.pos += 1
  result
}

///|
fn valve_uint(output : @buffer.Buffer, n : UInt64, width : Int32) -> Unit {
  for i in 0..> (i * 8)) & 255UL).to_byte())
  }
}

///|
fn valve_align(n : Int32, alignment : Int32) -> Int32 {
  (n + alignment - 1) / alignment * alignment
}

///|
/// Raw LZ4 block decoder, including overlapping matches and an optional chain
/// dictionary. A frame cannot consume bytes outside its explicit input range.
fn valve_lz4_decode(
  input : Bytes,
  expected : Int32,
  dictionary : Bytes,
  limit : Int32,
  exact~ : Bool = true,
) -> Bytes raise BinaryError {
  if expected < 0 || expected > limit || dictionary.length() > 65536 {
    raise InvalidBinary(0, "Invalid LZ4 output budget")
  }
  let reader = valve_bytes(input)
  let output : Array[Byte] = dictionary.to_array()
  let base = output.length()
  fn length(initial : Int32) -> Int32 raise BinaryError {
    let mut n = initial
    if initial == 15 {
      while true {
        let next = reader.uint(1).to_int()
        if next > limit - n {
          raise InvalidBinary(reader.pos, "LZ4 length exceeds budget")
        }
        n += next
        if next != 255 {
          break
        }
      }
    }
    n
  }
  while reader.remaining() > 0 {
    let token = reader.uint(1).to_int()
    let literals = length(token >> 4)
    if literals > expected - (output.length() - base) {
      raise InvalidBinary(reader.pos, "LZ4 literal overflow")
    }
    for byte in reader.take(literals) {
      output.push(byte)
    }
    if reader.remaining() == 0 {
      break
    }
    let distance = reader.uint(2).to_int()
    if distance == 0 || distance > output.length() {
      raise InvalidBinary(reader.pos, "Invalid LZ4 match distance")
    }
    let count = length(token & 15) + 4
    if count > expected - (output.length() - base) {
      raise InvalidBinary(reader.pos, "LZ4 match exceeds output")
    }
    for _ in 0.. Bytes {
  let output = @buffer.Buffer()
  let table : FixedArray[Int32] = FixedArray::make(65536, -1)
  fn extension(rest : Int32) {
    let mut rest = rest
    while rest >= 255 {
      output.write_byte(b'\xff')
      rest -= 255
    }
    output.write_byte(rest.to_byte())
  }
  fn hash(pos : Int32) -> Int32 {
    let mut word = 0U
    for i in 0..<4 {
      word = word | (input[pos + i].to_int().reinterpret_as_uint() << (8 * i))
    }
    ((word * 2654435761U) >> 16).reinterpret_as_int()
  }
  let mut cursor = 0
  let mut anchor = 0
  while cursor <= input.length() - 12 {
    let h = hash(cursor)
    let previous = table[h]
    table[h] = cursor
    if previous < 0 ||
      cursor - previous > 65535 ||
      input[previous:previous + 4].to_owned() !=
      input[cursor:cursor + 4].to_owned() {
      cursor += 1
      continue
    }
    let mut count = 4
    while cursor + count < input.length() - 5 &&
          input[previous + count] == input[cursor + count] {
      count += 1
    }
    let literals = cursor - anchor
    output.write_byte(((literals.min(15) << 4) | (count - 4).min(15)).to_byte())
    if literals >= 15 {
      extension(literals - 15)
    }
    output.write_bytes(input[anchor:cursor])
    valve_uint(output, (cursor - previous).to_uint64(), 2)
    if count - 4 >= 15 {
      extension(count - 19)
    }
    cursor += count
    anchor = cursor
    if cursor >= 2 && cursor + 2 < input.length() {
      table[hash(cursor - 2)] = cursor - 2
    }
  }
  let literals = input.length() - anchor
  output.write_byte((literals.min(15) << 4).to_byte())
  if literals >= 15 {
    extension(literals - 15)
  }
  output.write_bytes(input[anchor:])
  output.to_bytes()
}

///|
fn valve_decompress(
  input : Bytes,
  codec : Int32,
  expected : Int32,
  limit : Int32,
) -> Bytes raise {
  if expected < 0 || expected > limit || input.length() > limit {
    raise InvalidBinary(0, "Compression budget exceeded")
  }
  let result = match codec {
    0 => input
    1 => valve_lz4_decode(input, expected, b"", limit)
    2 => valve_zstd_decode(input, expected, limit)
    _ => raise InvalidBinary(0, "Unknown compression codec")
  }
  if result.length() != expected {
    raise InvalidBinary(0, "Decompressed size mismatch")
  }
  result
}

///|
fn valve_compress(input : Bytes, codec : Int32) -> Bytes raise {
  match codec {
    0 => input
    1 => valve_lz4_encode(input)
    2 => @zstd.compress(input)
    _ => raise InvalidBinary(0, "Unknown compression codec")
  }
}

///|
/// Validate every frame's declared window and boundary before invoking the
/// streaming decoder. Concatenated frames are decoded separately and bounded.
fn valve_zstd_decode(
  input : Bytes,
  expected : Int32,
  limit : Int32,
) -> Bytes raise {
  let reader = valve_bytes(input)
  let output = @buffer.Buffer()
  while reader.remaining() > 0 {
    let start = reader.pos
    if reader.uint(4) != 0xfd2fb528UL {
      raise InvalidBinary(start, "Expected a Zstandard frame")
    }
    let descriptor = reader.uint(1).to_int()
    if (descriptor & 24) != 0 {
      raise InvalidBinary(start, "Reserved Zstandard frame bits")
    }
    let single = (descriptor & 32) != 0
    if !single {
      let w = reader.uint(1).to_int()
      let base = 1UL << (10 + (w >> 3))
      let window = base + base / 8UL * (w & 7).to_uint64()
      if window > limit.to_uint64() {
        raise InvalidBinary(start, "Zstandard window exceeds budget")
      }
    }
    let dict_width = [0, 1, 2, 4][descriptor & 3]
    if dict_width > 0 && reader.uint(dict_width) != 0UL {
      raise InvalidBinary(start, "External Zstandard dictionary required")
    }
    let size_flag = descriptor >> 6
    let size_width = if size_flag == 0 {
      if single {
        1
      } else {
        0
      }
    } else {
      [0, 2, 4, 8][size_flag]
    }
    if size_width > 0 {
      let size = reader.uint(size_width) +
        (if size_flag == 1 { 256UL } else { 0UL })
      if size > (expected - output.length()).to_uint64() {
        raise InvalidBinary(
          start, "Zstandard content size exceeds output budget",
        )
      }
    }
    while true {
      let header = reader.uint(3).to_int()
      let kind = (header >> 1) & 3
      let size = header >> 3
      if kind == 3 || size > 131072 {
        raise InvalidBinary(reader.pos, "Invalid Zstandard block")
      }
      reader.take(if kind == 1 { 1 } else { size }) |> ignore
      if (header & 1) != 0 {
        break
      }
    }
    if (descriptor & 4) != 0 {
      reader.take(4) |> ignore
    }
    let inflater = @zstd.Inflater::new()
    inflater.src(input[start:reader.pos])
    let mut done = false
    while !done {
      match inflater.decode() {
        Data(bytes) => {
          if bytes.length() > expected - output.length() {
            raise InvalidBinary(start, "Zstandard output exceeds expected size")
          }
          output.write_bytes(bytes)
        }
        End => done = true
        Await => raise InvalidBinary(reader.pos, "Truncated Zstandard frame")
        Error(error) => raise error
      }
    }
  }
  if output.length() != expected {
    raise InvalidBinary(reader.pos, "Zstandard decoded size mismatch")
  }
  output.to_bytes()
}