///|
pub fn SemanticIndex::root_metrics(
  self : SemanticIndex,
) -> Array[(String, Int, Int)] {
  let result : Array[(String, Int, Int)] = []
  for root in self.hierarchy_roots() {
    result.push(
      (root, self.graph_depth(root), self.reachable_modules(root).length()),
    )
  }
  result
}

///|
pub fn SemanticIndex::leaf_modules(self : SemanticIndex) -> Array[String] {
  let result : Array[String] = []
  for mod in self.modules {
    if self.module_is_leaf(mod.name) && !result.contains(mod.name) {
      result.push(mod.name)
    }
  }
  result
}

///|
pub fn SemanticIndex::high_fanout_modules(
  self : SemanticIndex,
  threshold : Int,
) -> Array[String] {
  let result : Array[String] = []
  for mod in self.modules {
    if self.fanout(mod.name) >= threshold && !result.contains(mod.name) {
      result.push(mod.name)
    }
  }
  result
}

///|
pub fn SemanticIndex::high_fanin_modules(
  self : SemanticIndex,
  threshold : Int,
) -> Array[String] {
  let result : Array[String] = []
  for mod in self.modules {
    if self.reverse_dependencies(mod.name).length() >= threshold &&
      !result.contains(mod.name) {
      result.push(mod.name)
    }
  }
  result
}

///|
pub fn SemanticIndex::graph_is_acyclic(self : SemanticIndex) -> Bool {
  for mod in self.modules {
    if self.is_recursive(mod.name) {
      return false
    }
  }
  true
}

///|
pub fn SemanticIndex::graph_summary(self : SemanticIndex) -> String {
  "nodes=" +
  self.dependency_nodes().length().to_string() +
  ", edges=" +
  self.edge_count().to_string() +
  ", roots=" +
  self.hierarchy_roots().length().to_string() +
  ", leaves=" +
  self.leaf_modules().length().to_string() +
  ", acyclic=" +
  self.graph_is_acyclic().to_string()
}

///|
pub fn SemanticIndex::module_rank(
  self : SemanticIndex,
  module_name : String,
) -> Int {
  let mut rank = 0
  for root in self.hierarchy_roots() {
    if self.reachable_modules(root).contains(module_name) {
      let depth = self.graph_depth(root)
      if depth > rank {
        rank = depth
      }
    }
  }
  rank
}

///|
pub fn SemanticIndex::ranked_modules(
  self : SemanticIndex,
) -> Array[(String, Int)] {
  let result : Array[(String, Int)] = []
  for mod in self.modules {
    result.push((mod.name, self.module_rank(mod.name)))
  }
  result
}