///|
fn PdfDocument::encryption_object_number(self : PdfDocument) -> Int {
  match self.trailer_dict().lookup_immediate(pdf_crypt_encrypt_name) {
    Some(PdfIndirect(number)) => number
    _ => 0
  }
}

///|
fn pdf_crypt_file_id_object(file_id : BytesView) -> @syntax.PdfObject {
  let owned = file_id.to_owned()
  PdfArray([PdfString(owned), PdfString(owned)])
}

///|
fn PdfDocument::with_file_id(
  self : PdfDocument,
  file_id? : BytesView = [],
) -> (PdfDocument, @core.PdfBytes) raise @core.PdfError {
  match self.lookup_direct(pdf_crypt_id_name, self.trailer_dict()) {
    Some(PdfArray([PdfString(existing), _])) => (self.copy(), existing)
    _ => {
      let id = if file_id.length() > 0 {
        file_id.to_owned()
      } else {
        pdf_generate_current_id_bytes("")
      }
      let output = self.copy()
      output.set_trailer_dict(
        output
        .trailer_dict()
        .add_dict_entry(pdf_crypt_id_name, pdf_crypt_file_id_object(id)),
      )
      (output, id)
    }
  }
}

///|
fn pdf_crypt_deterministic_seed32(
  seed : BytesView,
  purpose : BytesView,
) -> @core.PdfBytes {
  @crypt_core.pdf_sha256_digest(
    pdf_crypt_concat_bytes([
      b"pdflite-deterministic-aes-provider", seed, purpose,
    ]),
  )
}

///|
fn pdf_crypt_deterministic_bytes(
  seed : BytesView,
  purpose : BytesView,
  length : Int,
) -> @core.PdfBytes {
  if length <= 0 {
    Bytes::new(0)
  } else {
    let rand = @random.Rand::chacha8(
      seed=pdf_crypt_deterministic_seed32(seed, purpose),
    )
    Bytes::makei(length, _ => rand.uint(limit=256U).to_byte())
  }
}

///|
#warnings("-unused_error_type")
fn pdf_deterministic_aes_iv_for_seed(
  seed : BytesView,
  object_number : Int,
  generation : Int,
  index : Int,
) -> @core.PdfBytes raise @core.PdfError {
  let purpose = @ascii.encode(
    "aes-iv:" +
    object_number.to_string() +
    ":" +
    generation.to_string() +
    ":" +
    index.to_string(),
  )
  pdf_crypt_deterministic_bytes(seed, purpose, 16)
}

///|
/// Build a deterministic AES IV provider from a seed.
///
/// The returned callback derives 16-byte IVs from the seed, object number,
/// generation, and per-object IV index. It is intended for reproducible tests
/// and fixtures; production encryption should use a cryptographically secure
/// random provider.
pub fn pdf_deterministic_aes_iv_provider_from_seed(
  seed : BytesView,
) -> (Int, Int, Int) -> @core.PdfBytes raise @core.PdfError {
  let owned_seed = seed.to_owned()
  fn(object_number, generation, index) raise @core.PdfError {
    pdf_deterministic_aes_iv_for_seed(
      owned_seed, object_number, generation, index,
    )
  }
}

///|
#warnings("-unused_error_type")
fn pdf_deterministic_aesv3_random_for_seed(
  seed : BytesView,
  field : @crypt_core.PdfAesV3RandomField,
  length : Int,
) -> @core.PdfBytes raise @core.PdfError {
  pdf_crypt_deterministic_bytes(
    seed,
    pdf_aesv3_random_field_key(field).view(),
    length,
  )
}

///|
/// Build a deterministic AESV3 random-field provider from a seed.
///
/// The provider derives field-specific bytes for file keys, salts, and
/// permissions padding. It is useful for stable test fixtures, not for
/// production encryption.
pub fn pdf_deterministic_aesv3_random_provider_from_seed(
  seed : BytesView,
) -> (@crypt_core.PdfAesV3RandomField, Int) -> @core.PdfBytes raise @core.PdfError {
  let owned_seed = seed.to_owned()
  fn(field, length) raise @core.PdfError {
    pdf_deterministic_aesv3_random_for_seed(owned_seed, field, length)
  }
}