///|
/// Credentials sent in the `Authorization` header. A call that sets that
/// header itself keeps its own.
pub(all) enum Auth {
  /// HTTP Basic: `username:password`, UTF-8 and base64-encoded.
  Basic(username~ : String, password~ : String)
  /// `Bearer `.
  Bearer(String)
  /// HTTP Digest (RFC 7616, and RFC 2617's challenges without qop): a 401
  /// with a `Digest` challenge from the request's origin is answered once
  /// (twice if the first answer's nonce was stale) by sending the request
  /// again, when its body can be; the nonce is reused, with a counted `nc`,
  /// for that origin's later calls. MD5, SHA-256 and their `-sess` forms,
  /// qop `auth`, and `auth-int` for a body in memory.
  Digest(username~ : String, password~ : String)
}

///|
/// Debug output redacts the password and the token.
pub extend Auth with @debug.Debug::{to_repr}

///|
pub impl @debug.Debug for Auth with fn to_repr(self) {
  let redacted = @debug.Repr::literal("")
  match self {
    Basic(username~, ..) =>
      @debug.Repr::ctor("Basic", [
        (Some("username"), @debug.Repr::Repr(username)),
        (Some("password"), redacted),
      ])
    Bearer(_) => @debug.Repr::ctor("Bearer", [(None, redacted)])
    Digest(username~, ..) =>
      @debug.Repr::ctor("Digest", [
        (Some("username"), @debug.Repr::Repr(username)),
        (Some("password"), redacted),
      ])
  }
}

///|
/// The step that authorizes requests that do not set `Authorization`
/// themselves. `random` draws Digest's client nonces.
fn auth_step(auth : Auth, random : () -> Double) -> Step {
  match auth {
    Basic(username~, password~) => header_step(basic_value(username, password))
    Bearer(token) => header_step("Bearer " + token)
    Digest(username~, password~) => digest_step(username, password, random)
  }
}

///|
/// The step that sets `Authorization: ` on requests without it.
fn header_step(value : String) -> Step {
  (request, next) => {
    guard !request.headers.contains("authorization") else {
      return next(request)
    }
    let headers = copy_headers(request.headers)
    headers.set("authorization", value)
    next({ ..request, headers, })
  }
}

///|
/// The `Authorization` value of HTTP Basic: `username:password`, UTF-8 and
/// base64-encoded.
fn basic_value(username : String, password : String) -> String {
  "Basic " + @base64.encode(@utf8.encode(username + ":" + password))
}

///|
/// `step` for requests to the origin of `url`; requests to any other origin,
/// such as a redirect elsewhere, pass by it.
fn same_origin_only(url : String, step : Step) -> Step {
  let origin = @http.Url::parse(url) catch { _ => return step }
  (request, next) => {
    if same_origin_as(origin, request.url) {
      step(request, next)
    } else {
      next(request)
    }
  }
}