turbovault_tools/
graph_tools.rs

1//! Graph operations and link analysis tools
2
3use serde::{Deserialize, Serialize};
4use std::sync::Arc;
5use turbovault_core::prelude::*;
6use turbovault_graph::HealthAnalyzer;
7use turbovault_vault::VaultManager;
8
9/// Graph tools context
10pub struct GraphTools {
11    pub manager: Arc<VaultManager>,
12}
13
14/// Simplified broken link for JSON serialization
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct BrokenLinkInfo {
17    pub source_file: String,
18    pub target: String,
19    pub line: usize,
20    pub suggestions: Vec<String>,
21}
22
23/// Simplified health report for JSON serialization
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct HealthInfo {
26    pub total_notes: usize,
27    pub total_links: usize,
28    pub broken_links_count: usize,
29    pub orphaned_notes_count: usize,
30    pub dead_end_notes_count: usize,
31    pub hub_notes_count: usize,
32    pub health_score: u8,
33    pub is_healthy: bool,
34}
35
36impl GraphTools {
37    /// Create new graph tools
38    pub fn new(manager: Arc<VaultManager>) -> Self {
39        Self { manager }
40    }
41
42    /// Get detailed broken links information
43    pub async fn get_broken_links(&self) -> Result<Vec<BrokenLinkInfo>> {
44        let graph_lock = self.manager.link_graph();
45        let graph = graph_lock.read().await;
46        let analyzer = HealthAnalyzer::new(&graph);
47
48        let report = analyzer.analyze()?;
49
50        Ok(report
51            .broken_links
52            .into_iter()
53            .map(|bl| BrokenLinkInfo {
54                source_file: bl.source_file.to_string_lossy().to_string(),
55                target: bl.target,
56                line: bl.line,
57                suggestions: bl.suggestions,
58            })
59            .collect())
60    }
61
62    /// Run quick health check
63    pub async fn quick_health_check(&self) -> Result<HealthInfo> {
64        let graph_lock = self.manager.link_graph();
65        let graph = graph_lock.read().await;
66        let analyzer = HealthAnalyzer::new(&graph);
67
68        let report = analyzer.quick_check()?;
69
70        Ok(HealthInfo {
71            total_notes: report.total_notes,
72            total_links: report.total_links,
73            broken_links_count: report.broken_links.len(),
74            orphaned_notes_count: report.orphaned_notes.len(),
75            dead_end_notes_count: 0,
76            hub_notes_count: 0,
77            health_score: report.health_score,
78            is_healthy: report.is_healthy(),
79        })
80    }
81
82    /// Run comprehensive health analysis
83    pub async fn full_health_analysis(&self) -> Result<HealthInfo> {
84        let graph_lock = self.manager.link_graph();
85        let graph = graph_lock.read().await;
86        let analyzer = HealthAnalyzer::new(&graph);
87
88        let report = analyzer.analyze()?;
89
90        Ok(HealthInfo {
91            total_notes: report.total_notes,
92            total_links: report.total_links,
93            broken_links_count: report.broken_links.len(),
94            orphaned_notes_count: report.orphaned_notes.len(),
95            dead_end_notes_count: report.dead_end_notes.len(),
96            hub_notes_count: report.hub_notes.len(),
97            health_score: report.health_score,
98            is_healthy: report.is_healthy(),
99        })
100    }
101
102    /// Get hub notes (highly connected nodes)
103    pub async fn get_hub_notes(&self, limit: usize) -> Result<Vec<(String, usize)>> {
104        let graph_lock = self.manager.link_graph();
105        let graph = graph_lock.read().await;
106        let analyzer = HealthAnalyzer::new(&graph);
107
108        let report = analyzer.analyze()?;
109
110        Ok(report
111            .hub_notes
112            .into_iter()
113            .take(limit)
114            .map(|(path, count)| (path.to_string_lossy().to_string(), count))
115            .collect())
116    }
117
118    /// Get dead-end notes (no outgoing links but have incoming)
119    pub async fn get_dead_end_notes(&self) -> Result<Vec<String>> {
120        let graph_lock = self.manager.link_graph();
121        let graph = graph_lock.read().await;
122        let analyzer = HealthAnalyzer::new(&graph);
123
124        let report = analyzer.analyze()?;
125
126        Ok(report
127            .dead_end_notes
128            .into_iter()
129            .map(|p| p.to_string_lossy().to_string())
130            .collect())
131    }
132
133    /// Detect cycles in the graph
134    pub async fn detect_cycles(&self) -> Result<Vec<Vec<String>>> {
135        let graph_lock = self.manager.link_graph();
136        let graph = graph_lock.read().await;
137        let cycles = graph.cycles();
138
139        Ok(cycles
140            .into_iter()
141            .map(|cycle| {
142                cycle
143                    .into_iter()
144                    .map(|p| p.to_string_lossy().to_string())
145                    .collect()
146            })
147            .collect())
148    }
149
150    /// Get connected components
151    pub async fn get_connected_components(&self) -> Result<Vec<Vec<String>>> {
152        let graph_lock = self.manager.link_graph();
153        let graph = graph_lock.read().await;
154        let components = graph.connected_components()?;
155
156        Ok(components
157            .into_iter()
158            .map(|component| {
159                component
160                    .into_iter()
161                    .map(|p| p.to_string_lossy().to_string())
162                    .collect()
163            })
164            .collect())
165    }
166
167    /// Get isolated clusters (small disconnected groups)
168    pub async fn get_isolated_clusters(&self) -> Result<Vec<Vec<String>>> {
169        let graph_lock = self.manager.link_graph();
170        let graph = graph_lock.read().await;
171        let analyzer = HealthAnalyzer::new(&graph);
172
173        let report = analyzer.analyze()?;
174
175        Ok(report
176            .isolated_clusters
177            .into_iter()
178            .map(|cluster| {
179                cluster
180                    .into_iter()
181                    .map(|p| p.to_string_lossy().to_string())
182                    .collect()
183            })
184            .collect())
185    }
186}