///|
/// Convert a query map into wire-form query parameters: names and values
/// RFC 3986 component-encoded, so delimiter characters cannot alter the
/// parameter structure. The SigV4 signer sorts these into canonical order —
/// the form the signature covers — and `canonical_query_string` renders the
/// wire representation.
fn query_params(query : Map[String, String]) -> Array[(String, String?)] {
  query
  .to_array()
  .map(fn(pair) {
    (
      @sigv4.encode_query_component(pair.0),
      Some(@sigv4.encode_query_component(pair.1)),
    )
  })
}

///|
/// The bare service endpoint (no bucket component): scheme and host.
fn service_endpoint_parts(config : Config) -> (String, String) {
  let endpoint = match config.endpoint {
    Some(value) => value.trim_end(chars="/").to_owned()
    None => "https://s3.\{config.region}.amazonaws.com"
  }
  endpoint_parts(endpoint)
}

///|
fn endpoint_origin_and_host(
  config : Config,
  bucket : String,
) -> (String, String) {
  let (scheme, endpoint_host) = service_endpoint_parts(config)
  match config.endpoint_style {
    Path => ("\{scheme}://\{endpoint_host}", endpoint_host)
    VirtualHost => {
      let origin_host = "\{bucket}.\{endpoint_host}"
      ("\{scheme}://\{origin_host}", origin_host)
    }
  }
}

///|
/// Split an endpoint into scheme and `host[:port]`. Endpoints without a
/// scheme default to `https`.
fn endpoint_parts(endpoint : String) -> (String, String) {
  match endpoint.find("://") {
    None => ("https", endpoint)
    Some(_) =>
      match @uri.parse_uri_reference(endpoint) {
        Ok(uri) if uri.authority is Some(authority) =>
          (uri.scheme, @uri.authority_to_string(authority))
        _ => ("https", endpoint)
      }
  }
}

///|
/// Percent-encode the object key (and bucket, in path style) into the
/// request path: unreserved characters survive, `/` separators pass
/// through, non-ASCII characters encode as UTF-8.
fn canonical_object_path(
  config : Config,
  bucket : String,
  key : String,
) -> String {
  let object = if key == "" { "/" } else { "/\{key}" }
  match config.endpoint_style {
    VirtualHost => @sigv4.percent_encode_path(object)
    Path =>
      "/\{@sigv4.percent_encode_path(bucket)}\{@sigv4.percent_encode_path(object)}"
  }
}

///|
fn append_query(path : String, query : String) -> String {
  if query == "" {
    path
  } else {
    "\{path}?\{query}"
  }
}