///|
fn pdf_page_default_mediabox() -> @syntax.PdfObject {
  PdfArray([PdfInteger(0), PdfInteger(0), PdfInteger(612), PdfInteger(792)])
}

///|
fn PdfDocument::pdf_page_direct_child(
  self : PdfDocument,
  kid : @syntax.PdfObject,
) -> @syntax.PdfObject raise @core.PdfError {
  match kid {
    PdfIndirect(number) => {
      let child = self.lookup_object_or_null(number)
      if child == PdfNull {
        raise PageTreeExpected
      }
      child
    }
    PdfDictionary(_) => kid
    _ => raise PageTreeExpected
  }
}

///|
/// The attributes a page tree node passes down: its own, else those it
/// inherited (resources, media box, crop box, rotation).
priv struct PdfPageInherited {
  resources : @syntax.PdfObject?
  mediabox : @syntax.PdfObject?
  cropbox : @syntax.PdfObject?
  rotate : @page.PdfPageRotation
}

///|
/// A branch node's kids, with the attributes they inherit, for
/// `pdf_page_tree_walk`; a branch without a `/Kids` array raises
/// `PageTreeExpected`.
fn PdfDocument::pdf_page_branch_children(
  self : PdfDocument,
  node : @syntax.PdfObject,
  inherited : PdfPageInherited,
) -> (Int, Array[@syntax.PdfObject], PdfPageInherited) raise @core.PdfError {
  let passed = PdfPageInherited::{
    resources: pdf_page_inherited_object(
      self.lookup_direct(pdf_page_resources_key(), node),
      inherited.resources,
    ),
    mediabox: pdf_page_inherited_object(
      self.lookup_direct(pdf_page_mediabox_key(), node),
      inherited.mediabox,
    ),
    cropbox: pdf_page_inherited_object(
      self.lookup_direct(pdf_page_cropbox_key(), node),
      inherited.cropbox,
    ),
    rotate: self.pdf_page_inherited_rotation(node, inherited.rotate),
  }
  match self.lookup_direct(pdf_page_kids_key(), node) {
    Some(PdfArray(kids)) => (pdf_page_tree_kids_number(node), kids, passed)
    _ => raise PageTreeExpected
  }
}

///|
fn PdfDocument::pdf_page_content_object(
  self : PdfDocument,
  page : @syntax.PdfObject,
) -> @syntax.PdfObject? {
  match self.direct(page).lookup_immediate(pdf_page_contents_key()) {
    Some(PdfIndirect(number) as indirect) =>
      match self.lookup_object_or_null(number) {
        PdfArray(items) => Some(PdfArray(items))
        _ => Some(PdfArray([indirect]))
      }
    Some(value) => Some(value)
    None => None
  }
}

///|
fn PdfDocument::pdf_page_content_list(
  self : PdfDocument,
  contents : @syntax.PdfObject?,
) -> Array[@syntax.PdfObject] {
  let output : Array[@syntax.PdfObject] = Array(
    capacity=match contents {
      Some(PdfArray(items)) => items.length()
      Some(_) => 1
      None => 0
    },
  )
  match contents {
    None => ()
    Some(PdfArray(items)) =>
      for item in items {
        match self.direct(item) {
          PdfStreamObject(_) => output.push(item)
          _ => ()
        }
      }
    Some(object) =>
      match self.direct(object) {
        PdfStreamObject(_) => output.push(object)
        _ => ()
      }
  }
  output
}

///|
fn PdfDocument::pdf_page_leaf(
  self : PdfDocument,
  page_node : @syntax.PdfObject,
  inherited_resources : @syntax.PdfObject?,
  inherited_mediabox : @syntax.PdfObject?,
  inherited_cropbox : @syntax.PdfObject?,
  inherited_rotate : @page.PdfPageRotation,
  last_mediabox : Ref[@syntax.PdfObject],
) -> @page.PdfPage raise @core.PdfError {
  let page = self.direct(page_node)
  let resources = match
    pdf_page_inherited_object(
      self.lookup_direct(pdf_page_resources_key(), page),
      inherited_resources,
    ) {
    Some(value) => value
    None => PdfDictionary([])
  }
  let mediabox = match
    pdf_page_inherited_object(
      self.lookup_direct(pdf_page_mediabox_key(), page),
      inherited_mediabox,
    ) {
    Some(value) => {
      last_mediabox.val = value
      value
    }
    None => last_mediabox.val
  }
  let rotate = self.pdf_page_inherited_rotation(page, inherited_rotate)
  let rest = match
    pdf_page_inherited_object(
      self.lookup_direct(pdf_page_cropbox_key(), page),
      inherited_cropbox,
    ) {
    Some(cropbox) =>
      pdf_page_rest_object_from_page(page).add_dict_entry(
        pdf_page_cropbox_key(),
        cropbox,
      )
    None => pdf_page_rest_object_from_page(page)
  }
  {
    content: self.pdf_page_content_list(self.pdf_page_content_object(page)),
    mediabox,
    resources,
    rotate,
    rest,
  }
}

///|
fn PdfDocument::pdf_pages_of_node(
  self : PdfDocument,
  node : @syntax.PdfObject,
  inherited_resources : @syntax.PdfObject?,
  inherited_mediabox : @syntax.PdfObject?,
  inherited_cropbox : @syntax.PdfObject?,
  inherited_rotate : @page.PdfPageRotation,
  last_mediabox : Ref[@syntax.PdfObject],
  depth : Int,
) -> Array[@page.PdfPage] raise @core.PdfError {
  let pages : Array[@page.PdfPage] = []
  let inherited = PdfPageInherited::{
    resources: inherited_resources,
    mediabox: inherited_mediabox,
    cropbox: inherited_cropbox,
    rotate: inherited_rotate,
  }
  self.pdf_page_tree_walk(
    node,
    -1,
    depth,
    inherited,
    (node, _, inherited) => {
      let branch = match self.lookup_direct(pdf_page_type_key(), node) {
        Some(PdfNameObject(name)) if name == pdf_page_type_pages() => true
        None =>
          self.lookup_direct(pdf_page_kids_key(), node) is Some(PdfArray(_))
        _ => false
      }
      if branch {
        Some(self.pdf_page_branch_children(node, inherited))
      } else {
        pages.push(
          self.pdf_page_leaf(
            node,
            inherited.resources,
            inherited.mediabox,
            inherited.cropbox,
            inherited.rotate,
            last_mediabox,
          ),
        )
        None
      }
    },
    kid => {
      let number = match kid {
        PdfIndirect(number) => number
        _ => -1
      }
      Some((self.pdf_page_direct_child(kid), number))
    },
  )
  pages
}