///|
fn @crypt_core.PdfEncryptionValues::file_key_for_recrypt_password(
  self : @crypt_core.PdfEncryptionValues,
  password : BytesView,
) -> @core.PdfBytes raise @core.PdfError {
  match self.file_key_for_user_password(password) {
    Some(key) => key
    None =>
      match self.file_key_for_owner_password(password) {
        Some(key) => key
        None => raise EncryptionRecryptFailed
      }
  }
}

///|
fn PdfDocument::pdf_crypt_prepare_plaintext_for_recrypt(
  self : PdfDocument,
) -> Unit {
  for number in self.object_numbers() {
    match self.objects.get_entry(number) {
      Some({ data: ObjectParsedAlreadyDecrypted(object), generation, }) =>
        self.objects.set_entry(number, {
          data: ObjectParsed(object),
          generation,
        })
      _ => ()
    }
  }
}

///|
fn PdfDocument::recrypt_arc4_with_file_key(
  self : PdfDocument,
  values : @crypt_core.PdfEncryptionValues,
  file_key : BytesView,
  key_length_bits : Int,
  revision : Int,
) -> PdfDocument raise @core.PdfError {
  let (document, _) = self.with_file_id(file_id=values.file_id)
  document.pdf_crypt_prepare_plaintext_for_recrypt()
  document.set_saved_encryption(None)
  let encrypted = document.arc4_crypt_parsed_objects(
    file_key,
    key_length_bits,
    !values.encrypt_metadata,
    0,
  )
  encrypted.set_saved_encryption(None)
  let encrypt_dictionary = pdf_crypt_saved_arc4_dictionary(
    values, key_length_bits, revision,
  )
  let encrypt_number = encrypted.add_object(encrypt_dictionary)
  encrypted.set_trailer_dict(
    encrypted
    .trailer_dict()
    .add_dict_entry(pdf_crypt_encrypt_name, PdfIndirect(encrypt_number)),
  )
  encrypted
}

///|
fn PdfDocument::recrypt_aesv2_with_file_key(
  self : PdfDocument,
  values : @crypt_core.PdfEncryptionValues,
  file_key : BytesView,
  iv_provider : (Int, Int, Int) -> @core.PdfBytes raise @core.PdfError,
) -> PdfDocument raise @core.PdfError {
  let (document, _) = self.with_file_id(file_id=values.file_id)
  document.pdf_crypt_prepare_plaintext_for_recrypt()
  document.set_saved_encryption(None)
  let encrypted = document.aesv2_encrypt_parsed_objects_with_iv_provider(
    file_key,
    128,
    iv_provider,
    !values.encrypt_metadata,
  )
  encrypted.set_saved_encryption(None)
  let encrypt_dictionary = pdf_crypt_saved_aesv2_dictionary(values)
  let encrypt_number = encrypted.add_object(encrypt_dictionary)
  encrypted.set_trailer_dict(
    encrypted
    .trailer_dict()
    .add_dict_entry(pdf_crypt_encrypt_name, PdfIndirect(encrypt_number)),
  )
  encrypted
}

///|
fn PdfDocument::recrypt_aesv3_with_file_key(
  self : PdfDocument,
  values : @crypt_core.PdfEncryptionValues,
  file_key : BytesView,
  iv_provider : (Int, Int, Int) -> @core.PdfBytes raise @core.PdfError,
) -> PdfDocument raise @core.PdfError {
  let (document, _) = self.with_file_id(file_id=values.file_id)
  document.pdf_crypt_prepare_plaintext_for_recrypt()
  document.set_saved_encryption(None)
  let encrypted = document.aesv3_encrypt_parsed_objects_with_iv_provider(
    file_key,
    iv_provider,
    !values.encrypt_metadata,
  )
  encrypted.set_saved_encryption(None)
  let encrypt_dictionary = pdf_crypt_saved_aesv3_dictionary(values)
  let encrypt_number = encrypted.add_object(encrypt_dictionary)
  encrypted.set_trailer_dict(
    encrypted
    .trailer_dict()
    .add_dict_entry(pdf_crypt_encrypt_name, PdfIndirect(encrypt_number)),
  )
  encrypted
}

///|
/// Re-encrypt a previously decrypted document using its saved ARC4 settings.
///
/// The document must have saved encryption state from a successful decrypt.
/// The supplied password is authenticated against that state before the
/// original encryption dictionary shape is restored.
pub fn PdfDocument::recrypt_with_password(
  self : PdfDocument,
  password : BytesView,
) -> PdfDocument raise @core.PdfError {
  let saved = match self.saved_encryption() {
    Some(saved) => saved
    None => raise EncryptionSavedStateExpected
  }
  let values = saved.values
  match values.crypt_type {
    PdfCryptARC4(key_length_bits, revision) if (
        key_length_bits == 40 || key_length_bits == 128
      ) &&
      (revision == 2 || revision == 3 || revision == 4) => {
      let file_key = values.file_key_for_recrypt_password(password)
      self.recrypt_arc4_with_file_key(
        values, file_key, key_length_bits, revision,
      )
    }
    _ => raise EncryptionRecryptUnsupported(values.crypt_type)
  }
}

///|
/// Re-encrypt a decrypted AESV2 document using its saved encryption settings.
///
/// `iv_provider` supplies fresh IVs for encrypted object data. The saved
/// encryption state determines passwords, permissions, and crypt-filter shape.
pub fn PdfDocument::recrypt_with_password_aesv2_with_iv_provider(
  self : PdfDocument,
  password : BytesView,
  iv_provider : (Int, Int, Int) -> @core.PdfBytes raise @core.PdfError,
) -> PdfDocument raise @core.PdfError {
  let saved = match self.saved_encryption() {
    Some(saved) => saved
    None => raise EncryptionSavedStateExpected
  }
  let values = saved.values
  match values.crypt_type {
    PdfCryptAESV2 => {
      let file_key = values.file_key_for_recrypt_password(password)
      self.recrypt_aesv2_with_file_key(values, file_key, iv_provider)
    }
    _ => raise EncryptionRecryptUnsupported(values.crypt_type)
  }
}

///|
/// Re-encrypt a decrypted AESV3 document using its saved encryption settings.
///
/// `iv_provider` supplies fresh IVs for encrypted object data. The saved
/// encryption state supplies the existing AESV3 password entries and file key
/// wrapping metadata.
pub fn PdfDocument::recrypt_with_password_aesv3_with_iv_provider(
  self : PdfDocument,
  password : BytesView,
  iv_provider : (Int, Int, Int) -> @core.PdfBytes raise @core.PdfError,
) -> PdfDocument raise @core.PdfError {
  let saved = match self.saved_encryption() {
    Some(saved) => saved
    None => raise EncryptionSavedStateExpected
  }
  let values = saved.values
  match values.crypt_type {
    PdfCryptAESV3(_) => {
      let file_key = values.file_key_for_recrypt_password(password)
      self.recrypt_aesv3_with_file_key(values, file_key, iv_provider)
    }
    _ => raise EncryptionRecryptUnsupported(values.crypt_type)
  }
}