///|
pub(all) struct ControlFlowSummary {
  assigned : Array[String]
  conditionally_assigned : Array[String]
  read : Array[String]
  sensitivity_names : Array[String]
  mut has_clock_edge : Bool
} derive(Eq, Debug)

///|
fn append_name(names : Array[String], expression : @syntax.Expression) -> Unit {
  match expression_name(expression) {
    Some(name) => if !names.contains(name) { names.push(name) }
    None => ()
  }
}

///|
fn collect_reads(
  expression : @syntax.Expression,
  names : Array[String],
) -> Unit {
  match expression {
    @syntax.Name(_, _) => append_name(names, expression)
    @syntax.Index(base, index, _) => {
      collect_reads(base, names)
      collect_reads(index, names)
    }
    @syntax.Unary(_, operand, _) => collect_reads(operand, names)
    @syntax.Binary(left, _, right, _) => {
      collect_reads(left, names)
      collect_reads(right, names)
    }
    @syntax.Number(_, _) | @syntax.StringLiteral(_, _) | @syntax.Unknown(_) =>
      ()
  }
}

///|
fn collect_statement_flow(
  statement : @syntax.Statement,
  assigned : Array[String],
  conditional : Array[String],
  reads : Array[String],
) -> Unit {
  match statement {
    @syntax.Assignment(target, value, _, _) => {
      append_name(assigned, target)
      collect_reads(value, reads)
    }
    @syntax.Block(statements, _) =>
      for child in statements {
        collect_statement_flow(child, assigned, conditional, reads)
      }
    @syntax.If(condition, then_branch, else_branch, _) => {
      collect_reads(condition, reads)
      let then_assigned : Array[String] = []
      let else_assigned : Array[String] = []
      collect_statement_flow(then_branch, then_assigned, [], reads)
      match else_branch {
        None =>
          for name in then_assigned {
            if !conditional.contains(name) {
              conditional.push(name)
            }
          }
        Some(branch) => {
          collect_statement_flow(branch, else_assigned, [], reads)
          for name in then_assigned {
            if else_assigned.contains(name) {
              if !assigned.contains(name) {
                assigned.push(name)
              }
            } else if !conditional.contains(name) {
              conditional.push(name)
            }
          }
          for name in else_assigned {
            if !then_assigned.contains(name) && !conditional.contains(name) {
              conditional.push(name)
            }
          }
        }
      }
    }
    @syntax.Case(expression, arms, _) => {
      collect_reads(expression, reads)
      let branch_names : Array[String] = []
      for arm in arms {
        let names : Array[String] = []
        collect_statement_flow(arm.body, names, [], reads)
        for name in names {
          if !branch_names.contains(name) {
            branch_names.push(name)
          }
        }
      }
      for name in branch_names {
        if !conditional.contains(name) {
          conditional.push(name)
        }
      }
    }
    @syntax.Empty(_) | @syntax.Unknown(_) => ()
  }
}

///|
fn merge_flow(target : ControlFlowSummary, source : ControlFlowSummary) -> Unit {
  for name in source.assigned {
    if !target.assigned.contains(name) {
      target.assigned.push(name)
    }
  }
  for name in source.conditionally_assigned {
    if !target.conditionally_assigned.contains(name) {
      target.conditionally_assigned.push(name)
    }
  }
  for name in source.read {
    if !target.read.contains(name) {
      target.read.push(name)
    }
  }
  for name in source.sensitivity_names {
    if !target.sensitivity_names.contains(name) {
      target.sensitivity_names.push(name)
    }
  }
  target.has_clock_edge = target.has_clock_edge || source.has_clock_edge
}

///|
/// Summarize procedural reads, writes, and sensitivity edges for one module.
pub fn SemanticIndex::control_flow(
  self : SemanticIndex,
  module_name : String,
) -> ControlFlowSummary? {
  let summary : ControlFlowSummary = {
    assigned: [],
    conditionally_assigned: [],
    read: [],
    sensitivity_names: [],
    has_clock_edge: false,
  }
  let mut found = false
  for mod in self.syntax_modules {
    if mod.name == module_name {
      found = true
      for item in mod.items {
        match item {
          @syntax.Always(always) => {
            let assigned : Array[String] = []
            let conditional : Array[String] = []
            let reads : Array[String] = []
            collect_statement_flow(always.body, assigned, conditional, reads)
            let flow : ControlFlowSummary = {
              assigned,
              conditionally_assigned: conditional,
              read: reads,
              sensitivity_names: [],
              has_clock_edge: false,
            }
            for sensitivity in always.sensitivity {
              collect_reads(sensitivity.expression, flow.sensitivity_names)
              if sensitivity.edge is @syntax.Posedge ||
                sensitivity.edge is @syntax.Negedge {
                flow.has_clock_edge = true
              }
            }
            merge_flow(summary, flow)
          }
          _ => ()
        }
      }
    }
  }
  if found {
    Some(summary)
  } else {
    None
  }
}