///|
pub fn normalize_u16_list(values : ArrayView[UInt16]) -> String {
  let parts : Array[String] = []
  for v in values {
    if !is_grease(v) {
      parts.push(v.to_int().to_string())
    }
  }
  parts.join("-")
}

///|
fn normalize_byte_list(values : ArrayView[Byte]) -> String {
  let parts : Array[String] = []
  for v in values {
    parts.push(v.to_int().to_string())
  }
  parts.join("-")
}

///|
pub fn ClientHello::ja3_string(self : ClientHello) -> String {
  [
    self.legacy_version.to_int().to_string(),
    normalize_u16_list(self.cipher_suites),
    normalize_u16_list(self.extensions),
    normalize_u16_list(self.supported_groups),
    normalize_byte_list(self.ec_point_formats),
  ].join(",")
}

///|
pub fn ascii_bytes(text : String) -> Bytes {
  let out : Array[Byte] = []
  for unit in text.code_units() {
    out.push(unit.to_byte())
  }
  Bytes::from_array(out)
}

///|
fn md5_hex(text : String) -> String {
  let digest = @crypto.md5(ascii_bytes(text))
  let out = StringBuilder()
  for b in digest {
    out.write_string(b.to_hex())
  }
  out.to_string()
}

///|
pub fn ClientHello::ja3_digest(self : ClientHello) -> String {
  md5_hex(self.ja3_string())
}

///|
pub fn ClientHello::ja4_a(self : ClientHello) -> String {
  let sni_mark = if self.server_name is Some(_) { "d" } else { "i" }
  let cipher_count = self.cipher_suites.filter(fn(v) { !is_grease(v) }).length()
  let ext_count = self.extensions.filter(fn(v) { !is_grease(v) }).length()
  "t13\{sni_mark}\{cipher_count.to_string().pad_start(2, '0')}\{ext_count.to_string().pad_start(2, '0')}"
}