use std::collections::{BTreeSet, HashMap};
use uuid::Uuid;
pub struct Node<'a> {
pub id: Uuid,
pub group: Option<Uuid>,
pub terminal: bool,
pub needs: &'a [Uuid],
pub after: &'a [Uuid],
}
pub fn held_by_unfinished(nodes: &[Node]) -> BTreeSet<Uuid> {
let by_id: HashMap<Uuid, &Node> = nodes.iter().map(|n| (n.id, n)).collect();
let live_groups: BTreeSet<Uuid> = nodes
.iter()
.filter(|n| !n.terminal)
.filter_map(|n| n.group)
.collect();
let mut held = BTreeSet::new();
let mut todo: Vec<Uuid> = nodes
.iter()
.filter(|n| !n.terminal)
.flat_map(|n| n.needs.iter().chain(n.after.iter()).copied())
.collect();
while let Some(id) = todo.pop() {
if !held.insert(id) {
continue;
}
if let Some(node) = by_id.get(&id) {
todo.extend(node.needs.iter().chain(node.after.iter()).copied());
}
}
for node in nodes {
if node.group.is_some_and(|g| live_groups.contains(&g)) {
held.insert(node.id);
}
}
held
}
pub enum Hold {
Needed(Vec<Uuid>),
Pipeline(Vec<Uuid>),
}
pub fn hold_reason(nodes: &[Node], id: Uuid) -> Option<Hold> {
let direct: Vec<Uuid> = nodes
.iter()
.filter(|n| !n.terminal)
.filter(|n| n.needs.contains(&id) || n.after.contains(&id))
.map(|n| n.id)
.collect();
if !direct.is_empty() {
return Some(Hold::Needed(direct));
}
let by_id: HashMap<Uuid, &Node> = nodes.iter().map(|n| (n.id, n)).collect();
let through_chain: Vec<Uuid> = nodes
.iter()
.filter(|n| !n.terminal)
.filter(|n| reaches(&by_id, n, id))
.map(|n| n.id)
.collect();
if !through_chain.is_empty() {
return Some(Hold::Needed(through_chain));
}
let group = nodes.iter().find(|n| n.id == id).and_then(|n| n.group)?;
let same: Vec<Uuid> = nodes
.iter()
.filter(|n| !n.terminal && n.group == Some(group))
.map(|n| n.id)
.collect();
if same.is_empty() {
return None;
}
Some(Hold::Pipeline(same))
}
fn reaches(by_id: &HashMap<Uuid, &Node>, from: &Node, id: Uuid) -> bool {
let mut seen = BTreeSet::new();
let mut todo: Vec<Uuid> = from
.needs
.iter()
.chain(from.after.iter())
.copied()
.collect();
while let Some(next) = todo.pop() {
if next == id {
return true;
}
if !seen.insert(next) {
continue;
}
if let Some(node) = by_id.get(&next) {
todo.extend(node.needs.iter().chain(node.after.iter()).copied());
}
}
false
}