///|
fn PdfDocument::pdf_structure_indirect_number(
  self : PdfDocument,
  object_number : Int,
  key : @core.PdfName,
) -> Int? {
  match self.direct(PdfIndirect(object_number)).lookup_immediate(key) {
    Some(PdfIndirect(number)) => Some(number)
    _ => None
  }
}

///|
fn PdfDocument::pdf_structure_object_has_deleted_page_in_set(
  self : PdfDocument,
  object : @syntax.PdfObject,
  deleted_pages : @core.PdfNumberSet,
) -> Bool {
  match self.direct(object).lookup_immediate(pdf_structure_pg_key()) {
    Some(PdfIndirect(page_number)) => deleted_pages.contains(page_number)
    _ => false
  }
}

///|
fn PdfDocument::pdf_structure_indirect_survives_in_set(
  self : PdfDocument,
  object_number : Int,
  deleted_pages : @core.PdfNumberSet,
) -> Bool {
  if self.lookup_object_or_null(object_number) == PdfNull {
    false
  } else {
    match
      (
        self.pdf_structure_indirect_number(
          object_number,
          pdf_structure_pg_key(),
        ),
        self.pdf_structure_indirect_number(
          object_number,
          pdf_structure_obj_key(),
        ),
      ) {
      (Some(page_number), _) if deleted_pages.contains(page_number) => false
      (_, Some(page_number)) if deleted_pages.contains(page_number) => false
      _ => true
    }
  }
}

///|
fn PdfDocument::pdf_structure_k_item_survives_in_set(
  self : PdfDocument,
  item : @syntax.PdfObject,
  deleted_pages : @core.PdfNumberSet,
) -> Bool {
  match item {
    PdfIndirect(object_number) =>
      self.pdf_structure_indirect_survives_in_set(object_number, deleted_pages)
    _ => true
  }
}

///|
fn PdfDocument::pdf_structure_filter_k_items_in_set(
  self : PdfDocument,
  items : ArrayView[@syntax.PdfObject],
  deleted_pages : @core.PdfNumberSet,
) -> Array[@syntax.PdfObject]? {
  let output : Array[@syntax.PdfObject] = Array(capacity=items.length())
  let mut changed = false
  for item in items {
    if self.pdf_structure_k_item_survives_in_set(item, deleted_pages) {
      output.push(item)
    } else {
      changed = true
    }
  }
  if changed {
    Some(output)
  } else {
    None
  }
}

///|
fn PdfDocument::pdf_structure_filtered_k_value_in_set(
  self : PdfDocument,
  object : @syntax.PdfObject,
  deleted_pages : @core.PdfNumberSet,
) -> Array[@syntax.PdfObject]? {
  match self.direct(object) {
    PdfDictionary(_) as dictionary =>
      match dictionary.lookup_immediate(pdf_structure_k_key()) {
        Some(PdfInteger(_)) => None
        Some(PdfIndirect(_) as child) =>
          self.pdf_structure_filter_k_items_in_set([child], deleted_pages)
        Some(PdfArray(children)) =>
          self.pdf_structure_filter_k_items_in_set(children, deleted_pages)
        _ => None
      }
    _ => None
  }
}

///|
fn pdf_structure_k_object(
  items : ArrayView[@syntax.PdfObject],
) -> @syntax.PdfObject {
  if items.length() == 1 {
    items[0]
  } else {
    PdfArray([ for item in items => item ])
  }
}

///|
fn PdfDocument::pdf_structure_trim_deleted_pages(
  self : PdfDocument,
  deleted_page_numbers : ArrayView[Int],
) -> Unit raise @core.PdfError {
  if deleted_page_numbers.length() > 0 {
    let deleted_pages = @core.pdf_number_set(deleted_page_numbers)
    let mut changed = true
    while changed {
      changed = false
      let object_numbers = self.object_numbers()
      let deletions : Array[Int] = Array(capacity=object_numbers.length())
      let deletion_seen = @core.pdf_number_set([])
      for object_number in object_numbers {
        if self.pdf_structure_object_has_deleted_page_in_set(
            self.lookup_object_or_null(object_number),
            deleted_pages,
          ) {
          @core.pdf_push_unique_number(deletions, deletion_seen, object_number)
        }
      }
      for object_number in deletions {
        if self.lookup_object_or_null(object_number) != PdfNull {
          self.remove_object(object_number)
          changed = true
        }
      }
      let object_numbers = self.object_numbers()
      let replacements : Array[(Int, @syntax.PdfObject)] = Array(
        capacity=object_numbers.length(),
      )
      for object_number in object_numbers {
        let object = self.lookup_object_or_null(object_number)
        match
          self.pdf_structure_filtered_k_value_in_set(object, deleted_pages) {
          Some([]) =>
            @core.pdf_push_unique_number(
              deletions, deletion_seen, object_number,
            )
          Some(filtered) =>
            replacements.push(
              (
                object_number,
                object.replace_dict_entry(
                  pdf_structure_k_key(),
                  pdf_structure_k_object(filtered),
                ),
              ),
            )
          None => ()
        }
      }
      for object_number in deletions {
        if self.lookup_object_or_null(object_number) != PdfNull {
          self.remove_object(object_number)
          changed = true
        }
      }
      for replacement in replacements {
        let (object_number, object) = replacement
        if self.lookup_object_or_null(object_number) != PdfNull {
          self.add_object_given_number(object_number, object)
          changed = true
        }
      }
    }
  }
}

///|
/// Trim structure-tree entries that point to pages outside the selected pages.
///
/// `page_numbers` uses the same page-selection numbering as the page helpers.
/// The document is mutated in place by deleting structure objects tied to
/// removed pages and pruning `/K` arrays that reference them.
pub fn PdfDocument::trim_structure_tree(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
) -> Unit raise @core.PdfError {
  let all_page_numbers = self.page_reference_numbers()
  let kept_page_numbers = pdf_page_select_numbers(
    all_page_numbers, page_numbers,
  )
  self.pdf_structure_trim_deleted_pages(
    pdf_page_deleted_numbers(all_page_numbers, kept_page_numbers),
  )
}

///|
/// Compatibility wrapper for `PdfDocument::trim_structure_tree`.
pub fn pdf_trim_structure_tree(
  document : PdfDocument,
  page_numbers : ArrayView[Int],
) -> Unit raise @core.PdfError {
  document.trim_structure_tree(page_numbers)
}