///|
fn pdf_lookup_wrap_chain(
  keys : ArrayView[@core.PdfName],
  index : Int,
  value : @syntax.PdfObject,
) -> @syntax.PdfObject {
  if index >= keys.length() {
    value
  } else {
    @syntax.pdf_dictionary([
      (keys[index], pdf_lookup_wrap_chain(keys, index + 1, value)),
    ])
  }
}

///|
fn pdf_lookup_replace_array_entry(
  values : ArrayView[@syntax.PdfObject],
  index : Int,
  value : @syntax.PdfObject,
  key : @core.PdfName,
) -> @syntax.PdfObject raise @core.PdfError {
  if index < 0 || index >= values.length() {
    raise DictionaryKeyNotFound(key)
  } else {
    PdfArray(
      [
        for i in 0.. {
          if i == index {
            value
          } else {
            values[i]
          }
        }
      ],
    )
  }
}

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

///|
/// Replace a value along a path rooted at the document trailer dictionary.
///
/// Each name in `chain` selects a dictionary entry. Names of the form `/[n]`
/// select array entries. Missing intermediate dictionary entries are created as
/// nested dictionaries, and indirect intermediate objects are updated in the
/// document object table. Raises when the chain is empty or when a non-container
/// or invalid array position is encountered.
pub fn PdfDocument::replace_chain(
  self : PdfDocument,
  chain : ArrayView[@core.PdfName],
  value : @syntax.PdfObject,
) -> Unit raise @core.PdfError {
  if chain.length() == 0 {
    raise DictionaryExpected
  }
  self.trailer_dict = self.replace_chain_in_object(
    self.trailer_dict,
    chain,
    0,
    value,
  )
}