// Mapping an HTTP message onto an HTTP/3 request or response stream (RFC 9114 §4.1). A
// message is a HEADERS frame carrying a QPACK-encoded field section, followed by zero or
// more DATA frames carrying the body, and optionally a trailing HEADERS frame for trailers.
// Requests and responses differ only in their pseudo-headers — :method/:scheme/:authority/
// :path for a request, :status for a response — which are ordinary field lines that precede
// the regular headers (§4.3.1). This ties the QPACK field-section codec and the HTTP/3 frame
// codec into the messages a connection actually exchanges; only the QUIC stream carrying
// these bytes is left to the async transport.

///|
/// Encode a message body as an HTTP/3 request/response stream: a HEADERS frame with the
/// QPACK-encoded `headers` (static table only), then a DATA frame when `body` is non-empty.
pub fn http3_encode_message(
  headers : Array[(String, String)],
  body : Bytes,
) -> Bytes {
  let buf = Buffer()
  buf.write_bytes(
    http3_frame_encode(Headers(qpack_encode_field_section(headers))),
  )
  if body.length() > 0 {
    buf.write_bytes(http3_frame_encode(Data(body)))
  }
  buf.to_bytes()
}

///|
/// Decode an HTTP/3 request/response stream back into its header list and body: the first
/// HEADERS frame gives the headers, the DATA frames concatenate into the body, and a second
/// HEADERS frame (trailers) and unknown frames are ignored (RFC 9114 §4.1). Raises if there
/// is no HEADERS frame or a frame is malformed.
pub fn http3_decode_message(
  bytes : Bytes,
) -> (Array[(String, String)], Bytes) raise Http3FrameError {
  let (frames, _) = http3_frame_decode_all(bytes)
  let mut headers : Array[(String, String)]? = None
  let body = Buffer()
  for frame in frames {
    match frame {
      Headers(field_section) =>
        match headers {
          None =>
            headers = Some(
              qpack_decode_field_section(field_section) catch {
                _ => raise Http3FrameError("malformed QPACK field section")
              },
            )
          Some(_) => () // trailers
        }
      Data(chunk) => body.write_bytes(chunk)
      _ => () // reserved, greased, or control frames on a request stream
    }
  }
  match headers {
    Some(h) => (h, body.to_bytes())
    None => raise Http3FrameError("HTTP/3 message has no HEADERS frame")
  }
}

///|
/// Encode an HTTP/3 request: the `:method`, `:scheme`, `:authority`, and `:path` pseudo-
/// headers (RFC 9114 §4.3.1), then `headers`, then the body.
pub fn http3_request(
  meth : String,
  scheme : String,
  authority : String,
  path : String,
  headers : Array[(String, String)],
  body : Bytes,
) -> Bytes {
  let all : Array[(String, String)] = [
    (":method", meth),
    (":scheme", scheme),
    (":authority", authority),
    (":path", path),
  ]
  for h in headers {
    all.push(h)
  }
  http3_encode_message(all, body)
}

///|
/// Encode an HTTP/3 response: the `:status` pseudo-header (RFC 9114 §4.3.2), then `headers`,
/// then the body.
pub fn http3_response(
  status : String,
  headers : Array[(String, String)],
  body : Bytes,
) -> Bytes {
  let all : Array[(String, String)] = [(":status", status)]
  for h in headers {
    all.push(h)
  }
  http3_encode_message(all, body)
}