///|
/// Merges selected page ranges from multiple documents into one document.
///
/// Each entry in `ranges` belongs to the document at the same index and uses
/// one-based page numbers. The retain flags control whether numbering,
/// bookmarks, optional-content data, AcroForms, named destinations, name
/// dictionary entries, structure trees, info dictionaries, and catalog entries
/// are copied into the output. Raises `@core.PdfError::PageTreeExpected` when the
/// document and range lists do not align.
pub fn pdf_merge_documents(
  documents : ArrayView[PdfDocument],
  ranges : ArrayView[Array[Int]],
  retain_numbering? : Bool = false,
  retain_bookmarks? : Bool = true,
  retain_optional_content? : Bool = true,
  retain_acroforms? : Bool = true,
  retain_named_destinations? : Bool = true,
  retain_name_dictionary? : Bool = true,
  retain_structure_tree? : Bool = true,
  add_toplevel_document? : Bool = false,
  retain_info? : Bool = true,
  retain_catalog_entries? : Bool = true,
  remove_duplicate_fonts? : Bool = false,
) -> PdfDocument raise @core.PdfError {
  if documents.length() == 0 || documents.length() != ranges.length() {
    raise PageTreeExpected
  }
  let page_labels = if retain_numbering {
    pdf_page_label_merge(documents, ranges)
  } else {
    []
  }
  let staged : Array[PdfDocument] = []
  let mut offset = 0
  for i in 0.. []
    ]
  }
  let catalog_destination_name_changes = if retain_named_destinations {
    pdf_merge_catalog_destination_name_changes(staged)
  } else {
    [
      for _ in staged => []
    ]
  }
  let all_destination_name_changes = pdf_merge_all_destination_name_changes(
    destination_name_changes,
  )
  let all_catalog_destination_name_changes = pdf_merge_all_catalog_destination_name_changes(
    catalog_destination_name_changes,
  )
  let open_action = if retain_catalog_entries {
    pdf_merge_open_action(
      staged, retain_named_destinations, destination_name_changes, all_destination_name_changes,
      catalog_destination_name_changes, all_catalog_destination_name_changes,
    )
  } else {
    None
  }
  if retain_named_destinations {
    for i in 0.. 1 {
    pdf_merge_renumber_parent_trees(staged)
  } else {
    []
  }
  let catalog_entries = if retain_catalog_entries {
    pdf_merge_catalog_extra_entries(staged)
  } else {
    []
  }
  let merged = pdf_document_empty()
  let (major, minor) = pdf_merge_max_version(staged)
  merged.set_version(major, minor)
  let pages : Array[@page.PdfPage] = []
  let old_page_numbers : Array[Int] = []
  let bookmarks : Array[@bookmark.PdfBookmark] = []
  let oldstyle_destinations : Array[(@core.PdfName, @syntax.PdfObject)] = []
  let nametree_destinations : Array[(@core.PdfBytes, @syntax.PdfObject)] = []
  for document in staged {
    merged.pdf_merge_copy_objects_from(document)
    let old_page_numbers_for_document = document.page_reference_numbers()
    for page_number in old_page_numbers_for_document {
      old_page_numbers.push(page_number)
    }
    if retain_bookmarks {
      for bookmark in document.read_bookmarks(preserve_actions=true) {
        bookmarks.push(bookmark)
      }
    }
    for page in document.pages_of_pagetree() {
      pages.push(page)
    }
  }
  let page_root_number = merged.add_pagetree(pages)
  ignore(merged.add_root(page_root_number, []))
  let merged = if catalog_entries.length() > 0 {
    for entry in catalog_entries {
      let (key, value) = entry
      ignore(merged.pdf_merge_add_catalog_entry(key, value))
    }
    merged
  } else {
    merged
  }
  let merged = match
    merged.pdf_merge_structure_tree_root(
      structure_tree_roots, parent_tree_entries, add_toplevel_document,
    ) {
    Some(structure_tree_root) =>
      merged.pdf_merge_add_catalog_entry(
        pdf_merge_struct_tree_root_key(),
        structure_tree_root,
      )
    None => merged
  }
  let new_page_numbers = merged.page_reference_numbers()
  let changes = pdf_page_reference_changes(old_page_numbers, new_page_numbers)
  let change_map = pdf_page_reference_map(changes)
  merged.pdf_page_renumber_references(changes)
  let merged = match open_action {
    Some(open_action) =>
      merged.pdf_merge_add_catalog_entry(
        pdf_merge_open_action_key(),
        pdf_page_renumber_object_with_map(open_action, change_map),
      )
    None => merged
  }
  if retain_named_destinations {
    for i in 0.. merged.write_optional_content_groups(content)
    None => merged
  }
  let merged = match acroform {
    Some(form) =>
      merged.pdf_merge_add_catalog_entry(
        pdf_merge_acroform_key(),
        PdfIndirect(merged.add_object(form)),
      )
    None => merged
  }
  let merged = if retain_named_destinations ||
    name_dictionary_entries.length() > 0 {
    merged.pdf_merge_add_named_destinations(
      oldstyle_destinations, nametree_destinations, name_dictionary_entries,
    )
  } else {
    merged
  }
  match info {
    Some(info) =>
      merged.set_trailer_dict(
        merged
        .trailer_dict()
        .add_dict_entry(
          pdf_merge_info_key(),
          PdfIndirect(merged.add_object(info)),
        ),
      )
    None => ()
  }
  merged.remove_unreferenced()
  let merged = if retain_bookmarks && bookmarks.length() > 0 {
    merged.add_bookmarks(
      [
        for bookmark in bookmarks => {
          pdf_merge_renumber_bookmark_with_map(bookmark, change_map)
        }
      ],
    )
  } else {
    merged
  }
  let merged = if retain_numbering && page_labels.length() > 0 {
    merged.write_page_labels(page_labels)
  } else {
    merged
  }
  if remove_duplicate_fonts {
    merged.remove_duplicate_fonts()
  }
  let (merged, _) = merged.with_file_id()
  merged
}

///|
/// CamlPDF-shaped convenience wrapper for merging PDFs.
///
/// `names` must have the same length as `documents`; it is retained for API
/// compatibility with CamlPDF's merge entry point. Structure-tree processing
/// and optional top-level `/Document` wrapping are controlled by the optional
/// flags.
pub fn pdf_merge_pdfs(
  retain_numbering : Bool,
  remove_duplicate_fonts : Bool,
  names : ArrayView[String],
  documents : ArrayView[PdfDocument],
  ranges : ArrayView[Array[Int]],
  process_struct_trees? : Bool = true,
  add_toplevel_document? : Bool = false,
) -> PdfDocument raise @core.PdfError {
  if names.length() != documents.length() {
    raise PageTreeExpected
  }
  pdf_merge_documents(
    documents,
    ranges,
    retain_numbering~,
    retain_structure_tree=process_struct_trees,
    add_toplevel_document~,
    remove_duplicate_fonts~,
  )
}