///|
fn pdf_page_shift_offset_index_map(
  page_numbers : ArrayView[Int],
) -> @hashmap.HashMap[Int, Int] {
  let indexes : @hashmap.HashMap[Int, Int] = HashMap([])
  for index in 0.. Unit raise @core.PdfError {
  if page_numbers.length() != offsets.length() {
    raise BadPageSpecification(
      "shift_pages: offset count must match page count",
    )
  }
}

///|
fn pdf_page_shift_validate_page_indexed_offsets(
  context : String,
  page_count : Int,
  offsets : ArrayView[(Double, Double)],
) -> Unit raise @core.PdfError {
  if offsets.length() != page_count {
    raise BadPageSpecification(context + ": offset count must match page count")
  }
}

///|
fn pdf_page_shift_select_page_indexed_offsets(
  page_numbers : ArrayView[Int],
  offsets : ArrayView[(Double, Double)],
) -> Array[(Double, Double)] {
  let selected : Array[(Double, Double)] = Array(capacity=page_numbers.length())
  for page_number in page_numbers {
    selected.push(offsets[page_number - 1])
  }
  selected
}

///|
fn PdfDocument::pdf_page_shift_page(
  self : PdfDocument,
  page : @page.PdfPage,
  matrix : @geometry.TransformMatrix,
  fast : Bool,
) -> @page.PdfPage raise @core.PdfError {
  let page = self.pdf_impose_change_pattern_matrices_page(matrix, page)
  self.transform_annotations(matrix, page.rest)
  self.prepend_operators(page, [Op_cm(matrix)], fast~)
}

///|
/// Shift selected page contents and annotations by per-page offsets.
///
/// `page_numbers` are 1-based and `offsets` must contain one `(dx, dy)` pair
/// for each selected page. Destination references for shifted pages are
/// transformed with the same matrix.
pub fn PdfDocument::shift_pages(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
  offsets : ArrayView[(Double, Double)],
  fast? : Bool = false,
) -> PdfDocument raise @core.PdfError {
  pdf_page_box_validate_page_numbers(
    page_numbers,
    self.endpage(),
    "shift_pages",
  )
  pdf_page_shift_validate_offsets(page_numbers, offsets)
  let pages = self.pages_of_pagetree()
  let page_indexes = pdf_page_shift_offset_index_map(page_numbers)
  let rewritten : Array[@page.PdfPage] = Array(capacity=pages.length())
  let matrices : Array[(Int, @geometry.TransformMatrix)] = Array(
    capacity=page_numbers.length(),
  )
  for index in 0.. {
        let offset = offsets[offset_index]
        let matrix = @geometry.transform_translate(offset.0, offset.1)
        rewritten.push(self.pdf_page_shift_page(pages[index], matrix, fast))
        matrices.push((page_number, matrix))
      }
      None => rewritten.push(pages[index])
    }
  }
  self.change_pages(rewritten, change_references=true, matrices~)
}

///|
/// Compatibility wrapper for `PdfDocument::shift_pages`.
pub fn pdf_shift_pages(
  document : PdfDocument,
  page_numbers : ArrayView[Int],
  offsets : ArrayView[(Double, Double)],
  fast? : Bool = false,
) -> PdfDocument raise @core.PdfError {
  document.shift_pages(page_numbers, offsets, fast~)
}

///|
/// Shift selected pages using cpdf's absolute page-indexed offset list.
///
/// This mirrors cpdfpage's `shift_pdf`: `offsets[page - 1]` is used for each
/// selected page, while unselected pages are left untouched.
pub fn PdfDocument::shift_pdf(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
  offsets : ArrayView[(Double, Double)],
  fast? : Bool = false,
) -> PdfDocument raise @core.PdfError {
  let pages = self.pages_of_pagetree()
  pdf_page_box_validate_page_numbers(page_numbers, pages.length(), "shift_pdf")
  pdf_page_shift_validate_page_indexed_offsets(
    "shift_pdf",
    pages.length(),
    offsets,
  )
  self.shift_pages(
    page_numbers,
    pdf_page_shift_select_page_indexed_offsets(page_numbers, offsets),
    fast~,
  )
}

///|
/// Compatibility wrapper matching cpdfpage's `shift_pdf` value argument order.
pub fn pdf_shift_pdf(
  offsets : ArrayView[(Double, Double)],
  document : PdfDocument,
  page_numbers : ArrayView[Int],
  fast? : Bool = false,
) -> PdfDocument raise @core.PdfError {
  document.shift_pdf(page_numbers, offsets, fast~)
}