///|
pub(all) struct NetMetrics {
  net_id : Int
  name : String
  node_count : Int
  connection_count : Int
  resistor_count : Int
  capacitor_count : Int
  total_resistance : Double
  detailed_capacitance : Double
  declared_capacitance : Double
  capacitance_delta : Double
  connected_components : Int
  isolated_node_count : Int
  has_cycle : Bool
  max_degree : Int
  complexity_score : Int
  anomalous : Bool
  anomaly_reasons : Array[String]
} derive(Debug)

///|
pub(all) struct DesignMetrics {
  net_count : Int
  node_count : Int
  connection_count : Int
  resistor_count : Int
  capacitor_count : Int
  total_resistance : Double
  total_capacitance : Double
  average_net_resistance : Double
  average_net_capacitance : Double
  max_resistance_net : String?
  max_capacitance_net : String?
  anomalous_net_count : Int
  anomalous_nets : Array[String]
  nets : Array[NetMetrics]
} derive(Debug)

///|
pub fn analyze_net(net : @model.Net) -> NetMetrics {
  let topology = @graph.summarize(net)
  let detailed_capacitance = net.effective_capacitance()
  let anomaly_reasons : Array[String] = []
  if net.nodes.length() == 0 {
    anomaly_reasons.push("empty network")
  }
  if net.connections.length() == 0 {
    anomaly_reasons.push("no connections")
  }
  if topology.connected_components > 1 {
    anomaly_reasons.push("disconnected resistance topology")
  }
  if topology.isolated_nodes.length() > 0 {
    anomaly_reasons.push("isolated nodes")
  }
  if topology.has_cycle {
    anomaly_reasons.push("resistance cycle")
  }
  if topology.driver_nodes.length() != 1 {
    anomaly_reasons.push("driver count is not one")
  }
  for resistor in net.resistors {
    if resistor.resistance < 0.0 ||
      resistor.resistance.is_nan() ||
      resistor.resistance.is_inf() {
      anomaly_reasons.push("invalid resistance")
      break
    }
  }
  for capacitor in net.capacitors {
    if capacitor.capacitance < 0.0 ||
      capacitor.capacitance.is_nan() ||
      capacitor.capacitance.is_inf() {
      anomaly_reasons.push("invalid capacitance")
      break
    }
  }
  let cap_scale = if detailed_capacitance > net.declared_capacitance {
    detailed_capacitance
  } else {
    net.declared_capacitance
  }
  if cap_scale > 0.0 &&
    (detailed_capacitance - net.declared_capacitance).abs() / cap_scale > 0.05 {
    anomaly_reasons.push("declared capacitance mismatch")
  }
  {
    net_id: net.id,
    name: net.name,
    node_count: net.nodes.length(),
    connection_count: net.connections.length(),
    resistor_count: net.resistors.length(),
    capacitor_count: net.capacitors.length(),
    total_resistance: net.total_resistance(),
    detailed_capacitance,
    declared_capacitance: net.declared_capacitance,
    capacitance_delta: detailed_capacitance - net.declared_capacitance,
    connected_components: topology.connected_components,
    isolated_node_count: topology.isolated_nodes.length(),
    has_cycle: topology.has_cycle,
    max_degree: topology.max_degree,
    complexity_score: net.nodes.length() +
    net.resistors.length() +
    net.capacitors.length() +
    topology.max_degree * 2,
    anomalous: anomaly_reasons.length() > 0,
    anomaly_reasons,
  }
}

///|
pub fn analyze(network : @model.RCNetwork) -> DesignMetrics {
  let nets : Array[NetMetrics] = []
  let mut node_count = 0
  let mut connection_count = 0
  let mut resistor_count = 0
  let mut capacitor_count = 0
  let mut total_resistance = 0.0
  let mut total_capacitance = 0.0
  let mut max_resistance = -1.0
  let mut max_capacitance = -1.0
  let mut max_resistance_net : String? = None
  let mut max_capacitance_net : String? = None
  let anomalous_nets : Array[String] = []
  for net in network.nets {
    let metrics = analyze_net(net)
    node_count += metrics.node_count
    connection_count += metrics.connection_count
    resistor_count += metrics.resistor_count
    capacitor_count += metrics.capacitor_count
    total_resistance += metrics.total_resistance
    total_capacitance += metrics.detailed_capacitance
    if metrics.total_resistance > max_resistance {
      max_resistance = metrics.total_resistance
      max_resistance_net = Some(metrics.name)
    }
    if metrics.detailed_capacitance > max_capacitance {
      max_capacitance = metrics.detailed_capacitance
      max_capacitance_net = Some(metrics.name)
    }
    if metrics.anomalous {
      anomalous_nets.push(metrics.name)
    }
    nets.push(metrics)
  }
  let count = network.nets.length()
  {
    net_count: count,
    node_count,
    connection_count,
    resistor_count,
    capacitor_count,
    total_resistance,
    total_capacitance,
    average_net_resistance: if count == 0 {
      0.0
    } else {
      total_resistance / count.to_double()
    },
    average_net_capacitance: if count == 0 {
      0.0
    } else {
      total_capacitance / count.to_double()
    },
    max_resistance_net,
    max_capacitance_net,
    anomalous_net_count: anomalous_nets.length(),
    anomalous_nets,
    nets,
  }
}

///|
pub fn highest_complexity(
  metrics : DesignMetrics,
  limit? : Int = 10,
) -> Array[NetMetrics] {
  let result = metrics.nets.copy()
  result.sort_by(fn(left, right) {
    right.complexity_score.compare(left.complexity_score)
  })
  if result.length() <= limit {
    result
  } else {
    let trimmed : Array[NetMetrics] = []
    for index in 0..