///|
pub(all) struct SslInfo {
  client_flags : Byte
  verify : UInt
  sub_tlvs : Array[RawTlv]
} derive(Debug)

///|
pub(all) enum ParsedTlv {
  Alpn(Bytes)
  Authority(String)
  Crc32c(UInt)
  Noop(Bytes)
  UniqueId(Bytes)
  Ssl(SslInfo)
  Netns(String)
  Custom(Byte, Bytes)
  Experimental(Byte, Bytes)
  FutureReserved(Byte, Bytes)
  Unknown(Byte, Bytes)
} derive(Debug)

///|
fn decode_utf8_tlv(
  value : Bytes,
  offset : Int,
  context : String,
) -> Result[String, ProxyError] {
  let decoded = Some(@utf8.decode(value, ignore_bom=true)) catch { _ => None }
  match decoded {
    Some(text) => Ok(text)
    None => Err(proxy_error(InvalidTlv, offset, context))
  }
}

///|
fn decode_ascii_tlv(
  value : Bytes,
  offset : Int,
  context : String,
) -> Result[String, ProxyError] {
  for i = 0; i < value.length(); i = i + 1 {
    if value[i].to_int() > 127 {
      return Err(proxy_error(InvalidTlv, offset + i, context))
    }
  }
  let chars = Array::make(0, ' ')
  for byte in value {
    chars.push(byte.to_int().to_char().unwrap())
  }
  Ok(String::from_array(chars))
}

///|
pub fn interpret_tlv(
  tlv : RawTlv,
  offset? : Int = 0,
) -> Result[ParsedTlv, ProxyError] {
  match tlv.type_code {
    TLV_ALPN => Ok(Alpn(tlv.value))
    TLV_AUTHORITY =>
      match
        decode_utf8_tlv(tlv.value, offset + 3, "AUTHORITY must be valid UTF-8") {
        Ok(text) => Ok(Authority(text))
        Err(err) => Err(err)
      }
    TLV_CRC32C =>
      if tlv.value.length() != 4 {
        Err(
          proxy_error(InvalidTlv, offset, "CRC32C TLV must contain four bytes"),
        )
      } else {
        Ok(
          Crc32c(
            (tlv.value[0].to_uint() << 24) |
            (tlv.value[1].to_uint() << 16) |
            (tlv.value[2].to_uint() << 8) |
            tlv.value[3].to_uint(),
          ),
        )
      }
    TLV_NOOP => Ok(Noop(tlv.value))
    TLV_UNIQUE_ID =>
      if tlv.value.length() > 128 {
        Err(
          proxy_error(InvalidTlv, offset, "UNIQUE_ID must be at most 128 bytes"),
        )
      } else {
        Ok(UniqueId(tlv.value))
      }
    TLV_SSL =>
      match parse_ssl_tlv(tlv.value, offset=offset + 3) {
        Ok(ssl) =>
          Ok(
            Ssl({
              client_flags: ssl.client_flags,
              verify: ssl.verify,
              sub_tlvs: ssl.sub_tlvs,
            }),
          )
        Err(err) => Err(err)
      }
    TLV_NETNS =>
      match decode_ascii_tlv(tlv.value, offset + 3, "NETNS must be US-ASCII") {
        Ok(text) => Ok(Netns(text))
        Err(err) => Err(err)
      }
    code =>
      match classify_tlv_range(code) {
        CustomRange => Ok(Custom(code, tlv.value))
        ExperimentalRange => Ok(Experimental(code, tlv.value))
        FutureReservedRange => Ok(FutureReserved(code, tlv.value))
        _ => Ok(Unknown(code, tlv.value))
      }
  }
}

///|
pub fn interpret_all_tlvs(
  tlvs : Array[RawTlv],
) -> Result[Array[ParsedTlv], ProxyError] {
  let parsed = Array::make(0, Unknown(b'\x00', Bytes::new(0)))
  for tlv in tlvs {
    match interpret_tlv(tlv) {
      Ok(value) => parsed.push(value)
      Err(err) => return Err(err)
    }
  }
  Ok(parsed)
}