///|
fn pdf_merge_append_ascii_suffix(
  name : @core.PdfBytes,
  suffix : String,
) -> @core.PdfBytes {
  let suffix_bytes = @ascii.encode(suffix)
  let output = Array::make(name.length() + suffix_bytes.length(), b'\x00')
  let mut position = 0
  for byte in name {
    output[position] = byte
    position += 1
  }
  for byte in suffix_bytes {
    output[position] = byte
    position += 1
  }
  Bytes::from_array(output)
}

///|
fn pdf_merge_append_name_suffix(
  name : @core.PdfName,
  suffix : String,
) -> @core.PdfName {
  @core.pdf_name_of_bytes(pdf_merge_append_ascii_suffix(name.bytes(), suffix))
}

///|
priv struct PdfMergeDestinationNameMap {
  values : @hashmap.HashMap[Bytes, @core.PdfBytes]
}

///|
fn pdf_merge_destination_name_map(
  changes : ArrayView[(@core.PdfBytes, @core.PdfBytes)],
) -> PdfMergeDestinationNameMap {
  let values : @hashmap.HashMap[Bytes, @core.PdfBytes] = HashMap([])
  for change in changes {
    let (old_name, new_name) = change
    if !values.contains(old_name) {
      values[old_name] = new_name
    }
  }
  { values, }
}

///|
fn PdfMergeDestinationNameMap::changed(
  self : PdfMergeDestinationNameMap,
  name : @core.PdfBytes,
) -> @core.PdfBytes {
  match self.values.get(name) {
    Some(new_name) => new_name
    None => name
  }
}

///|
priv struct PdfMergeDestinationNameLookup {
  local_names : PdfMergeDestinationNameMap
  global_names : PdfMergeDestinationNameMap
}

///|
fn pdf_merge_destination_name_lookup(
  changes : ArrayView[(@core.PdfBytes, @core.PdfBytes)],
  all_changes : ArrayView[(@core.PdfBytes, @core.PdfBytes)],
) -> PdfMergeDestinationNameLookup {
  {
    local_names: pdf_merge_destination_name_map(changes),
    global_names: pdf_merge_destination_name_map(all_changes),
  }
}

///|
fn PdfMergeDestinationNameLookup::changed(
  self : PdfMergeDestinationNameLookup,
  name : @core.PdfBytes,
) -> @core.PdfBytes {
  let changed = self.local_names.changed(name)
  if changed == name {
    self.global_names.changed(name)
  } else {
    changed
  }
}

///|
priv struct PdfMergeCatalogDestinationNameMap {
  values : @hashmap.HashMap[Bytes, @core.PdfName]
}

///|
fn pdf_merge_catalog_destination_name_map(
  changes : ArrayView[(@core.PdfName, @core.PdfName)],
) -> PdfMergeCatalogDestinationNameMap {
  let values : @hashmap.HashMap[Bytes, @core.PdfName] = HashMap([])
  for change in changes {
    let (old_name, new_name) = change
    let key = old_name.bytes()
    if !values.contains(key) {
      values[key] = new_name
    }
  }
  { values, }
}

///|
fn PdfMergeCatalogDestinationNameMap::changed(
  self : PdfMergeCatalogDestinationNameMap,
  name : @core.PdfName,
) -> @core.PdfName {
  match self.values.get(name.bytes()) {
    Some(new_name) => new_name
    None => name
  }
}

///|
priv struct PdfMergeCatalogDestinationNameLookup {
  local_names : PdfMergeCatalogDestinationNameMap
  global_names : PdfMergeCatalogDestinationNameMap
}

///|
fn pdf_merge_catalog_destination_name_lookup(
  changes : ArrayView[(@core.PdfName, @core.PdfName)],
  all_changes : ArrayView[(@core.PdfName, @core.PdfName)],
) -> PdfMergeCatalogDestinationNameLookup {
  {
    local_names: pdf_merge_catalog_destination_name_map(changes),
    global_names: pdf_merge_catalog_destination_name_map(all_changes),
  }
}

///|
fn PdfMergeCatalogDestinationNameLookup::changed(
  self : PdfMergeCatalogDestinationNameLookup,
  name : @core.PdfName,
) -> @core.PdfName {
  let changed = self.local_names.changed(name)
  if changed == name {
    self.global_names.changed(name)
  } else {
    changed
  }
}

