use crate::intent_graph::*;
use std::collections::{HashMap, HashSet, VecDeque};
#[derive(Debug, Default, Clone)]
pub struct GraphQuery {
node_type: Option<NodeType>,
status: Option<Status>,
dag_stage: Option<DagStage>,
tags: Vec<String>,
title_contains: Option<String>,
relationship_type: Option<RelationshipType>,
source_type: Option<NodeType>,
target_type: Option<NodeType>,
}
impl GraphQuery {
pub fn new() -> Self {
Self::default()
}
pub fn node_type(mut self, nt: NodeType) -> Self {
self.node_type = Some(nt);
self
}
pub fn status(mut self, s: Status) -> Self {
self.status = Some(s);
self
}
pub fn dag_stage(mut self, ds: DagStage) -> Self {
self.dag_stage = Some(ds);
self
}
pub fn tag(mut self, t: impl Into<String>) -> Self {
self.tags.push(t.into());
self
}
pub fn tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
pub fn title_contains(mut self, t: impl Into<String>) -> Self {
self.title_contains = Some(t.into());
self
}
pub fn relationship_type(mut self, rt: RelationshipType) -> Self {
self.relationship_type = Some(rt);
self
}
pub fn source_type(mut self, nt: NodeType) -> Self {
self.source_type = Some(nt);
self
}
pub fn target_type(mut self, nt: NodeType) -> Self {
self.target_type = Some(nt);
self
}
pub fn execute<'a>(&self, graph: &'a IntentGraph) -> Vec<&'a Node> {
graph
.nodes
.iter()
.filter(|n| {
self.node_type.is_none_or(|t| n.node_type == t)
&& self.status.is_none_or(|s| n.status == s)
&& self.dag_stage.is_none_or(|ds| n.dag_stage == ds)
&& self.tags.iter().all(|t| n.tags.contains(t))
&& self
.title_contains
.as_ref()
.is_none_or(|pat| n.title.contains(pat))
})
.collect()
}
pub fn execute_edges<'a>(&self, graph: &'a IntentGraph) -> Vec<&'a Edge> {
graph
.edges
.iter()
.filter(|e| {
let source_type_ok = self.source_type.is_none_or(|nt| {
graph
.node_by_id(&e.source)
.is_some_and(|n| n.node_type == nt)
});
let target_type_ok = self.target_type.is_none_or(|nt| {
graph
.node_by_id(&e.target)
.is_some_and(|n| n.node_type == nt)
});
self.relationship_type
.is_none_or(|rt| e.relationship_type == rt)
&& source_type_ok
&& target_type_ok
})
.collect()
}
}
pub fn find_roots(graph: &IntentGraph) -> Vec<&Node> {
let targets: HashSet<NodeId> = graph.edges.iter().map(|e| e.target.clone()).collect();
graph
.nodes
.iter()
.filter(|n| !targets.contains(&n.id))
.collect()
}
pub fn find_leaves(graph: &IntentGraph) -> Vec<&Node> {
let sources: HashSet<NodeId> = graph.edges.iter().map(|e| e.source.clone()).collect();
graph
.nodes
.iter()
.filter(|n| !sources.contains(&n.id))
.collect()
}
pub fn find_path<'a>(
graph: &'a IntentGraph,
from: &NodeId,
to: &NodeId,
) -> Option<Vec<&'a Node>> {
if from == to {
return graph.node_by_id(from).map(|n| vec![n]);
}
let mut queue: VecDeque<(NodeId, Vec<NodeId>)> = VecDeque::new();
queue.push_back((from.clone(), vec![from.clone()]));
let mut visited: HashSet<NodeId> = HashSet::new();
visited.insert(from.clone());
while let Some((current_id, path_ids)) = queue.pop_front() {
for edge in graph.edges_from(¤t_id) {
let next_id = edge.target.clone();
if visited.contains(&next_id) {
continue;
}
let mut next_path = path_ids.clone();
next_path.push(next_id.clone());
if next_id == *to {
let nodes: Vec<&Node> = next_path
.iter()
.map(|id| graph.node_by_id(id).unwrap())
.collect();
return Some(nodes);
}
visited.insert(next_id.clone());
queue.push_back((next_id, next_path));
}
}
None
}
pub fn topological_sort(graph: &IntentGraph) -> Result<Vec<&Node>, String> {
let mut in_degree: HashMap<NodeId, usize> =
graph.nodes.iter().map(|n| (n.id.clone(), 0)).collect();
for edge in &graph.edges {
*in_degree.entry(edge.target.clone()).or_insert(0) += 1;
}
let mut queue: VecDeque<NodeId> = VecDeque::new();
for node in &graph.nodes {
if in_degree.get(&node.id).copied().unwrap_or(0) == 0 {
queue.push_back(node.id.clone());
}
}
let mut sorted: Vec<&Node> = Vec::new();
let mut processed = 0;
while let Some(node_id) = queue.pop_front() {
let node = graph
.node_by_id(&node_id)
.ok_or_else(|| format!("Node {} not found", node_id))?;
sorted.push(node);
processed += 1;
for edge in graph.edges_from(&node_id) {
let target = &edge.target;
let degree = in_degree
.get_mut(target)
.ok_or_else(|| format!("Node {} not found", target))?;
*degree -= 1;
if *degree == 0 {
queue.push_back(target.clone());
}
}
}
if processed != graph.nodes.len() {
return Err("Graph contains cycles and cannot be topologically sorted".to_string());
}
Ok(sorted)
}