///|
pub(all) struct SignalUse {
  module_name : String
  signal_name : String
  context : String
  span : @source.Span
} derive(Eq, Debug)

///|
fn collect_expression_uses(
  module_name : String,
  expression : @syntax.Expression,
  context : String,
  result : Array[SignalUse],
) -> Unit {
  match expression {
    @syntax.Name(name, span) =>
      result.push({ module_name, signal_name: name, context, span })
    @syntax.Index(base, index, _) => {
      collect_expression_uses(module_name, base, context, result)
      collect_expression_uses(module_name, index, context, result)
    }
    @syntax.Unary(_, operand, _) =>
      collect_expression_uses(module_name, operand, context, result)
    @syntax.Binary(left, _, right, _) => {
      collect_expression_uses(module_name, left, context, result)
      collect_expression_uses(module_name, right, context, result)
    }
    @syntax.Number(_, _) | @syntax.StringLiteral(_, _) | @syntax.Unknown(_) =>
      ()
  }
}

///|
fn collect_statement_uses(
  module_name : String,
  statement : @syntax.Statement,
  result : Array[SignalUse],
) -> Unit {
  match statement {
    @syntax.Assignment(target, value, _, span) => {
      collect_expression_uses(module_name, target, "assignment-target", result)
      collect_expression_uses(module_name, value, "assignment-value", result)
      if target.name_text() is None {
        result.push({
          module_name,
          signal_name: "",
          context: "assignment-target",
          span,
        })
      }
    }
    @syntax.Block(statements, _) =>
      for child in statements {
        collect_statement_uses(module_name, child, result)
      }
    @syntax.If(condition, then_branch, else_branch, _) => {
      collect_expression_uses(module_name, condition, "condition", result)
      collect_statement_uses(module_name, then_branch, result)
      match else_branch {
        Some(branch) => collect_statement_uses(module_name, branch, result)
        None => ()
      }
    }
    @syntax.Case(expression, arms, _) => {
      collect_expression_uses(
        module_name, expression, "case-expression", result,
      )
      for arm in arms {
        for choice in arm.choices {
          collect_expression_uses(module_name, choice, "case-choice", result)
        }
        collect_statement_uses(module_name, arm.body, result)
      }
    }
    @syntax.Empty(_) | @syntax.Unknown(_) => ()
  }
}

///|
pub fn SemanticIndex::uses(self : SemanticIndex) -> Array[SignalUse] {
  let result : Array[SignalUse] = []
  for mod in self.syntax_modules {
    for item in mod.items {
      match item {
        @syntax.Assign(assign) => {
          collect_expression_uses(
            mod.name,
            assign.target,
            "continuous-target",
            result,
          )
          collect_expression_uses(
            mod.name,
            assign.value,
            "continuous-value",
            result,
          )
        }
        @syntax.Always(always) =>
          collect_statement_uses(mod.name, always.body, result)
        @syntax.Statement(statement) =>
          collect_statement_uses(mod.name, statement, result)
        @syntax.Instance(instance) =>
          for connection in instance.connections {
            collect_expression_uses(
              mod.name,
              connection.expression,
              "instance-connection",
              result,
            )
          }
        @syntax.Unknown(_) => ()
      }
    }
  }
  result
}

///|
pub fn SemanticIndex::uses_of(
  self : SemanticIndex,
  module_name : String,
  signal_name : String,
) -> Array[SignalUse] {
  self
  .uses()
  .filter(fn(usage) {
    usage.module_name == module_name && usage.signal_name == signal_name
  })
}

///|
pub fn SemanticIndex::consumer_count(
  self : SemanticIndex,
  module_name : String,
  signal_name : String,
) -> Int {
  self.uses_of(module_name, signal_name).length()
}

///|
pub fn SemanticIndex::port_direction(
  self : SemanticIndex,
  module_name : String,
  port_name : String,
) -> @syntax.Direction? {
  match self.find_module(module_name) {
    None => None
    Some(mod) => {
      let mut result : @syntax.Direction? = None
      for port in mod.ports {
        if port.name == port_name {
          result = Some(port.direction)
        }
      }
      result
    }
  }
}

///|
pub fn SemanticIndex::connection_count(
  self : SemanticIndex,
  module_name : String,
) -> Int {
  let mut count = 0
  for mod in self.modules {
    if mod.name == module_name {
      for instance in mod.instances {
        for syntax_mod in self.syntax_modules {
          if syntax_mod.name == module_name {
            for item in syntax_mod.items {
              match item {
                @syntax.Instance(value) =>
                  if value.instance_name == instance.instance_name {
                    count += value.connections.length()
                  }
                _ => ()
              }
            }
          }
        }
      }
    }
  }
  count
}

///|
pub fn SemanticIndex::unresolved_names(self : SemanticIndex) -> Array[String] {
  let names : Array[String] = []
  for issue in self.issues {
    if issue.code == "RTL102" {
      if !names.contains(issue.message) {
        names.push(issue.message)
      }
    }
  }
  names
}

///|
pub fn SemanticIndex::issue_summary(self : SemanticIndex) -> String {
  let codes = self.issue_codes()
  if codes.length() == 0 {
    return "no semantic issues"
  }
  let builder = StringBuilder()
  for index, code in codes {
    if index > 0 {
      builder.write_string(", ")
    }
    builder.write_string(code)
    builder.write_char('=')
    builder.write_object(self.issues_for_code(code).length())
  }
  builder.to_string()
}