use comfy_table::{Cell, Color, Table};
use super::{AgentLineage, TeamTopology, TopologyOverview, TopologyStats};
pub fn status_color(status: &str) -> Color {
match status.to_lowercase().as_str() {
"active" => Color::Green,
s if s.starts_with("suspended") => Color::Yellow,
"deregistered" => Color::Red,
_ => Color::Reset,
}
}
pub fn render_overview_table(overview: &TopologyOverview) {
println!(
"Teams: {} | Agents: {} | Roots: {}\n",
overview.team_count, overview.total_agent_count, overview.root_agent_count,
);
if !overview.teams.is_empty() {
let mut table = Table::new();
table.set_header(vec!["TEAM_ID", "AGENTS", "ROOT_AGENTS"]);
for t in &overview.teams {
table.add_row(vec![
Cell::new(&t.team_id),
Cell::new(t.agent_count),
Cell::new(t.root_agent_count),
]);
}
println!("{table}");
}
if !overview.standalone_root_agents.is_empty() {
println!("\nStandalone root agents:");
let mut table = Table::new();
table.set_header(vec!["AGENT_ID", "NAME", "STATUS"]);
for a in &overview.standalone_root_agents {
table.add_row(vec![
Cell::new(&a.id),
Cell::new(&a.name),
Cell::new(&a.status).fg(status_color(&a.status)),
]);
}
println!("{table}");
}
}
pub fn render_team_table(team: &TeamTopology) {
println!("Team: {} | Agents: {}\n", team.team_id, team.agent_count);
if team.members.is_empty() {
println!("(no members)");
return;
}
let mut members: Vec<&super::AgentNode> = team.members.iter().collect();
members.sort_by_key(|a| a.id.as_str());
let mut table = Table::new();
table.set_header(vec!["AGENT_ID", "NAME", "DEPTH", "STATUS"]);
for a in &members {
table.add_row(vec![
Cell::new(&a.id),
Cell::new(&a.name),
Cell::new(a.depth),
Cell::new(&a.status).fg(status_color(&a.status)),
]);
}
println!("{table}");
}
pub fn render_lineage_table(lineage: &AgentLineage) {
println!(
"Agent: {} | Ancestors: {}\n",
lineage.agent_id, lineage.ancestor_count,
);
if lineage.ancestors.is_empty() {
println!("No ancestry data.");
return;
}
let mut table = Table::new();
table.set_header(vec!["DEPTH", "AGENT_ID", "NAME", "TEAM", "DELEGATION_REASON"]);
for step in &lineage.ancestors {
table.add_row(vec![
Cell::new(step.depth),
Cell::new(&step.id),
Cell::new(&step.name),
Cell::new(step.team_id.as_deref().unwrap_or("-")),
Cell::new(
step.delegation_reason
.as_deref()
.map(|r| if r.len() > 60 { &r[..60] } else { r })
.unwrap_or("-"),
),
]);
}
println!("{table}");
let is_root = lineage.ancestors.len() == 1 && lineage.ancestors[0].depth == 0;
if is_root {
println!("\n(this is a root agent)");
}
}
pub fn render_stats_table(stats: &TopologyStats) {
let mut table = Table::new();
table.set_header(vec!["METRIC", "VALUE"]);
table.add_row(vec![Cell::new("Total agents"), Cell::new(stats.total_agents)]);
table.add_row(vec![Cell::new("Root agents"), Cell::new(stats.root_agent_count)]);
table.add_row(vec![Cell::new("Max depth"), Cell::new(stats.max_depth)]);
table.add_row(vec![
Cell::new("Active"),
Cell::new(stats.active_count).fg(Color::Green),
]);
table.add_row(vec![
Cell::new("Suspended"),
Cell::new(stats.suspended_count).fg(Color::Yellow),
]);
table.add_row(vec![
Cell::new("Deregistered"),
Cell::new(stats.deregistered_count).fg(Color::Red),
]);
table.add_row(vec![Cell::new("Teams"), Cell::new(stats.team_count)]);
table.add_row(vec![Cell::new("Orphans"), Cell::new(stats.orphan_count)]);
table.add_row(vec![
Cell::new("Avg children/parent"),
Cell::new(format!("{:.2}", stats.avg_children_per_parent)),
]);
println!("{table}");
if !stats.depth_histogram.is_empty() {
println!("\nDepth histogram:");
let mut htable = Table::new();
htable.set_header(vec!["DEPTH", "COUNT"]);
for (depth, count) in &stats.depth_histogram {
htable.add_row(vec![Cell::new(depth), Cell::new(count)]);
}
println!("{htable}");
}
if !stats.team_size_histogram.is_empty() {
println!("\nTeam-size histogram:");
let mut htable = Table::new();
htable.set_header(vec!["TEAM_SIZE", "COUNT"]);
for (size, count) in &stats.team_size_histogram {
htable.add_row(vec![Cell::new(size), Cell::new(count)]);
}
println!("{htable}");
}
}