use crate::ir::{Module, Node, NodeId};
use petgraph::Direction;
use std::collections::{HashMap, HashSet};
pub fn resolve_key(module: &Module, key: &str) -> Option<NodeId> {
module.resolve_value_path(key)
}
pub fn node_label(module: &Module, id: NodeId) -> String {
let mut paths: HashMap<NodeId, String> = HashMap::new();
for (name, binding) in &module.bindings {
paths.insert(*binding, name.clone());
}
for (node, path) in module.value_paths() {
paths.insert(node, path);
}
paths
.get(&id)
.cloned()
.unwrap_or_else(|| format!("node{}", id.0))
}
pub fn transitive_dependents(module: &Module, id: NodeId) -> Vec<NodeId> {
walk(module, id, Direction::Outgoing)
}
pub fn transitive_dependencies(module: &Module, id: NodeId) -> Vec<NodeId> {
walk(module, id, Direction::Incoming)
}
fn walk(module: &Module, start: NodeId, direction: Direction) -> Vec<NodeId> {
let Some(&start_idx) = module.node_to_graph.get(&start) else {
return Vec::new();
};
let mut seen = HashSet::from([start]);
let mut out = Vec::new();
let mut stack = vec![start_idx];
while let Some(idx) = stack.pop() {
let mut neighbors: Vec<_> = module.graph.neighbors_directed(idx, direction).collect();
neighbors.reverse();
for neighbor in neighbors {
let id = module.graph[neighbor];
if seen.insert(id) {
out.push(id);
stack.push(neighbor);
}
}
}
out
}
pub fn impact_paths(module: &Module, id: NodeId) -> Vec<String> {
let mut paths: Vec<String> = transitive_dependents(module, id)
.into_iter()
.filter(|node| !matches!(module.get(*node), Some(Node::Table { .. })))
.map(|node| node_label(module, node))
.collect();
paths.sort();
paths.dedup();
paths
}
pub fn why_paths(module: &Module, id: NodeId) -> Vec<String> {
let mut paths: Vec<String> = transitive_dependencies(module, id)
.into_iter()
.map(|node| node_label(module, node))
.collect();
paths.sort();
paths
}
fn render_id(id: NodeId) -> String {
format!("n{}", id.0)
}
fn sorted_nodes(module: &Module) -> Vec<(String, String)> {
let mut nodes: Vec<(String, String)> = module
.node_to_graph
.keys()
.map(|id| (render_id(*id), node_label(module, *id)))
.collect();
nodes.sort();
nodes
}
fn sorted_edges(module: &Module) -> Vec<(String, String)> {
let mut edges = Vec::new();
for edge in module.graph.raw_edges() {
let from = render_id(module.graph[edge.source()]);
let to = render_id(module.graph[edge.target()]);
edges.push((from, to));
}
edges.sort();
edges
}
pub fn to_mermaid(module: &Module) -> String {
let mut out = String::from("flowchart TD\n");
for (id, label) in sorted_nodes(module) {
out.push_str(&format!(" {id}[\"{label}\"]\n"));
}
for (from, to) in sorted_edges(module) {
out.push_str(&format!(" {from} --> {to}\n"));
}
out
}
pub fn to_dot(module: &Module) -> String {
fn escape(s: &str) -> String {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
let mut out = String::from("digraph active {\n");
for (id, label) in sorted_nodes(module) {
out.push_str(&format!(" \"{id}\" [label=\"{}\"];\n", escape(&label)));
}
for (from, to) in sorted_edges(module) {
out.push_str(&format!(" \"{from}\" -> \"{to}\";\n"));
}
out.push_str("}\n");
out
}
pub fn to_graph_json(module: &Module) -> serde_json::Value {
let nodes: Vec<serde_json::Value> = sorted_nodes(module)
.into_iter()
.map(|(id, label)| serde_json::json!({"id": id, "label": label}))
.collect();
let edges: Vec<serde_json::Value> = sorted_edges(module)
.into_iter()
.map(|(from, to)| serde_json::json!({"from": from, "to": to}))
.collect();
serde_json::json!({"nodes": nodes, "edges": edges})
}