///|
pub(all) struct CouplingLink {
  source_net : String
  source_node : String
  target_node : String
  target_net : String?
  capacitance : Double
  capacitor_id : Int
  resolved : Bool
} derive(Debug)

///|
pub(all) struct CouplingPair {
  first_net : String
  second_net : String
  capacitance : Double
  element_count : Int
} derive(Debug)

///|
pub(all) struct NetCouplingStats {
  net_name : String
  outgoing_capacitance : Double
  incoming_capacitance : Double
  internal_capacitance : Double
  unresolved_capacitance : Double
  resolved_links : Int
  unresolved_links : Int
  partners : Array[String]
  strongest_partner : String?
  strongest_partner_capacitance : Double
} derive(Debug)

///|
pub(all) struct CouplingReport {
  total_coupling_capacitance : Double
  resolved_capacitance : Double
  unresolved_capacitance : Double
  link_count : Int
  resolved_link_count : Int
  unresolved_link_count : Int
  links : Array[CouplingLink]
  pairs : Array[CouplingPair]
  nets : Array[NetCouplingStats]
} derive(Debug)

///|
fn node_name(net : @model.Net, node_id : Int) -> String? {
  match net.find_node(node_id) {
    Some(node) => Some(node.name)
    None => None
  }
}

///|
fn local_owner(network : @model.RCNetwork, name : String) -> String? {
  for net in network.nets {
    for node in net.nodes {
      if node.kind != @model.External && node.name == name {
        return Some(net.name)
      }
    }
  }
  None
}

///|
fn target_owner(
  network : @model.RCNetwork,
  source_net : String,
  target_name : String,
) -> String? {
  for net in network.nets {
    if net.name != source_net {
      for node in net.nodes {
        if node.kind != @model.External && node.name == target_name {
          return Some(net.name)
        }
      }
    }
  }
  local_owner(network, target_name)
}

///|
fn collect_links(network : @model.RCNetwork) -> Array[CouplingLink] {
  let links : Array[CouplingLink] = []
  for net in network.nets {
    for capacitor in net.capacitors {
      match capacitor.to_node {
        None => ()
        Some(target_id) => {
          let source_name = match node_name(net, capacitor.from_node) {
            Some(value) => value
            None => ""
          }
          let target_name = match node_name(net, target_id) {
            Some(value) => value
            None => ""
          }
          let owner = match net.find_node(target_id) {
            Some(node) =>
              if node.kind == @model.External {
                target_owner(network, net.name, target_name)
              } else {
                Some(net.name)
              }
            None => None
          }
          links.push({
            source_net: net.name,
            source_node: source_name,
            target_node: target_name,
            target_net: owner,
            capacitance: capacitor.capacitance,
            capacitor_id: capacitor.id,
            resolved: owner is Some(_),
          })
        }
      }
    }
  }
  links
}

///|
fn canonical_pair(first : String, second : String) -> (String, String) {
  if first.compare(second) <= 0 {
    (first, second)
  } else {
    (second, first)
  }
}

