///|
/// How an ACME request identifies its account key. Account creation uses the
/// full public JWK; later requests use the account URL (`kid`).
pub(all) enum AccountBinding {
  PublicJwk(String)
  KeyId(String)
} derive(Eq, Debug)

///|
/// A flattened-JWS request before the cryptographic signature is attached.
/// `signing_input` is the exact ASCII byte sequence passed to an ES256/RS256
/// signer. Keeping signing outside the protocol core prevents private keys
/// from entering diagnostic values or portable state.
pub(all) struct JwsDraft {
  protected_b64 : String
  payload_b64 : String
  signing_input : String
} derive(Eq, Debug)

///|
fn protected_header(
  algorithm : String,
  nonce : String,
  url : String,
  binding : AccountBinding,
) -> String raise AcmeError {
  if algorithm.length() == 0 {
    raise malformed("jws", "empty signing algorithm")
  }
  if nonce.length() == 0 {
    raise AcmeError::MissingNonce
  }
  if url.length() == 0 {
    raise malformed("jws", "empty request URL")
  }
  let common : Map[String, Json] = {
    "alg": Json::string(algorithm),
    "nonce": Json::string(nonce),
    "url": Json::string(url),
  }
  match binding {
    KeyId(kid) => {
      if kid.length() == 0 {
        raise malformed("jws", "empty account key id")
      }
      common["kid"] = Json::string(kid)
    }
    PublicJwk(jwk_text) => {
      let jwk = parse_json(jwk_text, "jws.jwk")
      match jwk {
        Object(_) => common["jwk"] = jwk
        _ => raise malformed("jws.jwk", "public JWK must be a JSON object")
      }
    }
  }
  @json.dumps(Json::object(common), sort=true)
}

///|
pub fn JwsDraft::new(
  algorithm~ : String,
  nonce~ : String,
  url~ : String,
  payload~ : String,
  binding~ : AccountBinding,
) -> JwsDraft raise AcmeError {
  let header_text = protected_header(algorithm, nonce, url, binding)
  let protected_b64 = base64url(@utf8.encode(header_text[:])[:])
  let payload_b64 = base64url(@utf8.encode(payload[:])[:])
  {
    protected_b64,
    payload_b64,
    signing_input: protected_b64 + "." + payload_b64,
  }
}

///|
/// Finish a flattened JWS object with the raw signature bytes returned by the
/// configured algorithm. ES256 adapters must supply the RFC 7515 `R || S`
/// representation, not an ASN.1 DER ECDSA signature.
pub fn JwsDraft::finish(
  self : JwsDraft,
  signature : BytesView,
) -> String raise AcmeError {
  if signature.length() == 0 {
    raise malformed("jws", "empty signature")
  }
  @json.dumps(
    Json::object({
      "payload": Json::string(self.payload_b64),
      "protected": Json::string(self.protected_b64),
      "signature": Json::string(base64url(signature)),
    }),
    sort=true,
  )
}

///|
/// ACME POST-as-GET is a signed request with an empty payload, distinct from a
/// JSON `null` payload.
pub fn JwsDraft::post_as_get(
  algorithm~ : String,
  nonce~ : String,
  url~ : String,
  kid~ : String,
) -> JwsDraft raise AcmeError {
  JwsDraft::new(
    algorithm~,
    nonce~,
    url~,
    payload="",
    binding=AccountBinding::KeyId(kid),
  )
}