///|
fn pdf_ua_matterhorn_push_unique_name(
  names : Array[@core.PdfName],
  name : @core.PdfName,
) -> Unit {
  if !names.contains(name) {
    names.push(name)
  }
}

///|
fn PdfDocument::pdf_ua_matterhorn_read_single_attribute(
  self : PdfDocument,
  object : @syntax.PdfObject,
) -> Array[@core.PdfName] raise @core.PdfError {
  match self.direct(object) {
    PdfDictionary(entries) => [ for entry in entries => entry.0 ]
    PdfStreamObject(stream) =>
      self.pdf_ua_matterhorn_read_single_attribute(stream.dictionary)
    PdfNameObject(name) => [name]
    _ => raise SoftError("read_single: malformed attribute")
  }
}

///|
fn PdfDocument::pdf_ua_matterhorn_read_attribute_entry(
  self : PdfDocument,
  key : @core.PdfName,
  node : @syntax.PdfObject,
) -> Array[@core.PdfName] raise @core.PdfError {
  match self.lookup_direct(key, node) {
    Some(PdfArray(items)) => {
      let attrs : Array[@core.PdfName] = []
      for item in items {
        match item {
          PdfInteger(_) => ()
          _ =>
            for attr in self.pdf_ua_matterhorn_read_single_attribute(item) {
              attrs.push(attr)
            }
        }
      }
      attrs
    }
    Some(PdfNameObject(name)) => [name]
    Some(PdfDictionary(_) | PdfStreamObject(_) as object) =>
      self.pdf_ua_matterhorn_read_single_attribute(object)
    Some(_) | None => []
  }
}

///|
fn PdfDocument::pdf_ua_matterhorn_read_struct_attributes(
  self : PdfDocument,
  node : @syntax.PdfObject,
) -> Array[@core.PdfName] raise @core.PdfError {
  let attrs : Array[@core.PdfName] = []
  for
    attr in self.pdf_ua_matterhorn_read_attribute_entry(
      pdf_ua_matterhorn_a_key(),
      node,
    ) {
    pdf_ua_matterhorn_push_unique_name(attrs, attr)
  }
  for
    attr in self.pdf_ua_matterhorn_read_attribute_entry(
      pdf_ua_matterhorn_c_key(),
      node,
    ) {
    pdf_ua_matterhorn_push_unique_name(attrs, attr)
  }
  if self.lookup_direct(pdf_ua_matterhorn_id_key(), node) is Some(_) {
    attrs.push(pdf_ua_matterhorn_id_key())
  }
  if self.lookup_direct(pdf_ua_matterhorn_actual_text_key(), node) is Some(_) {
    attrs.push(pdf_ua_matterhorn_actual_text_key())
  }
  if self.lookup_direct(pdf_ua_matterhorn_alt_key(), node) is Some(_) {
    attrs.push(pdf_ua_matterhorn_alt_key())
  }
  match self.direct(node) {
    PdfDictionary(entries) =>
      for entry in entries {
        if entry.0 == pdf_ua_pg_key() {
          match entry.1 {
            PdfIndirect(number) =>
              attrs.push(pdf_ua_name("_" + number.to_string()))
            _ => ()
          }
        }
      }
    _ => ()
  }
  attrs
}

///|
fn PdfDocument::pdf_ua_matterhorn_read_struct_tree_inner(
  self : PdfDocument,
  node : @syntax.PdfObject,
  rolemap : PdfUAStructTreeRoleMap,
  depth : Int,
) -> PdfUAMatterhornStructNode raise @core.PdfError {
  if depth > 10000 {
    raise SoftError("malformed st node")
  }
  let name = match self.lookup_direct(pdf_ua_s_key(), node) {
    Some(PdfNameObject(raw_name)) => rolemap.rewrite_name(raw_name)
    _ => pdf_ua_empty_name()
  }
  let attrs = self.pdf_ua_matterhorn_read_struct_attributes(node)
  let children = match self.lookup_direct(pdf_ua_k_key(), node) {
    None => []
    Some(PdfDictionary(_) as child) =>
      [self.pdf_ua_matterhorn_read_struct_tree_inner(child, rolemap, depth + 1)]
    Some(PdfInteger(_)) => []
    Some(PdfArray(items)) => {
      let output : Array[PdfUAMatterhornStructNode] = []
      for item in items {
        output.push(
          self.pdf_ua_matterhorn_read_struct_tree_inner(
            item,
            rolemap,
            depth + 1,
          ),
        )
      }
      output
    }
    Some(_) => raise SoftError("malformed st node")
  }
  { name, attrs, children, }
}

///|
fn PdfDocument::pdf_ua_matterhorn_read_struct_tree(
  self : PdfDocument,
) -> PdfUAMatterhornStructNode raise @core.PdfError {
  let rolemap = self.pdf_ua_matterhorn_rolemap()
  let root_name = rolemap.rewrite_name(pdf_ua_struct_tree_root_key())
  let catalog = self.catalog() catch { _ => raise SoftError("read_st no root") }
  match catalog.lookup_immediate(pdf_ua_struct_tree_root_key()) {
    None => { name: root_name, attrs: [], children: [], }
    Some(struct_tree_root) => {
      let tree = self.pdf_ua_matterhorn_read_struct_tree_inner(
        struct_tree_root, rolemap, 0,
      )
      { name: root_name, attrs: tree.attrs, children: tree.children, }
    }
  }
}

///|
fn PdfUAMatterhornStructNode::has_attr(
  self : PdfUAMatterhornStructNode,
  attr : @core.PdfName,
) -> Bool {
  self.attrs.contains(attr)
}

///|
fn PdfUAMatterhornStructNode::contains_name(
  self : PdfUAMatterhornStructNode,
  predicate : (@core.PdfName) -> Bool,
) -> Bool {
  if predicate(self.name) {
    true
  } else {
    for child in self.children {
      if child.contains_name(predicate) {
        return true
      }
    }
    false
  }
}