///|
/// The browser origin and socket endpoint of an application or development URL.
pub struct HttpUrl {
  origin : String
  host : String
  port : Int32
}

///|
/// Invalid input for an HTTP(S) application or development URL.
pub(all) suberror HttpUrlError {
  MissingScheme
  UnsupportedScheme
  MissingHost
  InvalidOrigin(reason~ : String)
  EmptyPort
  ZeroPort
}

///|
/// Parse an absolute HTTP(S) URL using browser host and port normalization.
pub fn parse_http_url(url : String) -> HttpUrl raise HttpUrlError {
  let (scheme, rest) = match url.split_once("://") {
    Some(parts) => parts
    None => raise MissingScheme
  }
  let default_port = match scheme.to_owned().to_lower() {
    "http" => 80
    "https" => 443
    _ => raise UnsupportedScheme
  }
  // Check Proton's input restrictions before the browser parser can normalize
  // away credentials, whitespace, backslashes or an empty port.
  let mut end = rest.length()
  for delimiter in ["/", "?", "#"] {
    match rest.find(delimiter) {
      Some(index) if index < end => end = index
      _ => ()
    }
  }
  let authority = rest[:end]
  if authority.is_empty() ||
    authority.has_prefix(":") ||
    authority == "[]" ||
    authority.has_prefix("[]:") {
    raise MissingHost
  }
  for ch in authority {
    let code = ch.to_int()
    if code <= 0x20 || code >= 0x7f || ch is ('@' | '"' | '\\') {
      raise InvalidOrigin(reason="URL has an invalid origin")
    }
  }
  if authority.has_suffix(":") {
    raise EmptyPort
  }
  let parsed = @url.Url::parse(url[:]) catch {
    _ => {
      let reason = if authority.has_prefix("[") {
        "URL has an invalid IPv6 origin"
      } else {
        "URL has an invalid origin"
      }
      raise InvalidOrigin(reason~)
    }
  }
  let port = match parsed.port {
    None => default_port
    Some(0) => raise ZeroPort
    Some(value) => value.to_int()
  }
  let host = match parsed.host {
    Some(IPv6(address)) => address.to_string()
    _ => parsed.hostname()
  }
  { origin: parsed.origin(), host, port, }
}