use crate::agent::ui::colors::icons;
use crate::agent::ui::response::brand;
use std::io::{self, Write};
const BOX_WIDTH: usize = 72;
pub struct HelmlintDisplay;
impl HelmlintDisplay {
pub fn print_result(json_result: &str) {
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(json_result) {
Self::print_formatted(&parsed);
} else {
println!("{}", json_result);
}
}
fn print_formatted(result: &serde_json::Value) {
let stdout = io::stdout();
let mut handle = stdout.lock();
let chart = result["chart"].as_str().unwrap_or("helm chart");
let _ = writeln!(handle);
let _ = writeln!(
handle,
"{}{}╭─ {} Helmlint {}{}╮{}",
brand::PURPLE,
brand::BOLD,
icons::HELM,
"─".repeat(BOX_WIDTH - 15),
brand::DIM,
brand::RESET
);
let _ = writeln!(
handle,
"{}│ {}{}{}{}",
brand::DIM,
brand::CYAN,
chart,
" ".repeat((BOX_WIDTH - 4 - chart.len()).max(0)),
brand::RESET
);
let _ = writeln!(handle, "{}│{}", brand::DIM, " ".repeat(BOX_WIDTH - 1));
if let Some(context) = result["decision_context"].as_str() {
let context_color = if context.contains("Critical") {
brand::CORAL
} else if context.contains("High") || context.contains("high") {
brand::PEACH
} else if context.contains("Good") || context.contains("No issues") {
brand::SUCCESS
} else {
brand::PEACH
};
let display_context = if context.len() > BOX_WIDTH - 6 {
&context[..BOX_WIDTH - 9]
} else {
context
};
let _ = writeln!(
handle,
"{}│ {}{}{}{}",
brand::DIM,
context_color,
display_context,
" ".repeat((BOX_WIDTH - 4 - display_context.len()).max(0)),
brand::RESET
);
}
let _ = writeln!(handle, "{}│{}", brand::DIM, " ".repeat(BOX_WIDTH - 1));
if let Some(summary) = result.get("summary") {
let total = summary["total"].as_u64().unwrap_or(0);
if total == 0 {
let _ = writeln!(
handle,
"{}│ {}{} All checks passed! No issues found.{}{}",
brand::DIM,
brand::SUCCESS,
icons::SUCCESS,
" ".repeat(BOX_WIDTH - 42),
brand::RESET
);
let files = summary["files_checked"].as_u64().unwrap_or(0);
let stats = format!("{} files checked", files);
let _ = writeln!(handle, "{}│{}", brand::DIM, " ".repeat(BOX_WIDTH - 1));
let _ = writeln!(
handle,
"{}│ {}{}{}{}",
brand::DIM,
brand::DIM,
stats,
" ".repeat((BOX_WIDTH - 4 - stats.len()).max(0)),
brand::RESET
);
} else {
if let Some(by_priority) = summary.get("by_priority") {
let critical = by_priority["critical"].as_u64().unwrap_or(0);
let high = by_priority["high"].as_u64().unwrap_or(0);
let medium = by_priority["medium"].as_u64().unwrap_or(0);
let low = by_priority["low"].as_u64().unwrap_or(0);
let mut counts = String::new();
if critical > 0 {
counts.push_str(&format!("{} {} critical ", icons::CRITICAL, critical));
}
if high > 0 {
counts.push_str(&format!("{} {} high ", icons::HIGH, high));
}
if medium > 0 {
counts.push_str(&format!("{} {} medium ", icons::MEDIUM, medium));
}
if low > 0 {
counts.push_str(&format!("{} {} low", icons::LOW, low));
}
let padding = if counts.len() < BOX_WIDTH - 4 {
(BOX_WIDTH - 4 - counts.chars().count()).max(0)
} else {
0
};
let _ = writeln!(
handle,
"{}│ {}{}{}",
brand::DIM,
counts,
" ".repeat(padding),
brand::RESET
);
}
}
}
if let Some(quick_fixes) = result.get("quick_fixes").and_then(|f| f.as_array())
&& !quick_fixes.is_empty()
{
let _ = writeln!(handle, "{}│{}", brand::DIM, " ".repeat(BOX_WIDTH - 1));
let _ = writeln!(
handle,
"{}│ {}{} Quick Fixes:{}{}",
brand::DIM,
brand::PURPLE,
icons::FIX,
" ".repeat(BOX_WIDTH - 18),
brand::RESET
);
for fix in quick_fixes.iter().take(5) {
if let Some(fix_str) = fix.as_str() {
let (issue, remediation) = if let Some(pos) = fix_str.find(" - ") {
(&fix_str[..pos], &fix_str[pos + 3..])
} else {
(fix_str, "")
};
let issue_display = if issue.len() > BOX_WIDTH - 10 {
format!("{}...", &issue[..BOX_WIDTH - 13])
} else {
issue.to_string()
};
let _ = writeln!(
handle,
"{}│ {}→ {}{}{}{}",
brand::DIM,
brand::CYAN,
issue_display,
" ".repeat((BOX_WIDTH - 8 - issue_display.len()).max(0)),
brand::RESET,
brand::RESET
);
if !remediation.is_empty() {
let rem_display = if remediation.len() > BOX_WIDTH - 10 {
format!("{}...", &remediation[..BOX_WIDTH - 13])
} else {
remediation.to_string()
};
let _ = writeln!(
handle,
"{}│ {}{}{}{}",
brand::DIM,
brand::DIM,
rem_display,
" ".repeat((BOX_WIDTH - 8 - rem_display.len()).max(0)),
brand::RESET
);
}
}
}
}
Self::print_priority_section(
&mut handle,
result,
"critical",
"Critical Issues",
brand::CORAL,
);
Self::print_priority_section(&mut handle, result, "high", "High Priority", brand::PEACH);
let medium_count = result["action_plan"]["medium"]
.as_array()
.map(|a| a.len())
.unwrap_or(0);
let low_count = result["action_plan"]["low"]
.as_array()
.map(|a| a.len())
.unwrap_or(0);
let other_count = medium_count + low_count;
if other_count > 0 {
let _ = writeln!(handle, "{}│{}", brand::DIM, " ".repeat(BOX_WIDTH - 1));
let msg = format!(
"{} {} priority issue{} (use --verbose to see all)",
other_count,
if medium_count > 0 {
"medium/low"
} else {
"low"
},
if other_count == 1 { "" } else { "s" }
);
let _ = writeln!(
handle,
"{}│ {}{}{}{}",
brand::DIM,
brand::DIM,
msg,
" ".repeat((BOX_WIDTH - 4 - msg.len()).max(0)),
brand::RESET
);
}
let _ = writeln!(
handle,
"{}╰{}╯{}",
brand::DIM,
"─".repeat(BOX_WIDTH - 2),
brand::RESET
);
let _ = writeln!(handle);
let _ = handle.flush();
}
fn print_priority_section(
handle: &mut io::StdoutLock,
result: &serde_json::Value,
priority: &str,
title: &str,
color: &str,
) {
if let Some(issues) = result["action_plan"][priority].as_array() {
if issues.is_empty() {
return;
}
let _ = writeln!(handle, "{}│{}", brand::DIM, " ".repeat(BOX_WIDTH - 1));
let _ = writeln!(
handle,
"{}│ {}{}:{}{}",
brand::DIM,
color,
title,
" ".repeat((BOX_WIDTH - 4 - title.len() - 1).max(0)),
brand::RESET
);
for issue in issues.iter().take(5) {
let code = issue["code"].as_str().unwrap_or("???");
let file = issue["file"].as_str().unwrap_or("");
let line = issue["line"].as_u64().unwrap_or(0);
let message = issue["message"].as_str().unwrap_or("");
let category = issue["category"].as_str().unwrap_or("");
let badge = Self::get_category_badge(category);
let file_short = if file.len() > 30 {
format!("...{}", &file[file.len() - 27..])
} else {
file.to_string()
};
let header = format!("{}:{} {} {}", file_short, line, code, badge);
let header_len = header.chars().count();
let _ = writeln!(
handle,
"{}│ {}{}{}{}",
brand::DIM,
brand::CYAN,
header,
" ".repeat((BOX_WIDTH - 6 - header_len).max(0)),
brand::RESET
);
let msg_display = if message.len() > BOX_WIDTH - 8 {
format!("{}...", &message[..BOX_WIDTH - 11])
} else {
message.to_string()
};
let _ = writeln!(
handle,
"{}│ {}{}{}",
brand::DIM,
msg_display,
" ".repeat((BOX_WIDTH - 6 - msg_display.len()).max(0)),
brand::RESET
);
if let Some(fix) = issue["fix"].as_str() {
let fix_display = if fix.len() > BOX_WIDTH - 12 {
format!("{}...", &fix[..BOX_WIDTH - 15])
} else {
fix.to_string()
};
let _ = writeln!(
handle,
"{}│ {}→ {}{}{}",
brand::DIM,
brand::CYAN,
fix_display,
" ".repeat((BOX_WIDTH - 8 - fix_display.len()).max(0)),
brand::RESET
);
}
}
if issues.len() > 5 {
let more_msg = format!("... and {} more", issues.len() - 5);
let _ = writeln!(
handle,
"{}│ {}{}{}{}",
brand::DIM,
brand::DIM,
more_msg,
" ".repeat((BOX_WIDTH - 6 - more_msg.len()).max(0)),
brand::RESET
);
}
}
}
fn get_category_badge(category: &str) -> String {
match category {
"Security" | "security" => format!("{}[SEC]{}", brand::CORAL, brand::RESET),
"Structure" | "structure" => format!("{}[STRUCT]{}", brand::DIM, brand::RESET),
"Values" | "values" => format!("{}[VAL]{}", brand::PEACH, brand::RESET),
"Template" | "template" => format!("{}[TPL]{}", brand::PEACH, brand::RESET),
"Best Practice" | "best-practice" => format!("{}[BP]{}", brand::CYAN, brand::RESET),
_ => String::new(),
}
}
pub fn format_summary(json_result: &str) -> String {
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(json_result) {
let success = parsed["success"].as_bool().unwrap_or(false);
let total = parsed["summary"]["total"].as_u64().unwrap_or(0);
if success && total == 0 {
format!(
"{}{} {} Helm chart OK - no issues{}",
brand::SUCCESS,
icons::SUCCESS,
icons::HELM,
brand::RESET
)
} else {
let critical = parsed["summary"]["by_priority"]["critical"]
.as_u64()
.unwrap_or(0);
let high = parsed["summary"]["by_priority"]["high"]
.as_u64()
.unwrap_or(0);
if critical > 0 {
format!(
"{}{} {} {} critical, {} high priority issues{}",
brand::CORAL,
icons::CRITICAL,
icons::HELM,
critical,
high,
brand::RESET
)
} else if high > 0 {
format!(
"{}{} {} {} high priority issues{}",
brand::PEACH,
icons::HIGH,
icons::HELM,
high,
brand::RESET
)
} else {
format!(
"{}{} {} {} issues (medium/low){}",
brand::PEACH,
icons::MEDIUM,
icons::HELM,
total,
brand::RESET
)
}
}
} else {
format!("{} Helmlint analysis complete", icons::HELM)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_summary_success() {
let json = r#"{"success": true, "summary": {"total": 0, "by_priority": {"critical": 0, "high": 0, "medium": 0, "low": 0}}}"#;
let summary = HelmlintDisplay::format_summary(json);
assert!(summary.contains("OK"));
}
#[test]
fn test_format_summary_high() {
let json = r#"{"success": false, "summary": {"total": 3, "by_priority": {"critical": 0, "high": 2, "medium": 1, "low": 0}}}"#;
let summary = HelmlintDisplay::format_summary(json);
assert!(summary.contains("high"));
}
#[test]
fn test_category_badge() {
let badge = HelmlintDisplay::get_category_badge("Template");
assert!(badge.contains("TPL"));
}
#[test]
fn test_print_result_with_issues() {
let json = r#"{
"chart": "test-chart",
"success": false,
"decision_context": "High priority issues found. Fix template syntax.",
"summary": {
"total": 3,
"files_checked": 5,
"by_priority": {"critical": 0, "high": 2, "medium": 1, "low": 0}
},
"action_plan": {
"critical": [],
"high": [{
"code": "HL3001",
"file": "templates/deployment.yaml",
"line": 15,
"category": "Template",
"message": "Unclosed template block",
"fix": "Add {{- end }} to close the block"
}, {
"code": "HL1007",
"file": "Chart.yaml",
"line": 1,
"category": "Structure",
"message": "Missing maintainers field",
"fix": "Add maintainers list with name and email"
}],
"medium": [{
"code": "HL2003",
"file": "values.yaml",
"line": 8,
"category": "Values",
"message": "Unused value defined",
"fix": "Remove unused value or reference it in templates"
}],
"low": []
},
"quick_fixes": ["templates/deployment.yaml:15 HL3001 - Add {{- end }}", "Chart.yaml:1 HL1007 - Add maintainers list"]
}"#;
HelmlintDisplay::print_result(json);
}
#[test]
fn test_print_result_success() {
let json = r#"{
"chart": "good-chart",
"success": true,
"decision_context": "No issues found.",
"summary": {
"total": 0,
"files_checked": 8,
"by_priority": {"critical": 0, "high": 0, "medium": 0, "low": 0}
},
"action_plan": {"critical": [], "high": [], "medium": [], "low": []}
}"#;
HelmlintDisplay::print_result(json);
}
}