use crate::domain::{Dependency, DependencyType, Issue, IssueId, IssueStatus};
use crate::error::{Error, Result};
use petgraph::algo;
use petgraph::graph::{DiGraph, NodeIndex};
use petgraph::visit::EdgeRef;
use petgraph::Direction;
use std::collections::{HashMap, HashSet, VecDeque};
const MAX_BLOCKING_DEPTH: usize = 50;
pub(super) fn get_dependency_tree_impl(
graph: &DiGraph<IssueId, DependencyType>,
node_map: &HashMap<IssueId, NodeIndex>,
id: &IssueId,
max_depth: Option<usize>,
) -> Result<Vec<(Dependency, usize)>> {
let start_node = node_map
.get(id)
.ok_or_else(|| Error::IssueNotFound(id.clone()))?;
let mut result = Vec::new();
let mut visited = HashSet::new();
let mut queue: VecDeque<(NodeIndex, usize)> = VecDeque::new();
for edge in graph.edges(*start_node) {
let target_node = edge.target();
if visited.insert(target_node) {
queue.push_back((target_node, 1));
result.push((
Dependency {
depends_on_id: graph[target_node].clone(),
dep_type: *edge.weight(),
},
1,
));
}
}
while let Some((current_node, depth)) = queue.pop_front() {
if let Some(max) = max_depth {
if depth >= max {
continue;
}
}
for edge in graph.edges(current_node) {
let target_node = edge.target();
if visited.insert(target_node) {
let next_depth = depth + 1;
queue.push_back((target_node, next_depth));
result.push((
Dependency {
depends_on_id: graph[target_node].clone(),
dep_type: *edge.weight(),
},
next_depth,
));
}
}
}
Ok(result)
}
pub(super) fn has_cycle_impl(
graph: &DiGraph<IssueId, DependencyType>,
node_map: &HashMap<IssueId, NodeIndex>,
from: &IssueId,
to: &IssueId,
) -> Result<bool> {
let from_node = node_map
.get(from)
.ok_or_else(|| Error::IssueNotFound(from.clone()))?;
let to_node = node_map
.get(to)
.ok_or_else(|| Error::IssueNotFound(to.clone()))?;
Ok(algo::has_path_connecting(graph, *to_node, *from_node, None))
}
pub(super) fn find_blocked_issues(
graph: &DiGraph<IssueId, DependencyType>,
node_map: &HashMap<IssueId, NodeIndex>,
issues: &HashMap<IssueId, Issue>,
) -> HashSet<IssueId> {
let mut blocked = HashSet::new();
for (id, issue) in issues {
if issue.status == IssueStatus::Closed {
continue;
}
let Some(&node) = node_map.get(id) else {
continue;
};
for edge in graph.edges(node) {
if edge.weight() == &DependencyType::Blocks {
let blocker_id = &graph[edge.target()];
if let Some(blocker) = issues.get(blocker_id) {
if blocker.status != IssueStatus::Closed {
blocked.insert(id.clone());
break;
}
}
}
}
}
let mut to_process: VecDeque<(IssueId, usize)> =
blocked.iter().map(|id| (id.clone(), 0)).collect();
while let Some((id, depth)) = to_process.pop_front() {
if depth >= MAX_BLOCKING_DEPTH {
continue;
}
let Some(&node) = node_map.get(&id) else {
continue;
};
for edge in graph.edges_directed(node, Direction::Incoming) {
if edge.weight() == &DependencyType::ParentChild {
let child_id = &graph[edge.source()];
if blocked.insert(child_id.clone()) {
to_process.push_back((child_id.clone(), depth + 1));
}
}
}
}
blocked
}