///|
/// Return module names in source declaration order.
pub fn SemanticIndex::module_names(self : SemanticIndex) -> Array[String] {
  let names : Array[String] = []
  for mod in self.modules {
    names.push(mod.name)
  }
  names
}

///|
/// Return the number of module declarations, including duplicate names.
pub fn SemanticIndex::module_count(self : SemanticIndex) -> Int {
  self.modules.length()
}

///|
/// Return names of signals visible in one indexed module.
pub fn SemanticIndex::signal_names(
  self : SemanticIndex,
  module_name : String,
) -> Array[String] {
  match self.find_module(module_name) {
    Some(mod) => {
      let names : Array[String] = []
      for signal in mod.signals {
        names.push(signal.name)
      }
      names
    }
    None => []
  }
}

///|
/// Return names of signals with at least one recorded driver.
pub fn SemanticIndex::driven_signals(
  self : SemanticIndex,
  module_name : String,
) -> Array[String] {
  match self.find_module(module_name) {
    Some(mod) => {
      let names : Array[String] = []
      for signal in mod.signals {
        if signal.has_driver() {
          names.push(signal.name)
        }
      }
      names
    }
    None => []
  }
}

///|
/// Return names of internal signals which have no recorded driver.
pub fn SemanticIndex::undriven_signals(
  self : SemanticIndex,
  module_name : String,
) -> Array[String] {
  match self.find_module(module_name) {
    Some(mod) => {
      let names : Array[String] = []
      for signal in mod.signals {
        if !signal.from_port && !signal.has_driver() {
          names.push(signal.name)
        }
      }
      names
    }
    None => []
  }
}

///|
/// Count hierarchy children of one module.
pub fn SemanticIndex::fanout(self : SemanticIndex, module_name : String) -> Int {
  let mut count = 0
  for edge in self.graph.edges() {
    let (parent, _) = edge
    if parent == module_name {
      count += 1
    }
  }
  count
}

///|
/// Return modules which instantiate at least one other module.
pub fn SemanticIndex::hierarchy_parents(self : SemanticIndex) -> Array[String] {
  let parents : Array[String] = []
  for edge in self.graph.edges() {
    let (parent, _) = edge
    if !parents.contains(parent) {
      parents.push(parent)
    }
  }
  parents
}

///|
/// Return modules which are not referenced as a child by another module.
pub fn SemanticIndex::hierarchy_roots(self : SemanticIndex) -> Array[String] {
  let children : Array[String] = []
  for edge in self.graph.edges() {
    let (_, child) = edge
    if !children.contains(child) {
      children.push(child)
    }
  }
  let roots : Array[String] = []
  for mod in self.modules {
    if !children.contains(mod.name) && !roots.contains(mod.name) {
      roots.push(mod.name)
    }
  }
  roots
}

///|
/// Return unresolved instance issues without exposing the whole issue list.
pub fn SemanticIndex::unresolved_instances(self : SemanticIndex) -> Int {
  let mut count = 0
  for issue in self.issues {
    if issue.code == "RTL102" {
      count += 1
    }
  }
  count
}

///|
/// Return whether the index contains a named module.
pub fn SemanticIndex::has_module(self : SemanticIndex, name : String) -> Bool {
  self.find_module(name) is Some(_)
}

///|
/// Return whether the graph contains a particular directed edge.
pub fn SemanticIndex::has_edge(
  self : SemanticIndex,
  parent : String,
  child : String,
) -> Bool {
  for edge in self.graph.edges() {
    let (edge_parent, edge_child) = edge
    if edge_parent == parent && edge_child == child {
      return true
    }
  }
  false
}

///|
/// Produce a compact, stable index summary for logs and diagnostics.
pub fn SemanticIndex::summary(self : SemanticIndex) -> String {
  "modules=" +
  self.module_count().to_string() +
  ", edges=" +
  self.graph.edges().length().to_string() +
  ", unresolved_instances=" +
  self.unresolved_instances().to_string()
}