///|
fn PdfDocument::remove_chain_from_object(
  self : PdfDocument,
  object : @syntax.PdfObject,
  chain : ArrayView[@core.PdfName],
  chain_index : Int,
) -> (@syntax.PdfObject, Bool) raise @core.PdfError {
  let key = chain[chain_index]
  let final_key = chain_index + 1 == chain.length()
  match object {
    PdfDictionary(_) | PdfStreamObject(_) =>
      if final_key {
        match object.lookup_immediate(key) {
          Some(PdfNull) | None => (object, false)
          Some(_) => (object.remove_dict_entry(key), true)
        }
      } else {
        match object.lookup_immediate(key) {
          Some(PdfIndirect(number)) => {
            let (updated, removed) = self.remove_chain_from_object(
              self.lookup_object_or_null(number),
              chain,
              chain_index + 1,
            )
            if removed {
              self.add_object_given_number(number, updated)
            }
            (object, removed)
          }
          Some(PdfNull) | None => (object, false)
          Some(child) => {
            let (updated, removed) = self.remove_chain_from_object(
              child,
              chain,
              chain_index + 1,
            )
            if removed {
              (object.add_dict_entry(key, updated), true)
            } else {
              (object, false)
            }
          }
        }
      }
    PdfArray(values) =>
      match pdf_lookup_array_index_name(key) {
        Some(index) =>
          if final_key {
            raise DictionaryExpected
          } else {
            match values.get(index) {
              Some(PdfIndirect(number)) => {
                let (updated, removed) = self.remove_chain_from_object(
                  self.lookup_object_or_null(number),
                  chain,
                  chain_index + 1,
                )
                if removed {
                  self.add_object_given_number(number, updated)
                }
                (object, removed)
              }
              Some(child) => {
                let (updated, removed) = self.remove_chain_from_object(
                  child,
                  chain,
                  chain_index + 1,
                )
                if removed {
                  (
                    pdf_lookup_replace_array_entry(values, index, updated, key),
                    true,
                  )
                } else {
                  (object, false)
                }
              }
              None => (object, false)
            }
          }
        None => raise DictionaryKeyNotFound(key)
      }
    _ => raise DictionaryExpected
  }
}

///|
/// Remove a nested value along a path rooted at the trailer dictionary.
///
/// Returns `true` when an entry was removed and `false` when the path is
/// absent. Empty chains raise `@core.PdfError::DictionaryExpected`. A single-key chain is
/// treated as an invalid request when the key exists, preserving the trailer
/// root rather than removing one of its direct entries.
pub fn PdfDocument::remove_chain(
  self : PdfDocument,
  chain : ArrayView[@core.PdfName],
) -> Bool raise @core.PdfError {
  if chain.length() == 0 {
    raise DictionaryExpected
  }
  if chain.length() == 1 {
    match self.lookup_chain(self.trailer_dict, chain) {
      Some(_) => raise DictionaryExpected
      None => return false
    }
  }
  let (updated, removed) = self.remove_chain_from_object(
    self.trailer_dict,
    chain,
    0,
  )
  if removed {
    self.trailer_dict = updated
  }
  removed
}

///|
/// Follow a sequence of dictionary keys and array pseudo-keys.
///