///|
/// Why a HEADERS frame does not map onto an `HttpScope` field-by-field: an
/// HTTP/2 (RFC 7540 §8.1.2.3) or HTTP/3 request carries its request line as four
/// *pseudo-headers* — `:method`, `:scheme`, `:authority`, `:path` — interleaved
/// with the ordinary fields, and ASGI reserves no slot for them in
/// `scope["headers"]`. A conforming server must consume the pseudo-headers into
/// the scope's typed fields and hand the application only the ordinary headers,
/// with `host` synthesised from `:authority`. `moonasgi` owns that lowering so
/// every h2/h2c/h3 transport in the suite (mooncat) produces the same scope from
/// the same frame, and an app never sees a `:`-prefixed header.
///
/// A malformed pseudo-header set — a missing required one, a duplicate, an
/// unknown `:foo`, or a pseudo-header after an ordinary field — is rejected with
/// the exact `Http2HeaderError`, matching RFC 7540's "malformed request"
/// treatment, so the transport can answer `RST_STREAM(PROTOCOL_ERROR)` instead
/// of forwarding a bad scope.
pub(all) enum Http2HeaderError {
  // No `:method` pseudo-header was present.
  MissingMethod
  // No `:scheme` pseudo-header was present.
  MissingScheme
  // No `:path` pseudo-header was present.
  MissingPath
  // `:path` was present but empty (a normal request must name a target; RFC
  // 7540 §8.1.2.3 allows an empty `:path` only for `OPTIONS *`, handled by the
  // transport, not lowered to an ASGI scope).
  EmptyPath
  // The named pseudo-header appeared more than once.
  DuplicatePseudoHeader(String)
  // A `:`-prefixed header that is not one of the four request pseudo-headers.
  UnknownPseudoHeader(String)
  // A pseudo-header followed an ordinary header — RFC 7540 requires all
  // pseudo-headers to precede the regular fields.
  PseudoHeaderAfterRegular(String)
} derive(Eq)

///|
/// Lower an HTTP/2 (or HTTP/3) request's HEADERS block — pseudo-headers and
/// ordinary fields in wire order — into an `HttpScope`, the way a conforming
/// ASGI server consumes a frame. `:method`, `:scheme` and `:path` become the
/// scope's `http_method` / `scheme` / `path` (with the query string split off
/// `:path`); the returned `headers` are the ordinary fields only, with `host`
/// synthesised at the front from `:authority` (replacing any `host` the peer
/// also sent, per RFC 7540 §8.1.2.3). `http_version` defaults to `"2"`; pass
/// `"3"` for an HTTP/3 transport, which shares this pseudo-header contract.
///
/// The remaining scope fields (`root_path`, `client`, `server`, `extensions`,
/// `asgi`, `state`) are supplied by the server exactly as for `HttpScope::new`.
/// Returns `Err` naming the first violation for a malformed pseudo-header set,
/// so the SEAM never hands a framework a scope with a `:`-prefixed header or a
/// missing request-line field.
pub fn HttpScope::from_h2_headers(
  headers : Array[(String, String)],
  http_version? : String = "2",
  root_path? : String = "",
  client? : (String, Int)? = None,
  server? : (String, Int?)? = None,
  extensions? : Extensions = Extensions::none(),
  asgi? : AsgiVersion = AsgiVersion::http(),
  state? : Map[String, Json] = Map([]),
) -> Result[HttpScope, Http2HeaderError] {
  let mut meth : String? = None
  let mut scheme : String? = None
  let mut authority : String? = None
  let mut target : String? = None
  let regular : Array[(String, String)] = []
  let mut seen_regular = false
  for pair in headers {
    let name = pair.0
    if name.length() > 0 && name[0].to_int() == 58 {
      if seen_regular {
        return Err(PseudoHeaderAfterRegular(name))
      }
      if name == ":method" {
        if meth is Some(_) {
          return Err(DuplicatePseudoHeader(name))
        }
        meth = Some(pair.1)
      } else if name == ":scheme" {
        if scheme is Some(_) {
          return Err(DuplicatePseudoHeader(name))
        }
        scheme = Some(pair.1)
      } else if name == ":authority" {
        if authority is Some(_) {
          return Err(DuplicatePseudoHeader(name))
        }
        authority = Some(pair.1)
      } else if name == ":path" {
        if target is Some(_) {
          return Err(DuplicatePseudoHeader(name))
        }
        target = Some(pair.1)
      } else {
        return Err(UnknownPseudoHeader(name))
      }
    } else {
      seen_regular = true
      regular.push(pair)
    }
  }
  let http_method = match meth {
    Some(m) => m
    None => return Err(MissingMethod)
  }
  let scheme = match scheme {
    Some(s) => s
    None => return Err(MissingScheme)
  }
  let raw_target = match target {
    Some(t) => t
    None => return Err(MissingPath)
  }
  if raw_target.length() == 0 {
    return Err(EmptyPath)
  }
  let out_headers : Array[(String, String)] = []
  match authority {
    Some(a) => {
      out_headers.push(("host", a))
      for pair in regular {
        if pair.0 != "host" {
          out_headers.push(pair)
        }
      }
    }
    None => out_headers.append(regular)
  }
  let (path, query) = split_target(raw_target)
  Ok(
    HttpScope::new(
      http_method~,
      path~,
      http_version~,
      scheme~,
      raw_path=@utf8.encode(path),
      query_string=@utf8.encode(query),
      root_path~,
      headers=out_headers,
      client~,
      server~,
      asgi~,
      extensions~,
      state~,
    ),
  )
}