///|
fn pdf_page_root_pages_number(pdf : PdfDocument) -> Int raise @core.PdfError {
  let root = match pdf.pdf_page_active_catalog() {
    Some((catalog, _)) => catalog
    None => raise RootExpected
  }
  match root.lookup_immediate(pdf_page_pages_key()) {
    Some(PdfIndirect(number)) => number
    _ => raise PageTreeExpected
  }
}

///|
fn PdfDocument::pdf_page_tree_nodes_not_pages_inner(
  self : PdfDocument,
  node : @syntax.PdfObject,
  node_number : Int,
  depth : Int,
) -> Array[Int] raise @core.PdfError {
  if depth > 10000 {
    raise PageTreeExpected
  }
  match self.lookup_direct(pdf_page_type_key(), node) {
    Some(PdfNameObject(name)) if name == pdf_page_type_page() => []
    _ =>
      match self.lookup_direct(pdf_page_kids_key(), node) {
        Some(PdfArray(kids)) => {
          let nodes : Array[Int] = Array(capacity=kids.length() + 1)
          nodes.push(node_number)
          for kid in kids {
            match kid {
              PdfIndirect(number) =>
                for
                  nested in self.pdf_page_tree_nodes_not_pages_inner(
                    self.direct(kid),
                    number,
                    depth + 1,
                  ) {
                  nodes.push(nested)
                }
              _ => ()
            }
          }
          nodes
        }
        _ => []
      }
  }
}

///|
fn PdfDocument::pdf_page_tree_nodes_not_pages(
  self : PdfDocument,
) -> Array[Int] raise @core.PdfError {
  let pages_number = pdf_page_root_pages_number(self)
  self.pdf_page_tree_nodes_not_pages_inner(
    self.lookup_object_or_null(pages_number),
    pages_number,
    0,
  )
}

///|
fn PdfDocument::pdf_page_reference_numbers_unordered_inner(
  self : PdfDocument,
  node : @syntax.PdfObject,
  node_number : Int,
  depth : Int,
) -> Array[Int] raise @core.PdfError {
  if depth > 10000 {
    raise PageTreeExpected
  }
  match self.lookup_direct(pdf_page_type_key(), node) {
    Some(PdfNameObject(name)) if name == pdf_page_type_page() => [node_number]
    _ =>
      match self.lookup_direct(pdf_page_kids_key(), node) {
        Some(PdfArray(kids)) => {
          let numbers : Array[Int] = Array(capacity=kids.length())
          for kid in kids {
            match kid {
              PdfIndirect(number) =>
                for
                  page_number in self.pdf_page_reference_numbers_unordered_inner(
                    self.direct(kid),
                    number,
                    depth + 1,
                  ) {
                  numbers.push(page_number)
                }
              _ => ()
            }
          }
          numbers
        }
        _ => [node_number]
      }
  }
}

///|
fn PdfDocument::pdf_page_reference_numbers_unordered(
  self : PdfDocument,
) -> Array[Int] raise @core.PdfError {
  let pages_number = pdf_page_root_pages_number(self)
  self.pdf_page_reference_numbers_unordered_inner(
    self.lookup_object_or_null(pages_number),
    pages_number,
    0,
  )
}

///|
fn pdf_page_first_duplicate_number(numbers : ArrayView[Int]) -> Int? {
  let sorted = numbers.to_owned()
  sorted.sort()
  for i in 1.. (Array[@syntax.PdfObject], Bool) {
  let output : Array[@syntax.PdfObject] = Array(capacity=kids.length())
  let mut rewritten = false
  for kid in kids {
    match kid {
      PdfIndirect(number) if !rewritten && number == old_number => {
        output.push(PdfIndirect(new_number))
        rewritten = true
      }
      _ => output.push(kid)
    }
  }
  (output, rewritten)
}

///|
fn PdfDocument::pdf_page_rewrite_first_indirect(
  self : PdfDocument,
  node_number : Int,
  old_number : Int,
  new_number : Int,
) -> Bool raise @core.PdfError {
  let node = self.lookup_object_or_null(node_number)
  match node.lookup_immediate(pdf_page_kids_key()) {
    Some(PdfArray(kids)) => {
      let (rewritten_kids, rewritten) = pdf_page_rewrite_first_kid(
        kids, old_number, new_number,
      )
      if rewritten {
        self.add_object_given_number(
          node_number,
          node.add_dict_entry(pdf_page_kids_key(), PdfArray(rewritten_kids)),
        )
      }
      rewritten
    }
    _ => false
  }
}

///|
fn PdfDocument::pdf_page_rewrite_page_tree_first(
  self : PdfDocument,
  nodes : ArrayView[Int],
  page_number : Int,
) -> Unit raise @core.PdfError {
  let new_number = self.add_object(self.lookup_object_or_null(page_number))
  let mut rewritten = false
  for node_number in nodes {
    if !rewritten {
      rewritten = self.pdf_page_rewrite_first_indirect(
        node_number, page_number, new_number,
      )
    }
  }
  if !rewritten {
    self.remove_object(new_number)
    raise PageTreeExpected
  }
}

///|
fn PdfDocument::pdf_page_fixup_duplicate_pages(
  self : PdfDocument,
) -> Unit raise @core.PdfError {
  let nodes = self.pdf_page_tree_nodes_not_pages()
  let mut done = false
  while !done {
    match
      pdf_page_first_duplicate_number(
        self.pdf_page_reference_numbers_unordered(),
      ) {
      Some(page_number) =>
        self.pdf_page_rewrite_page_tree_first(nodes, page_number)
      None => done = true
    }
  }
}

///|
fn PdfDocument::pdf_page_fixup_parents_inner(
  self : PdfDocument,
  parent_number : Int,
  node_number : Int,
  depth : Int,
) -> Unit raise @core.PdfError {
  if depth > 10000 {
    raise PageTreeExpected
  }
  let node = self.lookup_object_or_null(node_number)
  match node.lookup_immediate(pdf_page_parent_key()) {
    Some(PdfIndirect(_)) =>
      self.add_object_given_number(
        node_number,
        node.add_dict_entry(pdf_page_parent_key(), PdfIndirect(parent_number)),
      )
    _ => ()
  }
  match self.lookup_direct(pdf_page_kids_key(), node) {
    Some(PdfArray(kids)) =>
      for kid in kids {
        match kid {
          PdfIndirect(number) =>
            self.pdf_page_fixup_parents_inner(node_number, number, depth + 1)
          _ => ()
        }
      }
    _ => ()
  }
}

///|
fn PdfDocument::pdf_page_fixup_parents(
  self : PdfDocument,
) -> Unit raise @core.PdfError {
  self.pdf_page_fixup_parents_inner(0, pdf_page_root_pages_number(self), 0)
}