///|
fn pdf_metadata_stream(data : BytesView) -> @syntax.PdfObject {
  @syntax.pdf_stream(
    PdfDictionary([
      (pdf_metadata_length(), PdfInteger(data.length())),
      (pdf_metadata_type(), PdfNameObject(pdf_metadata_metadata())),
      (pdf_metadata_subtype(), PdfNameObject(pdf_metadata_xml())),
    ]),
    StreamGot(data.to_owned()),
  )
}

///|
/// Set the catalog metadata XML stream from in-memory bytes.
pub fn PdfDocument::set_metadata_from_bytes(
  self : PdfDocument,
  data : BytesView,
  keep_version? : Bool = false,
) -> PdfDocument raise @core.PdfError {
  let document = self.copy()
  let metadata_number = document.add_object(pdf_metadata_stream(data))
  document.pdf_metadata_update_catalog(fn(catalog) raise @core.PdfError {
    catalog.add_dict_entry(
      pdf_metadata_metadata(),
      PdfIndirect(metadata_number),
    )
  })
  document.pdf_metadata_bump_minor(4, keep_version)
  document
}

///|
/// Set the XMP `/Metadata` stream's `xmp:MetadataDate` field.
pub fn PdfDocument::set_metadata_date(
  self : PdfDocument,
  date : String,
) -> PdfDocument raise @core.PdfError {
  match self.metadata() {
    None => self
    Some(metadata) => {
      let value = if date == "now" {
        pdf_metadata_xmp_date(pdf_metadata_expand_date(date))
      } else {
        date
      }
      let updated = pdf_metadata_rewrite_xmp_field(
        metadata, @metadata.pdf_metadata_xmp_namespace, "xmp", "MetadataDate", value,
      )
      self.set_metadata_from_bytes(updated, keep_version=true)
    }
  }
}

///|
/// Create a fresh XMP metadata stream from the document `/Info` dictionary.
pub fn PdfDocument::create_metadata(
  self : PdfDocument,
) -> PdfDocument raise @core.PdfError {
  let mut xmp = pdf_metadata_xmp_template
  for replacement in self.pdf_metadata_template_replacements() {
    xmp = pdf_metadata_replace_all(xmp, replacement.0, replacement.1)
  }
  self.set_metadata_from_bytes(@utf8.encode(xmp))
}

///|
/// Extract the catalog metadata XML stream, decoding supported stream filters.
pub fn PdfDocument::metadata(
  self : PdfDocument,
) -> @core.PdfBytes? raise @core.PdfError {
  match
    self.lookup_chain(self.trailer_dict(), [
      pdf_metadata_root(),
      pdf_metadata_metadata(),
    ]) {
    Some(PdfStreamObject(_) as stream) => {
      let decoded = self.pdf_decode_stream(stream)
      Some(decoded.stream_bytes())
    }
    _ => None
  }
}

///|
/// Remove the catalog `/Metadata` entry.
pub fn PdfDocument::remove_metadata(
  self : PdfDocument,
) -> PdfDocument raise @core.PdfError {
  let document = self.copy()
  document.pdf_metadata_update_catalog(fn(catalog) raise @core.PdfError {
    catalog.remove_dict_entry(pdf_metadata_metadata())
  })
  document
}

///|
/// Remove the catalog metadata entry and every parsed object whose `/Type` is
/// `/Metadata`.
pub fn PdfDocument::remove_all_metadata(
  self : PdfDocument,
) -> PdfDocument raise @core.PdfError {
  let document = self.remove_metadata()
  let to_remove : Array[Int] = []
  for entry in document.parsed_object_entries() {
    match document.lookup_direct(pdf_metadata_type(), entry.2) {
      Some(PdfNameObject(name)) if name == pdf_metadata_metadata() =>
        to_remove.push(entry.0)
      _ => ()
    }
  }
  for number in to_remove {
    document.remove_object(number)
  }
  document
}