///|
priv struct KV3LegacyReader {
  input : ValveBytes
  strings : Array[String]
  options : BinaryOptions
  mut nodes : Int32
}

///|
fn KV3LegacyReader::string(self : KV3LegacyReader) -> String raise BinaryError {
  let id = self.input.uint(4)
  if id < self.strings.length().to_uint64() {
    self.strings[id.to_int()]
  } else {
    ""
  }
}

///|
fn KV3LegacyReader::tag(
  self : KV3LegacyReader,
) -> (Int32, String) raise BinaryError {
  let tag = self.input.uint(1).to_int()
  (
    tag & 127,
    if (tag & 128) != 0 {
      kv3_binary_flag_id(self.input.uint(1).to_int(), 0)
    } else {
      ""
    },
  )
}

///|
fn KV3LegacyReader::value(
  self : KV3LegacyReader,
  tag : Int32,
  flag : String,
  depth : Int32,
) -> KV3BinaryValue raise {
  self.nodes += 1
  if depth > self.options.max_depth || self.nodes > self.options.max_entries {
    raise InvalidBinary(self.input.pos, "Legacy KV3 tree budget exceeded")
  }
  let value : KV3BinaryValue = match tag {
    1 => BNull
    2 => BBool(self.input.uint(1) != 0UL)
    13 | 14 => BBool(tag == 13)
    15 | 16 => BNumber(3, (tag - 15).to_uint64())
    17 | 18 => BNumber(5, (tag - 17).to_double().reinterpret_as_uint64())
    3 | 4 | 5 => BNumber(tag, self.input.uint(8))
    11 | 12 => BNumber(tag, self.input.uint(4))
    6 => BString(self.string())
    7 => BBlob(self.input.take(self.input.count(self.options.max_bytes)))
    8 | 10 => {
      let count = self.input.count(self.options.max_entries - self.nodes)
      let subtype = if tag == 10 { self.tag() } else { (0, "") }
      let children : Array[KV3BinaryValue] = []
      for _ in 0.. {
      let count = self.input.count(self.options.max_entries - self.nodes)
      let entries : Array[(String, KV3BinaryValue)] = []
      let seen : Map[String, Int32] = Map([])
      for _ in 0.. raise InvalidBinary(self.input.pos, "Unsupported legacy KV3 node")
  }
  if flag.is_empty() {
    value
  } else {
    BFlag(flag, value)
  }
}

///|
fn kv3_legacy_decode(
  input : ValveBytes,
  options : BinaryOptions,
) -> KV3BinaryDocument raise {
  let encoding = kv3_bytes_guid(input.take(16))
  let format_id = kv3_bytes_guid(input.take(16))
  let compression = if encoding == "95791a46-95bc-4f6c-a70b-05bca1b7dfd2" {
    3
  } else if encoding == "6847348a-63a1-4f5c-a197-53806fd9b119" {
    1
  } else if [
      "1b860500-f7d8-40c1-ad82-75a48267e714", "6f620a00-fef0-4305-a35f-042346b1db29",
      "6eb109e6-6b85-4583-a312-703a6e04068c",
    ].contains(encoding) {
    0
  } else {
    raise InvalidBinary(input.pos, "Unknown legacy KV3 encoding")
  }
  let payload = if compression == 1 {
    let size = input.count(options.max_bytes)
    valve_lz4_decode(
      input.take(input.remaining()),
      size,
      b"",
      options.max_bytes,
    )
  } else if compression == 3 {
    let header = input.uint(4)
    let size = (header & 0x7fffffffUL).to_int()
    if size > options.max_bytes {
      raise InvalidBinary(input.pos, "Legacy block output exceeds budget")
    }
    if (header & 0x80000000UL) != 0UL {
      input.take(size)
    } else {
      let output : Array[Byte] = []
      while output.length() < size {
        let mask = input.uint(2).to_int()
        for bit in 0..<16 {
          if output.length() == size {
            break
          }
          if (mask & (1 << bit)) == 0 {
            output.push(input.uint(1).to_byte())
          } else {
            let token = input.uint(2).to_int()
            let distance = (token >> 4) + 1
            let length = (token & 15) + 3
            if distance > output.length() || length > size - output.length() {
              raise InvalidBinary(input.pos, "Invalid legacy block match")
            }
            for _ in 0..