///|
priv struct PdfPageMatrixLookup {
  page_numbers : PdfPageNumberLookup
  matrices : @hashmap.HashMap[Int, @geometry.TransformMatrix]
}

///|
fn pdf_page_matrix_lookup(
  matrices : ArrayView[(Int, @geometry.TransformMatrix)],
  page_reference_numbers : ArrayView[Int],
) -> PdfPageMatrixLookup {
  let values : @hashmap.HashMap[Int, @geometry.TransformMatrix] = HashMap([])
  for entry in matrices {
    let (matrix_page_number, matrix) = entry
    if !values.contains(matrix_page_number) {
      values[matrix_page_number] = matrix
    }
  }
  {
    page_numbers: pdf_page_number_lookup(page_reference_numbers),
    matrices: values,
  }
}

///|
fn PdfPageMatrixLookup::matrix_for_page_number(
  self : PdfPageMatrixLookup,
  page_number : Int,
) -> @geometry.TransformMatrix? {
  self.matrices.get(page_number)
}

///|
fn PdfPageMatrixLookup::page_number_of_target(
  self : PdfPageMatrixLookup,
  target : @destination.PdfTargetPage,
) -> Int {
  match target {
    TargetPageObject(object_number) =>
      self.page_numbers.page_number(object_number)
    TargetOtherDocPageNumber(_) => 0
  }
}

///|
fn PdfDocument::pdf_page_matrix_for_destination_with_lookup(
  self : PdfDocument,
  destination : @destination.PdfDestination,
  lookup : PdfPageMatrixLookup,
) -> @geometry.TransformMatrix? raise @core.PdfError {
  let page_number = self.pdf_page_matrix_page_number_for_destination_with_lookup(
    destination, lookup,
  )
  if page_number == 0 {
    None
  } else {
    lookup.matrix_for_page_number(page_number)
  }
}

///|
fn PdfDocument::pdf_page_matrix_page_number_for_destination_with_lookup(
  self : PdfDocument,
  destination : @destination.PdfDestination,
  lookup : PdfPageMatrixLookup,
) -> Int raise @core.PdfError {
  match destination {
    DestNamed(_) | DestString(_) => {
      let resolved = self.read_destination(
        pdf_object_of_destination(destination),
      )
      if resolved == destination {
        0
      } else {
        self.pdf_page_matrix_page_number_for_destination_with_lookup(
          resolved, lookup,
        )
      }
    }
    DestAction(action) =>
      match
        (
          self.lookup_direct(pdf_dest_s_key(), action),
          self.lookup_direct(pdf_dest_d_key(), action),
        ) {
        (Some(PdfNameObject(name)), Some(raw_destination)) if name ==
          pdf_dest_goto_name() =>
          self.pdf_page_matrix_page_number_for_destination_with_lookup(
            self.read_destination(raw_destination),
            lookup,
          )
        _ => 0
      }
    DestXYZ(target, _, _, _)
    | DestFitH(target, _)
    | DestFitV(target, _)
    | DestFitR(target, _, _, _, _)
    | DestFitBH(target, _)
    | DestFitBV(target, _) => lookup.page_number_of_target(target)
    _ => 0
  }
}

///|
fn PdfDocument::pdf_page_transform_destination_object_with_lookup(
  self : PdfDocument,
  raw_destination : @syntax.PdfObject,
  lookup : PdfPageMatrixLookup,
) -> @syntax.PdfObject? raise @core.PdfError {
  match
    self.pdf_page_transform_destination_object_with_shallow_lookup(
      raw_destination, lookup, false,
    ) {
    Some(destination) => Some(PdfIndirect(self.add_object(destination)))
    None => None
  }
}

///|
fn PdfDocument::pdf_page_transform_destination_object_with_shallow_lookup(
  self : PdfDocument,
  raw_destination : @syntax.PdfObject,
  lookup : PdfPageMatrixLookup,
  shallow : Bool,
) -> @syntax.PdfObject? raise @core.PdfError {
  let destination = self.read_destination(raw_destination, shallow~)
  match self.pdf_page_matrix_for_destination_with_lookup(destination, lookup) {
    Some(matrix) if matrix != @geometry.transform_identity_matrix =>
      Some(
        pdf_object_of_destination(
          self.transform_destination(matrix, destination),
        ),
      )
    _ => None
  }
}