///|
/// Hash algorithms used by OpenPGP, identified by their RFC 9580 ยง9.5
/// algorithm numbers. This is the PGP-level identity; concrete digest
/// computation is delegated to `moonbitlang/x/crypto` inside this package
/// only, so the hash backend can be swapped without touching callers.
pub(all) enum HashId {
  Md5
  Sha1
  Ripemd160
  Sha224
  Sha256
  Sha384
  Sha512
} derive(Eq, Debug)

///|
pub fn HashId::number(self : HashId) -> Int32 {
  match self {
    Md5 => 1
    Sha1 => 2
    Ripemd160 => 3
    Sha224 => 11
    Sha256 => 8
    Sha384 => 9
    Sha512 => 10
  }
}

///|
pub fn hash_id_of_number(n : Int32) -> HashId? {
  match n {
    1 => Some(Md5)
    2 => Some(Sha1)
    3 => Some(Ripemd160)
    8 => Some(Sha256)
    9 => Some(Sha384)
    10 => Some(Sha512)
    11 => Some(Sha224)
    _ => None
  }
}

///|
/// Size of the digest output in bytes.
pub fn HashId::digest_size(self : HashId) -> Int32 {
  match self {
    Md5 => 16
    Sha1 => 20
    Ripemd160 => 20
    Sha224 => 28
    Sha256 => 32
    Sha384 => 48
    Sha512 => 64
  }
}

///|
/// Digest function block size in bytes (as used by HMAC, RFC 2104).
pub fn HashId::block_size(self : HashId) -> Int32 {
  match self {
    Sha384 | Sha512 => 128
    _ => 64
  }
}

///|
/// Digest a single buffer.
pub fn HashId::digest(self : HashId, data : BytesView) -> Bytes {
  match self {
    Md5 => Bytes::from_iter(@xcrypto.md5(data).iter())
    Sha1 => Bytes::from_iter(@xcrypto.sha1(data).iter())
    Ripemd160 => Bytes::from_iter(@xcrypto.ripemd160(data).iter())
    Sha224 => Bytes::from_iter(@xcrypto.sha224(data).iter())
    Sha256 => Bytes::from_iter(@xcrypto.sha256(data).iter())
    Sha384 => Bytes::from_iter(@xcrypto.sha384(data).iter())
    Sha512 => Bytes::from_iter(@xcrypto.sha512(data).iter())
  }
}

///|
/// HMAC (RFC 2104) over a single buffer, using this hash algorithm.
pub fn HashId::hmac(self : HashId, key : BytesView, data : BytesView) -> Bytes {
  let bs = self.block_size()
  let k : Bytes = if key.length() > bs {
    self.digest(key)
  } else {
    key.to_owned()
  }
  let kp = pad_right(k, bs)
  let ipad = Bytes::makei(bs, i => kp[i] ^ b'\x36')
  let opad = Bytes::makei(bs, i => kp[i] ^ b'\x5c')
  let inner = self.digest(ipad.add(data.to_owned()))
  self.digest(opad.add(inner))
}

///|
/// DER DigestInfo prefix for this hash (PKCS#1 v1.5 signatures).
pub fn HashId::der_prefix(self : HashId) -> Bytes {
  from_hex_unchecked(
    match self {
      Md5 => "3020300c06082a864886f70d020505000410"
      Sha1 => "3021300906052b0e03021a05000414"
      Ripemd160 => "3021300906052b2403020105000414"
      Sha224 => "302d300d06096086480165030402040500041c"
      Sha256 => "3031300d060960864801650304020105000420"
      Sha384 => "3041300d060960864801650304020205000430"
      Sha512 => "3051300d060960864801650304020305000440"
    },
  )
}

///|
/// Hex decode of a compile-time constant; invalid constants abort.
fn from_hex_unchecked(s : String) -> Bytes {
  match hex_decode_const(s) {
    Some(b) => b
    None => abort("primitives: invalid constant hex string")
  }
}

///|
fn hex_decode_const(s : String) -> Bytes? {
  Some(@hex.decode(s) catch { _ => return None })
}

///|
pub extend HashId with @moonbitlang/core/debug.Debug::{to_repr}

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