///|
/// gopenpgp-style entry point: `pgp().encryption()...`.
pub fn pgp() -> Pgp {
  Pgp::{ }
}

///|
/// Top-level facade for the builder API. Stateless — every operation is
/// configured through one of the three builders below.
pub struct Pgp {}

///|
/// Builder for encryption. Fields are private; instances are created via
/// `Pgp::encryption` and evolve through the chain methods.
pub struct EncryptionBuilder {
  priv passwords : Array[String]
  priv recipients : Array[PublicKey]
  priv signer : PrivateKey?
  priv file_name : String
  priv compress : @compress.CompressedAlgo?
}

///|
/// Start building an encryption (password or public-key recipients).
pub fn Pgp::encryption(_self : Pgp) -> EncryptionBuilder {
  EncryptionBuilder::{
    passwords: [],
    recipients: [],
    signer: None,
    file_name: "message.txt",
    compress: None,
  }
}

///|
/// Compress the payload before encryption (ZIP or ZLIB; M4). Off by
/// default — the historical behavior of this library — and only honored
/// by the encryption entry points.
pub fn EncryptionBuilder::compress(
  self : EncryptionBuilder,
  algo : @compress.CompressedAlgo,
) -> EncryptionBuilder {
  { ..self, compress: Some(algo), }
}

///|
pub fn EncryptionBuilder::password(
  self : EncryptionBuilder,
  pass : String,
) -> EncryptionBuilder {
  let passwords = self.passwords.copy()
  passwords.push(pass)
  { ..self, passwords, }
}

///|
pub fn EncryptionBuilder::recipient(
  self : EncryptionBuilder,
  key : PublicKey,
) -> EncryptionBuilder {
  let recipients = self.recipients.copy()
  recipients.push(key)
  { ..self, recipients, }
}

///|
pub fn EncryptionBuilder::sign_with(
  self : EncryptionBuilder,
  key : PrivateKey,
) -> EncryptionBuilder {
  { ..self, signer: Some(key), }
}

///|
pub fn EncryptionBuilder::file_name(
  self : EncryptionBuilder,
  name : String,
) -> EncryptionBuilder {
  { ..self, file_name: name, }
}

///|
/// Finish the builder: at least one password or recipient is required.
/// Returns a closure that encrypts arbitrary payloads with this setup.
pub fn EncryptionBuilder::finish(
  self : EncryptionBuilder,
) -> (BytesView) -> String raise {
  let passwords = self.passwords
  let recipients = self.recipients
  let signer = self.signer
  let file_name = self.file_name
  let compress = self.compress
  fn(data : BytesView) -> String raise {
    if !recipients.is_empty() {
      // Public-key path; wrap an inline signature when a signer is set.
      // `file_name` applies to both branches (N9).
      let armored = match signer {
        Some(sk) =>
          sign_and_encrypt(data, sk, recipients, file_name~, compress?)
        None => encrypt_to_recipients(data, recipients, file_name~, compress?)
      }
      return armored
    }
    if !passwords.is_empty() {
      // Every supplied passphrase gets its own SKESK over one shared
      // session key (N4) instead of silently dropping all but the first.
      return seal_skesks_with_passwords(data, passwords, file_name~, compress?)
    }
    @error.fail("api: encryption requires a password or a recipient")
  }
}

///|
/// Builder for decryption.
pub struct DecryptionBuilder {
  priv passwords : Array[String]
  priv private_keys : Array[PrivateKey]
  priv verifiers : Array[PublicKey]
  priv max_decompressed : Int32
}

///|
/// Start building a decryption (private keys and optional verifiers).
pub fn Pgp::decryption(_self : Pgp) -> DecryptionBuilder {
  DecryptionBuilder::{
    passwords: [],
    private_keys: [],
    verifiers: [],
    max_decompressed: DEFAULT_MAX_DECOMPRESSED_BYTES,
  }
}

///|
/// Override the decompressed-payload ceiling (M4; default 512 MiB).
pub fn DecryptionBuilder::max_decompressed(
  self : DecryptionBuilder,
  limit : Int32,
) -> DecryptionBuilder {
  { ..self, max_decompressed: limit, }
}

///|
pub fn DecryptionBuilder::password(
  self : DecryptionBuilder,
  pass : String,
) -> DecryptionBuilder {
  let passwords = self.passwords.copy()
  passwords.push(pass)
  { ..self, passwords, }
}

///|
pub fn DecryptionBuilder::decryption_key(
  self : DecryptionBuilder,
  key : PrivateKey,
) -> DecryptionBuilder {
  let private_keys = self.private_keys.copy()
  private_keys.push(key)
  { ..self, private_keys, }
}

///|
pub fn DecryptionBuilder::verification_key(
  self : DecryptionBuilder,
  key : PublicKey,
) -> DecryptionBuilder {
  let verifiers = self.verifiers.copy()
  verifiers.push(key)
  { ..self, verifiers, }
}