///|
fn find_pair(
  pairs : Array[CouplingPair],
  first : String,
  second : String,
) -> Int {
  for index in 0.. Array[CouplingPair] {
  let pairs : Array[CouplingPair] = []
  for link in links {
    match link.target_net {
      Some(target) => {
        let (first, second) = canonical_pair(link.source_net, target)
        let index = find_pair(pairs, first, second)
        if index == -1 {
          pairs.push({
            first_net: first,
            second_net: second,
            capacitance: link.capacitance,
            element_count: 1,
          })
        } else {
          let current = pairs[index]
          pairs[index] = {
            first_net: current.first_net,
            second_net: current.second_net,
            capacitance: current.capacitance + link.capacitance,
            element_count: current.element_count + 1,
          }
        }
      }
      None => ()
    }
  }
  pairs.sort_by(fn(left, right) {
    let capacitance_order = right.capacitance.compare(left.capacitance)
    if capacitance_order == 0 {
      let first_order = left.first_net.compare(right.first_net)
      if first_order == 0 {
        left.second_net.compare(right.second_net)
      } else {
        first_order
      }
    } else {
      capacitance_order
    }
  })
  pairs
}

///|
fn add_unique(values : Array[String], value : String) -> Unit {
  if !values.contains(value) {
    values.push(value)
  }
}

///|
fn partner_totals(
  pairs : Array[CouplingPair],
  net_name : String,
) -> Array[(String, Double)] {
  let totals : Array[(String, Double)] = []
  for pair in pairs {
    if pair.first_net == net_name && pair.second_net != net_name {
      totals.push((pair.second_net, pair.capacitance))
    } else if pair.second_net == net_name && pair.first_net != net_name {
      totals.push((pair.first_net, pair.capacitance))
    }
  }
  totals.sort_by(fn(left, right) {
    let value_order = right.1.compare(left.1)
    if value_order == 0 {
      left.0.compare(right.0)
    } else {
      value_order
    }
  })
  totals
}

///|
fn stats_for_net(
  net : @model.Net,
  links : Array[CouplingLink],
  pairs : Array[CouplingPair],
) -> NetCouplingStats {
  let mut outgoing = 0.0
  let mut incoming = 0.0
  let mut internal = 0.0
  let mut unresolved = 0.0
  let mut resolved_links = 0
  let mut unresolved_links = 0
  let partners : Array[String] = []
  for link in links {
    if link.source_net == net.name {
      match link.target_net {
        Some(target) => {
          resolved_links += 1
          if target == net.name {
            internal += link.capacitance
          } else {
            outgoing += link.capacitance
            add_unique(partners, target)
          }
        }
        None => {
          unresolved += link.capacitance
          unresolved_links += 1
        }
      }
    }
    match link.target_net {
      Some(target) =>
        if target == net.name && link.source_net != net.name {
          incoming += link.capacitance
          add_unique(partners, link.source_net)
        }
      None => ()
    }
  }
  partners.sort()
  let ranked = partner_totals(pairs, net.name)
  let strongest_partner = if ranked.length() == 0 {
    None
  } else {
    Some(ranked[0].0)
  }
  {
    net_name: net.name,
    outgoing_capacitance: outgoing,
    incoming_capacitance: incoming,
    internal_capacitance: internal,
    unresolved_capacitance: unresolved,
    resolved_links,
    unresolved_links,
    partners,
    strongest_partner,
    strongest_partner_capacitance: if ranked.length() == 0 {
      0.0
    } else {
      ranked[0].1
    },
  }
}

///|
/// Builds a design-level coupling index from the endpoint names retained by
/// the parser. Coupling to nodes in another D_NET is resolved by exact expanded
/// SPEF name; unknown external endpoints remain explicit in the report.
pub fn analyze(network : @model.RCNetwork) -> CouplingReport {
  let links = collect_links(network)
  let pairs = aggregate_pairs(links)
  let nets : Array[NetCouplingStats] = []
  let mut total = 0.0
  let mut resolved_capacitance = 0.0
  let mut unresolved_capacitance = 0.0
  let mut resolved_link_count = 0
  let mut unresolved_link_count = 0
  for link in links {
    total += link.capacitance
    if link.resolved {
      resolved_capacitance += link.capacitance
      resolved_link_count += 1
    } else {
      unresolved_capacitance += link.capacitance
      unresolved_link_count += 1
    }
  }
  for net in network.nets {
    nets.push(stats_for_net(net, links, pairs))
  }
  {
    total_coupling_capacitance: total,
    resolved_capacitance,
    unresolved_capacitance,
    link_count: links.length(),
    resolved_link_count,
    unresolved_link_count,
    links,
    pairs,
    nets,
  }
}

///|
pub fn CouplingReport::strongest_pairs(
  self : CouplingReport,
  limit? : Int = 10,
) -> Array[CouplingPair] {
  if limit <= 0 {
    return []
  }
  if self.pairs.length() <= limit {
    return self.pairs.copy()
  }
  let result : Array[CouplingPair] = []
  for index in 0.. NetCouplingStats? {
  self.nets.iter().find_first(fn(net) { net.net_name == name })
}