use crate::analyze::Analysis;
use std::collections::HashMap;
use std::fmt::Write;
pub fn render_report(
analysis: &Analysis,
communities: &HashMap<usize, Vec<String>>,
) -> String {
let mut report = String::with_capacity(4096);
writeln!(report, "# Knowledge Graph Report").unwrap();
writeln!(report).unwrap();
writeln!(report, "> Generated by [codeskeleton](https://github.com/DhanushNehru/codeskeleton)").unwrap();
writeln!(report).unwrap();
writeln!(report, "## Overview").unwrap();
writeln!(report).unwrap();
writeln!(report, "| Metric | Value |").unwrap();
writeln!(report, "|--------|-------|").unwrap();
writeln!(report, "| Files analyzed | {} |", analysis.stats.files_analyzed).unwrap();
writeln!(report, "| Total nodes | {} |", analysis.stats.total_nodes).unwrap();
writeln!(report, "| Total edges | {} |", analysis.stats.total_edges).unwrap();
writeln!(report, "| Communities | {} |", analysis.stats.total_communities).unwrap();
writeln!(report).unwrap();
writeln!(report, "## God Nodes").unwrap();
writeln!(report).unwrap();
writeln!(
report,
"Highest-degree concepts — what everything connects through."
)
.unwrap();
writeln!(report).unwrap();
if analysis.god_nodes.is_empty() {
writeln!(report, "_No god nodes found._").unwrap();
} else {
writeln!(report, "| Rank | Node | Kind | Degree | Community |").unwrap();
writeln!(report, "|------|------|------|--------|-----------|").unwrap();
for (i, node) in analysis.god_nodes.iter().enumerate() {
writeln!(
report,
"| {} | **{}** | {} | {} | {} |",
i + 1,
node.label,
node.kind,
node.degree,
node.community.map_or("—".to_string(), |c| c.to_string())
)
.unwrap();
}
}
writeln!(report).unwrap();
writeln!(report, "## Surprising Connections").unwrap();
writeln!(report).unwrap();
writeln!(
report,
"Cross-community edges that reveal hidden structure."
)
.unwrap();
writeln!(report).unwrap();
if analysis.surprising_connections.is_empty() {
writeln!(report, "_No surprising connections found._").unwrap();
} else {
for (i, conn) in analysis.surprising_connections.iter().enumerate() {
writeln!(report, "{}. {}", i + 1, conn.why).unwrap();
}
}
writeln!(report).unwrap();
writeln!(report, "## Communities").unwrap();
writeln!(report).unwrap();
let mut sorted_communities: Vec<_> = communities.iter().collect();
sorted_communities.sort_by_key(|(id, _)| *id);
for (cid, nodes) in &sorted_communities {
let cohesion = analysis
.community_scores
.get(cid)
.copied()
.unwrap_or(0.0);
writeln!(
report,
"### Community {} ({} nodes, cohesion: {:.2})",
cid,
nodes.len(),
cohesion
)
.unwrap();
writeln!(report).unwrap();
let display_count = std::cmp::min(nodes.len(), 20);
for node in &nodes[..display_count] {
writeln!(report, "- `{}`", node).unwrap();
}
if nodes.len() > 20 {
writeln!(report, "- _... and {} more_", nodes.len() - 20).unwrap();
}
writeln!(report).unwrap();
}
writeln!(report, "## Suggested Questions").unwrap();
writeln!(report).unwrap();
writeln!(
report,
"Questions this graph is uniquely positioned to answer:"
)
.unwrap();
writeln!(report).unwrap();
for (i, question) in analysis.suggested_questions.iter().enumerate() {
writeln!(report, "{}. {}", i + 1, question).unwrap();
}
writeln!(report).unwrap();
writeln!(report, "---").unwrap();
writeln!(report).unwrap();
writeln!(report, "### Confidence Labels").unwrap();
writeln!(report).unwrap();
writeln!(report, "| Label | Meaning |").unwrap();
writeln!(report, "|-------|---------|").unwrap();
writeln!(
report,
"| EXTRACTED | Found directly in source (import, direct call) |"
)
.unwrap();
writeln!(
report,
"| INFERRED | Reasonable deduction (call-graph pass, name matching) |"
)
.unwrap();
writeln!(
report,
"| AMBIGUOUS | Uncertain — flagged for review |"
)
.unwrap();
report
}