// The authorization request the browser (or a headless stand-in) is sent to,
// and the redirect that comes back with a code.

///|
/// The state parameter of an authorization request: a value the client makes up
/// and checks on the way back, so a redirect it did not cause is not taken.
pub fn new_state() -> String raise Refused {
  let bytes = FixedArray::make(16, b'\x00')
  let rng = @auto.rng() catch {
    _ => raise Refused(message="no entropy to draw a state from")
  }
  rng.fill(bytes)
  @base64.encode(Bytes::from_array(bytes)[:], kind=Url, padding=false)
}

///|
/// The URL to send the user to: `response_type=code`, the client, the redirect,
/// the state, the S256 challenge, and the resource indicator RFC 8707 asks for.
pub fn authorize_url(
  server : Server,
  registration : Registration,
  redirect_uri : String,
  state : String,
  pkce : Pkce,
  scope? : String,
  resource? : String,
) -> String raise Refused {
  guard server.serves_authorization_code() else {
    raise Refused(
      message="the authorization server does not offer the code flow with S256",
    )
  }
  guard server.authorization_endpoint is Some(endpoint) else {
    raise Refused(
      message="the authorization server names no authorization endpoint",
    )
  }
  with_query(
    endpoint,
    [
      ("response_type", "code"),
      ("client_id", registration.client_id),
      ("redirect_uri", redirect_uri),
      ("state", state),
      ("code_challenge", pkce.challenge),
      ("code_challenge_method", "S256"),
      ..match scope {
        Some(scope) => [("scope", scope)]
        None => []
      },
      ..match resource {
        Some(resource) => [("resource", resource)]
        None => []
      },
    ],
  )
}

///|
/// The code and state a redirect carried, or why there is none.
#derive(Debug, Eq)
pub(all) struct Redirect {
  code : String
  state : String?
}

///|
/// Read a redirect back: the `code`, the `state` to compare with the one sent,
/// and an `error` if the server refused instead.
pub fn Redirect::parse(url : String) -> Redirect raise Refused {
  let query = query_of(url)
  match query_value(query, "error") {
    Some(error) => {
      let why = match query_value(query, "error_description") {
        Some(why) => ": " + why
        None => ""
      }
      raise Refused(message="the authorization server refused: \{error}\{why}")
    }
    None => ()
  }
  match query_value(query, "code") {
    Some(code) => { code, state: query_value(query, "state"), }
    None => raise Refused(message="the redirect carried no code")
  }
}

///|
pub extend Redirect with Debug::{to_repr}

///|
pub extend Redirect with Eq::{not_equal, equal}