///|
pub(all) struct ParseMetrics {
  modules : Int
  syntax_errors : Int
  ports : Int
  signals : Int
  items : Int
  instances : Int
  expressions : Int
  statements : Int
} derive(Eq, Debug)

///|
pub fn ParseResult::metrics(self : ParseResult) -> ParseMetrics {
  let design_metrics = self.design.metrics()
  let mut items = 0
  let mut expressions = 0
  let mut statements = 0
  for mod in self.design.modules {
    items += mod.items.length()
    for item in mod.items {
      match item {
        @syntax.Assign(assign) => {
          expressions += assign.target.node_count()
          expressions += assign.value.node_count()
        }
        @syntax.Always(always) => {
          statements += always.body.node_count()
          expressions += always.body.node_count()
        }
        @syntax.Statement(statement) => {
          statements += statement.node_count()
          expressions += statement.read_names().length()
        }
        _ => ()
      }
    }
  }
  {
    modules: design_metrics.modules,
    syntax_errors: self.errors.length(),
    ports: design_metrics.ports,
    signals: design_metrics.signals,
    items,
    instances: design_metrics.instances,
    expressions,
    statements,
  }
}

///|
pub fn ParseResult::error_count(self : ParseResult) -> Int {
  self.errors.length()
}

///|
pub fn ParseResult::has_recoverable_errors(self : ParseResult) -> Bool {
  self.errors.length() > 0 && self.design.modules.length() > 0
}

///|
pub fn ParseResult::error_spans(self : ParseResult) -> Array[@source.Span] {
  self.errors.map(fn(error) { error.span })
}

///|
pub fn ParseResult::error_summary(self : ParseResult) -> String {
  if self.errors.length() == 0 {
    "no syntax errors"
  } else {
    self.errors.length().to_string() +
    " syntax errors, recovered modules=" +
    self.design.modules.length().to_string()
  }
}

///|
pub fn ParseResult::module_item_counts(
  self : ParseResult,
) -> Array[(String, Int)] {
  let result : Array[(String, Int)] = []
  for mod in self.design.modules {
    result.push((mod.name, mod.items.length()))
  }
  result
}

///|
pub fn ParseResult::module_has_name(self : ParseResult, name : String) -> Bool {
  self.design.find_module(name) is Some(_)
}

///|
pub fn ParseResult::syntax_error_at(self : ParseResult, offset : Int) -> Bool {
  self.errors.any(fn(error) {
    offset >= error.span.start && offset <= error.span.end
  })
}

///|
pub fn ParseResult::summary(self : ParseResult) -> String {
  let metrics = self.metrics()
  "modules=" +
  metrics.modules.to_string() +
  ", ports=" +
  metrics.ports.to_string() +
  ", signals=" +
  metrics.signals.to_string() +
  ", items=" +
  metrics.items.to_string() +
  ", errors=" +
  metrics.syntax_errors.to_string()
}