// The Application-Layer Protocol Negotiation extension (RFC 7301): the client offers a list
// of application protocols in its ClientHello, and the server names the one it picked in its
// EncryptedExtensions (TLS 1.3) or ServerHello (TLS 1.2). For HTTP/3 the protocol is "h3"
// (RFC 9114 §3.1) — a QUIC connection that does not negotiate it is not HTTP/3. The
// extension_data is a ProtocolNameList: a two-byte list length, then each name as a one-byte
// length and its bytes. This is the wire codec that lets a real client (curl --http3) and a
// mooncat server agree they are both speaking h3.

///|
/// The application_layer_protocol_negotiation extension type (RFC 7301 §3.1).
pub let tls_ext_alpn : Int = 0x0010

///|
/// The HTTP/3 ALPN protocol identifier (RFC 9114 §3.1).
pub let alpn_h3 : String = "h3"

///|
/// Encode an ALPN ProtocolNameList (RFC 7301 §3.1): a two-byte length of the name list, then
/// each protocol as a one-byte length and its bytes.
pub fn tls_encode_alpn(protocols : Array[String]) -> Bytes {
  let inner = Buffer()
  for p in protocols {
    let name = @utf8.encode(p)
    inner.write_byte((name.length() & 0xff).to_byte())
    inner.write_bytes(name[:])
  }
  let body = inner.to_bytes()
  let buf = Buffer()
  buf.write_byte(((body.length() >> 8) & 0xff).to_byte())
  buf.write_byte((body.length() & 0xff).to_byte())
  buf.write_bytes(body[:])
  buf.to_bytes()
}

///|
/// Decode an ALPN ProtocolNameList back into its protocol names, stopping at the declared
/// list length or a truncated entry.
pub fn tls_decode_alpn(view : BytesView) -> Array[String] {
  let names : Array[String] = []
  if view.length() < 2 {
    return names
  }
  let total = (view[0].to_int() << 8) | view[1].to_int()
  let end = 2 + total
  let mut off = 2
  while off < end && off < view.length() {
    let n = view[off].to_int()
    off = off + 1
    if off + n > view.length() {
      break
    }
    names.push(@utf8.decode_lossy(view[off:off + n]))
    off = off + n
  }
  names
}

///|
/// Build an ALPN extension carrying `protocols` — the offered list in a ClientHello, or the
/// single selected protocol in a server's reply.
pub fn tls_alpn_extension(protocols : Array[String]) -> TlsExtension {
  { ext_type: tls_ext_alpn, data: tls_encode_alpn(protocols), }
}

///|
/// The protocols a ClientHello's ALPN extension offers, in order (empty if it has none).
pub fn tls_client_hello_alpn(extensions : Array[TlsExtension]) -> Array[String] {
  match tls_find_extension(extensions, tls_ext_alpn) {
    Some(ext) => tls_decode_alpn(ext.data[:])
    None => []
  }
}

///|
/// The single protocol a server's ALPN extension selected, or `None` if there is none.
pub fn tls_selected_alpn(extensions : Array[TlsExtension]) -> String? {
  match tls_find_extension(extensions, tls_ext_alpn) {
    Some(ext) => {
      let names = tls_decode_alpn(ext.data[:])
      if names.length() > 0 {
        Some(names[0])
      } else {
        None
      }
    }
    None => None
  }
}