use crate::models::{CommandInfo, StructInfo};
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
#[derive(Debug, Default)]
pub struct TypeDependencyGraph {
pub type_definitions: HashMap<String, PathBuf>,
pub dependencies: HashMap<String, HashSet<String>>,
pub resolved_types: HashMap<String, StructInfo>,
}
impl TypeDependencyGraph {
pub fn new() -> Self {
Self::default()
}
pub fn add_type_definition(&mut self, type_name: String, file_path: PathBuf) {
self.type_definitions.insert(type_name, file_path);
}
pub fn add_dependency(&mut self, dependent: String, dependency: String) {
self.dependencies
.entry(dependent)
.or_default()
.insert(dependency);
}
pub fn add_dependencies(&mut self, dependent: String, dependencies: HashSet<String>) {
self.dependencies.insert(dependent, dependencies);
}
pub fn add_resolved_type(&mut self, type_name: String, struct_info: StructInfo) {
self.resolved_types.insert(type_name, struct_info);
}
pub fn get_resolved_types(&self) -> &HashMap<String, StructInfo> {
&self.resolved_types
}
pub fn get_dependencies(&self, type_name: &str) -> Option<&HashSet<String>> {
self.dependencies.get(type_name)
}
pub fn has_type_definition(&self, type_name: &str) -> bool {
self.type_definitions.contains_key(type_name)
}
pub fn get_type_definition_path(&self, type_name: &str) -> Option<&PathBuf> {
self.type_definitions.get(type_name)
}
pub fn topological_sort_types(&self, types: &HashSet<String>) -> Vec<String> {
let mut sorted = Vec::new();
let mut visited = HashSet::new();
let mut visiting = HashSet::new();
for type_name in types {
if !visited.contains(type_name) {
self.topological_visit(type_name, &mut sorted, &mut visited, &mut visiting);
}
}
sorted
}
fn topological_visit(
&self,
type_name: &str,
sorted: &mut Vec<String>,
visited: &mut HashSet<String>,
visiting: &mut HashSet<String>,
) {
if visiting.contains(type_name) {
eprintln!(
"Warning: Circular dependency detected involving type: {}",
type_name
);
return;
}
if visited.contains(type_name) {
return;
}
visiting.insert(type_name.to_string());
if let Some(deps) = self.dependencies.get(type_name) {
for dep in deps {
self.topological_visit(dep, sorted, visited, visiting);
}
}
visiting.remove(type_name);
visited.insert(type_name.to_string());
sorted.push(type_name.to_string());
}
pub fn visualize_dependencies(&self, entry_commands: &[crate::models::CommandInfo]) -> String {
let mut output = String::new();
output.push_str("🌐 Type Dependency Graph\n");
output.push_str("======================\n\n");
output.push_str("📋 Command Entry Points:\n");
for cmd in entry_commands {
output.push_str(&format!(
"• {} ({}:{})\n",
cmd.name, cmd.file_path, cmd.line_number
));
for param in &cmd.parameters {
output.push_str(&format!(
" ├─ {}: {} → {}\n",
param.name, param.rust_type, param.typescript_type
));
}
output.push_str(&format!(
" └─ returns: {} → {}\n",
cmd.return_type, cmd.return_type
));
}
output.push_str("\n🏗️ Discovered Types:\n");
for (type_name, struct_info) in &self.resolved_types {
let type_kind = if struct_info.is_enum {
"enum"
} else {
"struct"
};
output.push_str(&format!(
"• {} ({}) - {} fields - defined in {}\n",
type_name,
type_kind,
struct_info.fields.len(),
struct_info.file_path
));
if let Some(deps) = self.dependencies.get(type_name) {
if !deps.is_empty() {
let deps_list: Vec<String> = deps.iter().cloned().collect();
output.push_str(&format!(" └─ depends on: {}\n", deps_list.join(", ")));
}
}
}
output.push_str("\n🔗 Dependency Chains:\n");
for type_name in self.resolved_types.keys() {
self.show_dependency_chain(type_name, &mut output, 0);
}
output.push_str(&format!(
"\n📊 Summary:\n• {} commands analyzed\n• {} types discovered\n• {} files with type definitions\n",
entry_commands.len(),
self.resolved_types.len(),
self.type_definitions.len()
));
output
}
fn show_dependency_chain(&self, type_name: &str, output: &mut String, indent: usize) {
let indent_str = " ".repeat(indent);
output.push_str(&format!("{}├─ {}\n", indent_str, type_name));
if let Some(deps) = self.dependencies.get(type_name) {
for dep in deps {
if indent < 3 {
self.show_dependency_chain(dep, output, indent + 1);
}
}
}
}
pub fn generate_dot_graph(&self, commands: &[CommandInfo]) -> String {
let mut output = String::new();
output.push_str("digraph Dependencies {\n");
output.push_str(" rankdir=LR;\n");
output.push_str(" node [shape=box];\n");
output.push('\n');
for command in commands {
output.push_str(&format!(
" \"{}\" [color=blue, style=filled, fillcolor=lightblue];\n",
command.name
));
}
for type_name in self.resolved_types.keys() {
output.push_str(&format!(" \"{}\" [color=green];\n", type_name));
}
for command in commands {
for param in &command.parameters {
if self.resolved_types.contains_key(¶m.rust_type) {
output.push_str(&format!(
" \"{}\" -> \"{}\" [label=\"param\"];\n",
command.name, param.rust_type
));
}
}
if self.resolved_types.contains_key(&command.return_type) {
output.push_str(&format!(
" \"{}\" -> \"{}\" [label=\"return\"];\n",
command.name, command.return_type
));
}
}
for (type_name, deps) in &self.dependencies {
for dep in deps {
output.push_str(&format!(" \"{}\" -> \"{}\";\n", type_name, dep));
}
}
output.push_str("}\n");
output
}
}