1use std::sync::Arc;
2
3use anyhow::Result;
4
5use crate::engine::EngineState;
6use crate::graph;
7
8pub struct GraphResult {
10 pub rendered: String,
12 pub report: graph::GraphReport,
14}
15
16pub struct GraphParams<'a> {
18 pub format: Option<&'a str>,
20 pub root: Option<String>,
22 pub depth: Option<usize>,
24 pub type_filter: Option<&'a str>,
26 pub relation: Option<String>,
28 pub output: Option<&'a str>,
30 pub cross_wiki: bool,
32}
33
34pub fn graph_build(
36 engine: &EngineState,
37 wiki_name: &str,
38 params: &GraphParams<'_>,
39) -> Result<GraphResult> {
40 let space = engine.space(wiki_name)?;
41 let resolved = space.resolved_config(&engine.config);
42
43 let fmt = params.format.unwrap_or(&resolved.graph.format);
44 let types: Vec<String> = params
45 .type_filter
46 .map(|t| t.split(',').map(|s| s.trim().to_string()).collect())
47 .unwrap_or_default();
48
49 let filter = graph::GraphFilter {
50 root: params.root.clone(),
51 depth: params.depth.or(Some(resolved.graph.depth as usize)),
52 types,
53 relation: params.relation.clone(),
54 };
55 let g: Arc<graph::WikiGraph> = if params.cross_wiki {
56 let mut per_space: Vec<(&str, Arc<graph::WikiGraph>)> = Vec::new();
58 for (name, sp) in engine.spaces.iter() {
59 if let Ok(searcher) = sp.index_manager.searcher() {
60 let g = graph::get_or_build_graph(
61 &sp.index_schema,
62 &sp.type_registry,
63 &sp.index_manager,
64 &sp.graph_cache,
65 &searcher,
66 &filter,
67 )?;
68 per_space.push((name.as_str(), g));
69 }
70 }
71 Arc::new(graph::merge_cached_graphs(&per_space, &filter)?)
72 } else {
73 let searcher = space.index_manager.searcher()?;
74 graph::get_or_build_graph(
75 &space.index_schema,
76 &space.type_registry,
77 &space.index_manager,
78 &space.graph_cache,
79 &searcher,
80 &filter,
81 )?
82 };
83
84 let rendered = match fmt {
85 "dot" => graph::render_dot(&g),
86 "llms" => graph::render_llms(&g),
87 _ => graph::render_mermaid(&g),
88 };
89
90 let out = if let Some(out_path) = params.output {
91 let content = if out_path.ends_with(".md") {
92 graph::wrap_graph_md(&rendered, fmt, &filter)
93 } else {
94 rendered.clone()
95 };
96 std::fs::write(out_path, &content)?;
97 out_path.to_string()
98 } else {
99 "stdout".to_string()
100 };
101
102 Ok(GraphResult {
103 rendered,
104 report: graph::GraphReport {
105 nodes: g.node_count(),
106 edges: g.edge_count(),
107 output: out,
108 },
109 })
110}