///|
/// Outcome of a combined decrypt+verify: data plus signature status.
pub(all) enum DecryptResult {
  /// Data decrypted; the message carried no verifiable signature.
  Data(Bytes)
  /// Data decrypted and the inline signature verified.
  Verified(Bytes)
  /// Data decrypted but the inline signature failed.
  SignatureError(Bytes)
} derive(Debug)

///|
pub fn DecryptionBuilder::finish(
  self : DecryptionBuilder,
) -> (String) -> DecryptResult raise {
  let passwords = self.passwords
  let private_keys = self.private_keys
  let verifiers = self.verifiers
  let max_decompressed = self.max_decompressed
  fn(message : String) -> DecryptResult raise {
    if !private_keys.is_empty() {
      let (d, status) = decrypt_and_verify_list(
        message, private_keys, verifiers, max_decompressed,
      )
      return match status {
        Ok => Verified(d)
        Failed => SignatureError(d)
        // A valid-but-rejected signature (revoked or expired key) is still
        // a signature error, not a missing signature.
        KeyRevoked | Expired => SignatureError(d)
        // No verifier matched, or the message carried no signature.
        NoVerifier | NotSigned => Data(d)
      }
    }
    if !passwords.is_empty() {
      // Try each passphrase in turn (N4): the message may have been
      // sealed to several and the matching SKESK need not be first.
      let mut last_error : String? = None
      for pass in passwords {
        let data : Bytes? = Some(
          decrypt_with_password(message, pass, max_decompressed~),
        ) catch {
          e => {
            last_error = Some(e.to_string())
            None
          }
        }
        if data is Some(d) {
          return Data(d)
        }
      }
      match last_error {
        Some(msg) => @error.fail(msg)
        None => @error.fail("api: decryption failed")
      }
    }
    @error.fail("api: decryption requires a password or a private key")
  }
}

///|
/// decrypt_and_verify over a list of candidate keys: every key is tried
/// until one decrypts, and — when verifiers were supplied — every
/// verifier is consulted on that successful decryption, keeping the
/// best-ranked status (N5: verifiers used to be silently dropped the
/// moment more than one key was given).
fn decrypt_and_verify_list(
  message : String,
  keys : Array[PrivateKey],
  verifiers : Array[PublicKey],
  max_decompressed : Int32,
) -> (Bytes, VerifyStatus) raise {
  let mut last_error : String? = None
  for key in keys {
    if verifiers.is_empty() {
      let pair : (Bytes, VerifyStatus)? = Some(
        decrypt_and_verify(message, [key], None, max_decompressed~),
      ) catch {
        e => {
          last_error = Some(e.to_string())
          None
        }
      }
      if pair is Some(result) {
        return result
      }
    } else {
      let mut best : (Bytes, VerifyStatus)? = None
      for v in verifiers {
        let pair : (Bytes, VerifyStatus)? = Some(
          decrypt_and_verify(message, [key], Some(v), max_decompressed~),
        ) catch {
          e => {
            last_error = Some(e.to_string())
            None
          }
        }
        if pair is Some((d, s)) {
          let take = match best {
            Some((_, cur)) => status_rank(s) > status_rank(cur)
            None => true
          }
          if take {
            best = Some((d, s))
          }
        }
      }
      if best is Some(result) {
        return result
      }
    }
  }
  match last_error {
    Some(msg) => @error.fail(msg)
    None => @error.fail("api: decryption failed")
  }
}

///|
/// Ranking across candidate verifiers (gopenpgp's
/// prioritizeSignatureErrors): a verified signature outranks everything;
/// among failures, verification rejections outweigh missing signatures.
fn status_rank(status : VerifyStatus) -> Int32 {
  match status {
    Ok => 4
    Failed | KeyRevoked | Expired => 3
    NoVerifier => 2
    NotSigned => 1
  }
}

///|
/// Builder for key generation.
pub struct KeyGenBuilder {
  priv user_id : String
  priv bits : Int32
}

///|
/// Start building a key generation (RSA by default; Ed25519 via
/// `generate_ed25519_key`).
pub fn Pgp::key_generation(_self : Pgp) -> KeyGenBuilder {
  KeyGenBuilder::{ user_id: "", bits: 3072, }
}

///|
pub fn KeyGenBuilder::add_user_id(
  self : KeyGenBuilder,
  user_id : String,
) -> KeyGenBuilder {
  { ..self, user_id, }
}

///|
pub fn KeyGenBuilder::rsa_bits(
  self : KeyGenBuilder,
  bits : Int32,
) -> KeyGenBuilder {
  { ..self, bits, }
}

///|
pub fn KeyGenBuilder::generate(self : KeyGenBuilder) -> PrivateKey raise {
  guard self.user_id != "" else {
    @error.fail("api: key generation needs a user id")
  }
  generate_key(self.user_id, bits=self.bits)
}

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