use crate::buffer::{BufferAPI, ColumnStatistics, ColumnType};
use crate::widget_traits::DebugInfoProvider;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::{
layout::Rect,
style::{Color, Modifier, Style},
text::Line,
widgets::{Block, Borders, Paragraph, Wrap},
Frame,
};
pub struct StatsWidget {
handle_keys: bool,
}
impl StatsWidget {
#[must_use]
pub fn new() -> Self {
Self { handle_keys: true }
}
pub fn handle_key(&mut self, key: KeyEvent) -> StatsAction {
if !self.handle_keys {
return StatsAction::PassThrough;
}
match key.code {
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
StatsAction::Quit
}
KeyCode::Char('q' | 'S') | KeyCode::Esc => StatsAction::Close,
_ => StatsAction::Continue,
}
}
pub fn render(&self, f: &mut Frame, area: Rect, buffer: &dyn BufferAPI) {
if let Some(stats) = buffer.get_column_stats() {
let lines = self.build_stats_lines(stats);
let stats_paragraph = Paragraph::new(lines)
.block(Block::default().borders(Borders::ALL).title(format!(
"Column Statistics - {} (S to close)",
stats.column_name
)))
.wrap(Wrap { trim: false });
f.render_widget(stats_paragraph, area);
} else {
let error = Paragraph::new("No statistics available")
.block(
Block::default()
.borders(Borders::ALL)
.title("Column Statistics"),
)
.style(Style::default().fg(Color::Red));
f.render_widget(error, area);
}
}
fn build_stats_lines(&self, stats: &ColumnStatistics) -> Vec<Line<'static>> {
let mut lines = vec![
Line::from(format!("Column Statistics: {}", stats.column_name)).style(
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
),
Line::from(""),
Line::from(format!("Type: {:?}", stats.column_type))
.style(Style::default().fg(Color::Yellow)),
Line::from(format!("Total Rows: {}", stats.total_count)),
Line::from(format!("Unique Values: {}", stats.unique_count)),
Line::from(format!("Null/Empty Count: {}", stats.null_count)),
Line::from(""),
];
if matches!(stats.column_type, ColumnType::Numeric | ColumnType::Mixed) {
lines.push(
Line::from("Numeric Statistics:").style(
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
),
);
if let Some(min) = stats.min {
lines.push(Line::from(format!(" Min: {min:.2}")));
}
if let Some(max) = stats.max {
lines.push(Line::from(format!(" Max: {max:.2}")));
}
if let Some(mean) = stats.mean {
lines.push(Line::from(format!(" Mean: {mean:.2}")));
}
if let Some(median) = stats.median {
lines.push(Line::from(format!(" Median: {median:.2}")));
}
if let Some(sum) = stats.sum {
lines.push(Line::from(format!(" Sum: {sum:.2}")));
}
lines.push(Line::from(""));
}
if let Some(ref freq_map) = stats.frequency_map {
lines.push(
Line::from("Frequency Distribution:").style(
Style::default()
.fg(Color::Magenta)
.add_modifier(Modifier::BOLD),
),
);
let mut freq_vec: Vec<(&String, &usize)> = freq_map.iter().collect();
freq_vec.sort_by(|a, b| b.1.cmp(a.1));
let max_count = freq_vec.first().map_or(1, |(_, c)| **c);
for (value, count) in freq_vec.iter().take(20) {
let bar_width = ((**count as f64 / max_count as f64) * 30.0) as usize;
let bar = "█".repeat(bar_width);
let display_value = if value.len() > 30 {
format!("{}...", &value[..27])
} else {
(*value).to_string()
};
lines.push(Line::from(format!(" {display_value:30} {bar} ({count})")));
}
if freq_vec.len() > 20 {
lines.push(
Line::from(format!(
" ... and {} more unique values",
freq_vec.len() - 20
))
.style(Style::default().fg(Color::DarkGray)),
);
}
}
lines.push(Line::from(""));
lines.push(
Line::from("Press S or Esc to return to results")
.style(Style::default().fg(Color::DarkGray)),
);
lines
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum StatsAction {
Continue,
Close,
Quit,
PassThrough,
}
impl Default for StatsWidget {
fn default() -> Self {
Self::new()
}
}
impl DebugInfoProvider for StatsWidget {
fn debug_info(&self) -> String {
let mut info = String::from("=== STATS WIDGET ===\n");
info.push_str("State: Active\n");
info.push_str(&format!("Handle Keys: {}\n", self.handle_keys));
info
}
fn debug_summary(&self) -> String {
format!(
"StatsWidget: keys={}",
if self.handle_keys { "on" } else { "off" }
)
}
}