///|
/// Sign a JWT token with the specified HMAC algorithm.
///
/// Automatically selects the correct HMAC function based on the algorithm.
pub fn sign_with_alg(
  alg : Algorithm,
  header_json : String,
  claims_json : String,
  secret : Bytes,
) -> String {
  let header_b64 = base64url_encode_str(header_json)
  let claims_b64 = base64url_encode_str(claims_json)
  let signing_input = header_b64 + "." + claims_b64
  let signing_bytes = @utf8.encode(signing_input)
  let mac = match alg {
    HS256 => hmac_sha256(secret, signing_bytes)
    HS384 => hmac_sha384(secret, signing_bytes)
    HS512 => hmac_sha512(secret, signing_bytes)
  }
  let sig_b64 = base64url_encode(mac)
  signing_input + "." + sig_b64
}

///|
/// Sign HS384 with a JwtHeader.
pub fn sign_hs384(
  header : JwtHeader,
  claims_json : String,
  secret : Bytes,
) -> String {
  let h = header
  h.alg = HS384
  sign_with_alg(HS384, h.to_json().stringify(), claims_json, secret)
}

///|
/// Sign HS512 with a JwtHeader.
pub fn sign_hs512(
  header : JwtHeader,
  claims_json : String,
  secret : Bytes,
) -> String {
  let h = header
  h.alg = HS512
  sign_with_alg(HS512, h.to_json().stringify(), claims_json, secret)
}

///|
/// Verify a token and return the algorithm used.
///
/// This decodes the header to determine which HMAC variant was used,
/// then verifies with the corresponding function.
pub fn verify_with_alg(
  token : String,
  secret : Bytes,
) -> Result[Algorithm, JwtError] {
  let parts = token.split(".").to_array()
  if parts.length() != 3 {
    return Err(InvalidToken("token must have 3 parts"))
  }
  let header_b64 = parts[0].to_owned()
  let claims_b64 = parts[1].to_owned()
  let sig_b64 = parts[2].to_owned()
  // Parse header to determine algorithm
  let header_json = match parse_header_json(header_b64) {
    Ok(h) => h
    Err(e) => return Err(e)
  }
  let alg = match header_json {
    Object(o) =>
      match o["alg"] {
        String(s) =>
          match s {
            "HS256" => HS256
            "HS384" => HS384
            "HS512" => HS512
            other => return Err(InvalidAlgorithm(other))
          }
        _ => return Err(InvalidHeader("alg field missing or not string"))
      }
    _ => return Err(InvalidHeader("header is not an object"))
  }
  let signing_input = header_b64 + "." + claims_b64
  let signing_bytes = @utf8.encode(signing_input)
  let expected_mac = match alg {
    HS256 => hmac_sha256(secret, signing_bytes)
    HS384 => hmac_sha384(secret, signing_bytes)
    HS512 => hmac_sha512(secret, signing_bytes)
  }
  let provided_mac = match base64url_decode(sig_b64) {
    None => return Err(InvalidSignature)
    Some(b) => b
  }
  if expected_mac.length() != provided_mac.length() {
    return Err(InvalidSignature)
  }
  let mut diff = 0
  for i in 0.. JwtHeader {
  { alg: HS384, typ: "JWT", kid: None }
}

///|
/// Create a JwtHeader for HS512.
pub fn JwtHeader::hs512() -> JwtHeader {
  { alg: HS512, typ: "JWT", kid: None }
}