///|
/// A compact view of one physical or logical flow layer.
pub(all) struct FlowLayerHealth {
  flow_type : @types.FlowType
  active_edge_count : Int
  inactive_edge_count : Int
  total_bandwidth_units_per_hour : Double
  max_bandwidth_units_per_hour : Double
} derive(Debug, Eq)

///|
/// Structural and operational health indicators for a factory topology.
pub(all) struct GraphHealthReport {
  total_node_count : Int
  active_node_count : Int
  inactive_node_count : Int
  total_edge_count : Int
  active_edge_count : Int
  inactive_edge_count : Int
  orphan_node_ids : Array[String]
  layer_health : Array[FlowLayerHealth]
} derive(Debug, Eq)

///|
fn inspect_flow_layer(
  graph : FactoryGraph,
  flow_type : @types.FlowType,
) -> FlowLayerHealth {
  let mut active = 0
  let mut inactive = 0
  let mut total_bandwidth = 0.0
  let mut max_bandwidth = 0.0
  for edge in graph.get_all_edges() {
    if edge.flow_type() == flow_type {
      if edge.is_active() {
        active = active + 1
      } else {
        inactive = inactive + 1
      }
      let bandwidth = edge.bandwidth()
      total_bandwidth = total_bandwidth + bandwidth
      if bandwidth > max_bandwidth {
        max_bandwidth = bandwidth
      }
    }
  }
  {
    flow_type,
    active_edge_count: active,
    inactive_edge_count: inactive,
    total_bandwidth_units_per_hour: total_bandwidth,
    max_bandwidth_units_per_hour: max_bandwidth,
  }
}

///|
/// Inspect topology completeness and per-layer operational capacity.
///
/// Orphan nodes have no incoming or outgoing edge in any layer. Inactive
/// edges remain visible in the report so callers can distinguish a planned
/// maintenance path from a missing connection.
pub fn inspect_graph_health(graph : FactoryGraph) -> GraphHealthReport {
  let nodes = graph.get_all_nodes()
  let edges = graph.get_all_edges()
  let mut active_nodes = 0
  let orphan_ids = []
  for node in nodes {
    if node.is_operational() {
      active_nodes = active_nodes + 1
    }
    if graph.get_in_edges(node.id()).length() == 0 &&
      graph.get_out_edges(node.id()).length() == 0 {
      orphan_ids.push(node.id())
    }
  }

  let mut active_edges = 0
  for edge in edges {
    if edge.is_active() {
      active_edges = active_edges + 1
    }
  }

  let layer_health = [
    inspect_flow_layer(graph, @types.FlowType::MaterialFlow),
    inspect_flow_layer(graph, @types.FlowType::ControlFlow),
    inspect_flow_layer(graph, @types.FlowType::EnergyFlow),
    inspect_flow_layer(graph, @types.FlowType::SafetyInterlock),
    inspect_flow_layer(graph, @types.FlowType::PneumaticFlow),
    inspect_flow_layer(graph, @types.FlowType::CoolantFlow),
    inspect_flow_layer(graph, @types.FlowType::DataTelemetry),
  ]
  {
    total_node_count: nodes.length(),
    active_node_count: active_nodes,
    inactive_node_count: nodes.length() - active_nodes,
    total_edge_count: edges.length(),
    active_edge_count: active_edges,
    inactive_edge_count: edges.length() - active_edges,
    orphan_node_ids: orphan_ids,
    layer_health,
  }
}