use std::collections::BTreeMap;
use petgraph::algo::toposort;
use petgraph::graph::{DiGraph, NodeIndex};
use super::event::{NodeStatus, PlanStatus};
use super::ids::{NodeId, PlanId};
use super::state::Funnel;
#[derive(Debug, Clone, serde::Serialize)]
pub struct NextStep {
pub plan_id: PlanId,
pub node_id: NodeId,
pub kind: String,
pub targets: Vec<String>,
pub summary: String,
pub prompt_excerpt: Option<String>,
}
pub fn topo_ready(funnel: &mut Funnel) -> Vec<NextStep> {
funnel.promote_ready();
let mut out: Vec<NextStep> = Vec::new();
for plan in funnel.plans.values() {
if !matches!(plan.status, PlanStatus::Draft | PlanStatus::Active) {
continue;
}
let mut graph: DiGraph<NodeId, ()> = DiGraph::new();
let mut idx: BTreeMap<NodeId, NodeIndex> = BTreeMap::new();
for id in plan.nodes.keys() {
idx.insert(id.clone(), graph.add_node(id.clone()));
}
for (from, to) in &plan.edges {
if let (Some(&a), Some(&b)) = (idx.get(from), idx.get(to)) {
graph.add_edge(a, b, ());
}
}
let order = match toposort(&graph, None) {
Ok(o) => o,
Err(cyc) => {
eprintln!(
"funnel: plan `{}` has a cycle around node `{}` β skipping",
plan.id,
graph[cyc.node_id()]
);
continue;
}
};
for ni in order {
let nid = &graph[ni];
let node = &plan.nodes[nid];
if matches!(node.status, NodeStatus::Ready | NodeStatus::Pending) {
if node.status == NodeStatus::Ready {
out.push(NextStep {
plan_id: plan.id.clone(),
node_id: nid.clone(),
kind: node.kind.clone(),
targets: node.targets.clone(),
summary: plan.summary.clone(),
prompt_excerpt: node.prompt_excerpt.clone(),
});
}
}
}
}
out
}
pub fn topo_ready_for_plan(funnel: &mut Funnel, plan_id: &PlanId) -> Vec<NextStep> {
topo_ready(funnel)
.into_iter()
.filter(|s| &s.plan_id == plan_id)
.collect()
}