///|
fn pdf_merge_destination_name_changes_are_unique(
  changes_by_document : ArrayView[Array[(@core.PdfBytes, @core.PdfBytes)]],
) -> Bool {
  let seen = pdf_merge_bytes_set()
  for changes in changes_by_document {
    for change in changes {
      let (_, new_name) = change
      if seen.contains(new_name) {
        return false
      }
      seen.add(new_name)
    }
  }
  true
}

///|
fn pdf_merge_catalog_destination_name_changes_are_unique(
  changes_by_document : ArrayView[Array[(@core.PdfName, @core.PdfName)]],
) -> Bool {
  let seen = pdf_merge_name_set()
  for changes in changes_by_document {
    for change in changes {
      let (_, new_name) = change
      if seen.contains(new_name) {
        return false
      }
      seen.add(new_name)
    }
  }
  true
}

///|
fn pdf_merge_destination_name_lists(
  documents : ArrayView[PdfDocument],
) -> Array[Array[@core.PdfBytes]] raise @core.PdfError {
  let output : Array[Array[@core.PdfBytes]] = []
  for document in documents {
    output.push(
      [
        for entry in document.pdf_merge_name_tree_entries(pdf_merge_dests_key()) => {
          entry.0
        }
      ],
    )
  }
  output
}

///|
fn PdfDocument::pdf_merge_catalog_destination_name_entries(
  self : PdfDocument,
) -> Array[@core.PdfName] raise @core.PdfError {
  match self.pdf_merge_catalog_entry(pdf_merge_dests_key()) {
    Some(PdfDictionary(entries)) => [ for entry in entries => entry.0 ]
    _ => []
  }
}

///|
fn pdf_merge_catalog_destination_name_lists(
  documents : ArrayView[PdfDocument],
) -> Array[Array[@core.PdfName]] raise @core.PdfError {
  let output : Array[Array[@core.PdfName]] = []
  for document in documents {
    output.push(document.pdf_merge_catalog_destination_name_entries())
  }
  output
}

///|
fn pdf_merge_destination_name_changes(
  documents : ArrayView[PdfDocument],
) -> Array[Array[(@core.PdfBytes, @core.PdfBytes)]] raise @core.PdfError {
  let names_by_document = pdf_merge_destination_name_lists(documents)
  let identity : Array[Array[(@core.PdfBytes, @core.PdfBytes)]] = [
    for names in names_by_document => [ for name in names => (name, name) ]
  ]
  if pdf_merge_destination_name_changes_are_unique(identity) {
    [
      for _ in names_by_document => []
    ]
  } else {
    let mut suffix_start = 0
    let mut changes : Array[Array[(@core.PdfBytes, @core.PdfBytes)]] = identity
    while !pdf_merge_destination_name_changes_are_unique(changes) {
      changes = []
      for i in 0.. Array[Array[(@core.PdfName, @core.PdfName)]] raise @core.PdfError {
  let names_by_document = pdf_merge_catalog_destination_name_lists(documents)
  let identity : Array[Array[(@core.PdfName, @core.PdfName)]] = [
    for names in names_by_document => [ for name in names => (name, name) ]
  ]
  if pdf_merge_catalog_destination_name_changes_are_unique(identity) {
    [
      for _ in names_by_document => []
    ]
  } else {
    let mut suffix_start = 0
    let mut changes : Array[Array[(@core.PdfName, @core.PdfName)]] = identity
    while !pdf_merge_catalog_destination_name_changes_are_unique(changes) {
      changes = []
      for i in 0.. Array[(@core.PdfBytes, @core.PdfBytes)] {
  let output : Array[(@core.PdfBytes, @core.PdfBytes)] = []
  for document_changes in changes {
    for change in document_changes {
      output.push(change)
    }
  }
  output
}

///|
fn pdf_merge_all_catalog_destination_name_changes(
  changes : ArrayView[Array[(@core.PdfName, @core.PdfName)]],
) -> Array[(@core.PdfName, @core.PdfName)] {
  let output : Array[(@core.PdfName, @core.PdfName)] = []
  for document_changes in changes {
    for change in document_changes {
      output.push(change)
    }
  }
  output
}