use ratatui::Frame;
use ratatui::layout::{Constraint, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::text::Text;
use ratatui::widgets::{Block, Borders, Cell, Row, Table};
use crate::core::models::QueryResult;
use crate::ui::state::Mode;
use crate::ui::tabs::WorkspaceTab;
use crate::ui::theme::Theme;
pub fn render_for_tab(
frame: &mut Frame,
tab: &mut WorkspaceTab,
focused: bool,
theme: &Theme,
area: Rect,
mode: &Mode,
) {
let is_table_view = matches!(tab.kind, crate::ui::tabs::TabKind::Table { .. });
let grid_active = focused && (is_table_view || tab.grid_focused);
let border_style = theme.border_style(grid_active, mode);
if tab.query_result.is_none() {
let block = Block::default()
.title(" Data ")
.borders(Borders::ALL)
.border_style(border_style)
.style(Style::default().bg(theme.editor_bg));
let empty_rows: Vec<Row> = vec![];
let empty = Table::new(empty_rows, &[Constraint::Min(1)]).block(block);
frame.render_widget(empty, area);
return;
}
let visible_height = area.height.saturating_sub(4) as usize;
tab.grid_visible_height = visible_height.max(1);
let available_width = area.width.saturating_sub(2) as usize;
let col_widths = compute_smart_widths(tab.query_result.as_ref().expect("checked"));
ensure_col_visible(tab, &col_widths, available_width);
let result = tab.query_result.as_ref().expect("checked");
let total_rows = result.rows.len();
let total_cols = result.columns.len();
let scroll_col = tab.grid_scroll_col;
let (vis_col_start, vis_col_end) = visible_col_range(&col_widths, scroll_col, available_width);
let sel_range: Option<((usize, usize), (usize, usize))> =
tab.grid_selection_anchor.map(|(ar, ac)| {
let cur = (tab.grid_selected_row, tab.grid_selected_col);
let anchor = (ar, ac);
if anchor <= cur {
(anchor, cur)
} else {
(cur, anchor)
}
});
let visual_tag = if tab.grid_visual_mode { " VISUAL " } else { "" };
let col_info = if total_cols > vis_col_end - vis_col_start {
format!(" cols {}-{}/{}", vis_col_start + 1, vis_col_end, total_cols)
} else {
String::new()
};
let status = format!(
" Data [{}-{} of {}]{col_info} {visual_tag}",
if total_rows > 0 {
tab.grid_scroll_row + 1
} else {
0
},
(tab.grid_scroll_row + visible_height).min(total_rows),
total_rows
);
let block = Block::default()
.title(status)
.borders(Borders::ALL)
.border_style(border_style)
.style(Style::default().bg(theme.editor_bg));
let vis_constraints: Vec<Constraint> = col_widths[vis_col_start..vis_col_end]
.iter()
.map(|&w| Constraint::Min(w as u16))
.collect();
let header_cells: Vec<Cell> = result.columns[vis_col_start..vis_col_end]
.iter()
.map(|c| Cell::from(Text::from(c.as_str())).style(theme.grid_header_style()))
.collect();
let header = Row::new(header_cells)
.height(1)
.style(Style::default().bg(theme.grid_header_bg));
let selected_col = tab.grid_selected_col;
let is_grid_focused = grid_active;
let rows: Vec<Row> = result
.rows
.iter()
.skip(tab.grid_scroll_row)
.take(visible_height)
.enumerate()
.map(|(vis_idx, row_data)| {
let absolute_idx = tab.grid_scroll_row + vis_idx;
let row_style = theme.grid_row_style(absolute_idx);
let cells: Vec<Cell> = (vis_col_start..vis_col_end)
.map(|col_idx| {
let val = row_data.get(col_idx).map(|s| s.as_str()).unwrap_or("");
let base_style = if val == "NULL" {
theme.null_style()
} else if val.parse::<f64>().is_ok() {
Style::default().fg(theme.grid_number)
} else {
Style::default()
};
let is_cursor = is_grid_focused
&& absolute_idx == tab.grid_selected_row
&& col_idx == selected_col;
let in_selection = sel_range.is_some_and(|((sr, sc), (er, ec))| {
absolute_idx >= sr && absolute_idx <= er && col_idx >= sc && col_idx <= ec
});
if is_cursor {
Cell::from(Text::from(val)).style(
base_style
.bg(theme.grid_selected_bg)
.fg(theme.grid_selected_fg)
.add_modifier(Modifier::BOLD),
)
} else if in_selection {
Cell::from(Text::from(val)).style(
base_style
.bg(ratatui::style::Color::Rgb(40, 50, 70))
.fg(ratatui::style::Color::Rgb(200, 210, 230)),
)
} else {
Cell::from(Text::from(val)).style(base_style)
}
})
.collect();
Row::new(cells).style(row_style)
})
.collect();
let table = Table::new(rows, &vis_constraints)
.header(header)
.block(block)
.column_spacing(1);
frame.render_widget(table, area);
}
fn compute_smart_widths(result: &QueryResult) -> Vec<usize> {
let mut widths: Vec<usize> = result
.columns
.iter()
.map(|c| c.len().max(4)) .collect();
for row in result.rows.iter().take(100) {
for (i, val) in row.iter().enumerate() {
if i < widths.len() {
widths[i] = widths[i].max(val.len());
}
}
}
for w in &mut widths {
*w = (*w).min(40);
}
widths
}
fn ensure_col_visible(tab: &mut WorkspaceTab, col_widths: &[usize], available: usize) {
let sel = tab.grid_selected_col;
if sel < tab.grid_scroll_col {
tab.grid_scroll_col = sel;
return;
}
let mut used = 0;
for (i, &w) in col_widths.iter().enumerate().skip(tab.grid_scroll_col) {
used += w + 1; if i == sel {
if used > available {
tab.grid_scroll_col = sel;
let mut back_used = 0;
let mut new_start = sel;
for j in (0..sel).rev() {
back_used += col_widths[j] + 1;
if back_used + col_widths[sel] + 1 > available {
break;
}
new_start = j;
}
tab.grid_scroll_col = new_start;
}
return;
}
}
}
fn visible_col_range(col_widths: &[usize], scroll_col: usize, available: usize) -> (usize, usize) {
let start = scroll_col.min(col_widths.len());
let mut used = 0;
let mut end = start;
for &w in &col_widths[start..] {
if used + w + 1 > available && end > start {
break;
}
used += w + 1;
end += 1;
}
(start, end)
}