///|
/// Encrypt a copy of the document with revision-4 128-bit ARC4.
///
/// This writes a crypt-filter dictionary using `/V2`. `encrypt_metadata=false`
/// follows the revision-4 no-metadata key derivation and stream policy.
pub fn PdfDocument::encrypt_128bit_r4(
  self : PdfDocument,
  user_password : BytesView,
  owner_password : BytesView,
  denied_permissions : ArrayView[@crypt_core.PdfPermission],
  encrypt_metadata? : Bool = true,
  file_id? : BytesView = [],
) -> PdfDocument raise @core.PdfError {
  let no_encrypt_metadata = !encrypt_metadata
  let (document, id) = self.with_file_id(file_id~)
  let permissions = @crypt_core.pdf_p_of_permissions(denied_permissions)
  let owner_entry = @crypt_core.pdf_make_owner_password_entry(
    4, owner_password, user_password, 128,
  )
  let user_entry = @crypt_core.pdf_make_user_password_entry(
    no_encrypt_metadata, user_password, 4, owner_entry, permissions, id, 128,
  )
  let file_key = @crypt_core.pdf_encryption_file_key(
    no_encrypt_metadata, user_password, 4, owner_entry, permissions, id, 128,
  )
  let encrypted = document.arc4_crypt_parsed_objects(
    file_key, 128, no_encrypt_metadata, 0,
  )
  let std_cf = pdf_crypt_std_cf_name
  let encrypt_dictionary : @syntax.PdfObject = PdfDictionary([
    (pdf_crypt_filter_name, PdfNameObject(pdf_crypt_standard_name)),
    (pdf_crypt_v_name, PdfInteger(4)),
    (
      pdf_crypt_cf_name,
      PdfDictionary([
        (
          std_cf,
          PdfDictionary([
            (pdf_crypt_length_name, PdfInteger(16)),
            (pdf_crypt_auth_event_name, PdfNameObject(pdf_crypt_doc_open_name)),
            (pdf_crypt_cfm_name, PdfNameObject(pdf_crypt_v2_name)),
          ]),
        ),
      ]),
    ),
    (pdf_crypt_encrypt_metadata_name, PdfBoolean(encrypt_metadata)),
    (pdf_crypt_length_name, PdfInteger(128)),
    (pdf_crypt_r_name, PdfInteger(4)),
    (pdf_crypt_o_name, PdfString(owner_entry)),
    (pdf_crypt_u_name, PdfString(user_entry)),
    (pdf_crypt_p_name, PdfInteger(permissions)),
    (pdf_crypt_strf_name, PdfNameObject(std_cf)),
    (pdf_crypt_stmf_name, PdfNameObject(std_cf)),
  ])
  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
}

///|
/// Encrypt a copy of the document with revision-4 AESV2.
///
/// `iv_provider` supplies per-object 16-byte CBC IVs by object number,
/// generation, and IV index. This makes tests reproducible while allowing
/// production callers to provide secure random IVs.
pub fn PdfDocument::encrypt_128bit_aesv2_with_iv_provider(
  self : PdfDocument,
  user_password : BytesView,
  owner_password : BytesView,
  denied_permissions : ArrayView[@crypt_core.PdfPermission],
  iv_provider : (Int, Int, Int) -> @core.PdfBytes raise @core.PdfError,
  encrypt_metadata? : Bool = true,
  file_id? : BytesView = [],
) -> PdfDocument raise @core.PdfError {
  let no_encrypt_metadata = !encrypt_metadata
  let (document, id) = self.with_file_id(file_id~)
  let permissions = @crypt_core.pdf_p_of_permissions(denied_permissions)
  let owner_entry = @crypt_core.pdf_make_owner_password_entry(
    4, owner_password, user_password, 128,
  )
  let user_entry = @crypt_core.pdf_make_user_password_entry(
    no_encrypt_metadata, user_password, 4, owner_entry, permissions, id, 128,
  )
  let file_key = @crypt_core.pdf_encryption_file_key(
    no_encrypt_metadata, user_password, 4, owner_entry, permissions, id, 128,
  )
  let encrypted = document.aesv2_encrypt_parsed_objects_with_iv_provider(
    file_key, 128, iv_provider, no_encrypt_metadata,
  )
  let std_cf = pdf_crypt_std_cf_name
  let encrypt_dictionary : @syntax.PdfObject = PdfDictionary([
    (pdf_crypt_filter_name, PdfNameObject(pdf_crypt_standard_name)),
    (pdf_crypt_v_name, PdfInteger(4)),
    (
      pdf_crypt_cf_name,
      PdfDictionary([
        (
          std_cf,
          PdfDictionary([
            (pdf_crypt_length_name, PdfInteger(16)),
            (pdf_crypt_auth_event_name, PdfNameObject(pdf_crypt_doc_open_name)),
            (pdf_crypt_cfm_name, PdfNameObject(pdf_crypt_aesv2_name)),
          ]),
        ),
      ]),
    ),
    (pdf_crypt_encrypt_metadata_name, PdfBoolean(encrypt_metadata)),
    (pdf_crypt_length_name, PdfInteger(128)),
    (pdf_crypt_r_name, PdfInteger(4)),
    (pdf_crypt_o_name, PdfString(owner_entry)),
    (pdf_crypt_u_name, PdfString(user_entry)),
    (pdf_crypt_p_name, PdfInteger(permissions)),
    (pdf_crypt_strf_name, PdfNameObject(std_cf)),
    (pdf_crypt_stmf_name, PdfNameObject(std_cf)),
  ])
  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
}