///|
pub(all) struct FailoverRoutePlan {
  failed_node_id : String
  alternate_route_found : Bool
  suggested_bypass_path : Array[String]
  derated_capacity_units_per_hour : Double
  action_summary : String
} derive(Debug, Eq)

///|
pub fn synthesize_failover_routing(
  graph : @graph.FactoryGraph,
  failed_node_id : String,
  source_id : String,
  target_id : String,
) -> FailoverRoutePlan {
  // Clone graph and mark failed node inactive
  let failover_graph = graph.clone()
  match failover_graph.get_node(failed_node_id) {
    Some(n) => n.set_status(@types.OperatingStatus::Offline)
    None => ()
  }

  // Deactivate all edges touching failed node
  let in_e = failover_graph.get_in_edges(failed_node_id)
  for e in in_e {
    e.set_active(false)
  }
  let out_e = failover_graph.get_out_edges(failed_node_id)
  for e in out_e {
    e.set_active(false)
  }

  let path_opt = @algorithms.dijkstra_shortest_path(
    failover_graph,
    source_id,
    target_id,
    flow_type=@types.FlowType::MaterialFlow,
  )

  match path_opt {
    Some(p) =>
      {
        failed_node_id,
        alternate_route_found: true,
        suggested_bypass_path: p.nodes,
        derated_capacity_units_per_hour: p.bottleneck_bandwidth,
        action_summary: "Dynamic rerouting active: diverting material via " +
        p.nodes.length().to_string() +
        " stations at " +
        p.bottleneck_bandwidth.to_string() +
        " units/hour.",
      }
    None =>
      {
        failed_node_id,
        alternate_route_found: false,
        suggested_bypass_path: [],
        derated_capacity_units_per_hour: 0.0,
        action_summary: "No valid alternate path exists between " +
        source_id +
        " and " +
        target_id +
        "; production line paused.",
      }
  }
}