1use ratatui::layout::{Constraint, Rect};
4use ratatui::style::{Color, Modifier, Style};
5use ratatui::widgets::{Block, Borders, Cell, Row as TuiRow, Table, TableState};
6use ratatui::Frame;
7
8use crate::models::{Column, Row, Value};
9
10use super::search::SearchState;
11
12const MAX_CELL_WIDTH: usize = 32;
13
14pub struct TableView {
16 columns: Vec<Column>,
17 rows: Vec<Row>,
18 state: TableState,
19 row_offset: usize,
20 col_offset: usize,
21 view_height: usize,
22}
23
24impl TableView {
25 pub fn new(columns: Vec<Column>, rows: Vec<Row>) -> Self {
26 let mut state = TableState::default();
27 if !rows.is_empty() {
28 state.select(Some(0));
29 }
30 Self {
31 columns,
32 rows,
33 state,
34 row_offset: 0,
35 col_offset: 0,
36 view_height: 10,
37 }
38 }
39
40 pub fn columns(&self) -> &[Column] {
41 &self.columns
42 }
43
44 pub fn rows(&self) -> &[Row] {
45 &self.rows
46 }
47
48 pub fn selected_row(&self) -> Option<&Row> {
49 let selected = self.state.selected()?;
50 self.rows.get(selected)
51 }
52
53 #[allow(dead_code)]
54 pub fn selected_index(&self) -> Option<usize> {
55 self.state.selected()
56 }
57
58 pub fn select_row(&mut self, index: usize) {
59 if self.rows.is_empty() {
60 return;
61 }
62 let index = index.min(self.rows.len() - 1);
63 self.state.select(Some(index));
64 self.ensure_visible(index);
65 }
66
67 pub fn move_up(&mut self) {
68 if self.rows.is_empty() {
69 return;
70 }
71 let selected = self.state.selected().unwrap_or(0);
72 let next = selected.saturating_sub(1);
73 self.state.select(Some(next));
74 self.ensure_visible(next);
75 }
76
77 pub fn move_down(&mut self) {
78 if self.rows.is_empty() {
79 return;
80 }
81 let selected = self.state.selected().unwrap_or(0);
82 let next = (selected + 1).min(self.rows.len().saturating_sub(1));
83 self.state.select(Some(next));
84 self.ensure_visible(next);
85 }
86
87 pub fn move_left(&mut self) {
88 self.col_offset = self.col_offset.saturating_sub(1);
89 }
90
91 pub fn move_right(&mut self) {
92 if self.col_offset + 1 < self.columns.len() {
93 self.col_offset += 1;
94 }
95 }
96
97 pub fn page_up(&mut self) {
98 if self.rows.is_empty() {
99 return;
100 }
101 let selected = self.state.selected().unwrap_or(0);
102 let max_visible = self.view_height.saturating_sub(3).max(1);
103 let delta = (max_visible / 2).max(1);
104 let next = selected.saturating_sub(delta);
105 self.state.select(Some(next));
106 self.ensure_visible(next);
107 }
108
109 pub fn page_down(&mut self) {
110 if self.rows.is_empty() {
111 return;
112 }
113 let selected = self.state.selected().unwrap_or(0);
114 let max_visible = self.view_height.saturating_sub(3).max(1);
115 let delta = (max_visible / 2).max(1);
116 let next = (selected + delta).min(self.rows.len().saturating_sub(1));
117 self.state.select(Some(next));
118 self.ensure_visible(next);
119 }
120
121 pub fn jump_top(&mut self) {
122 self.state.select(Some(0));
123 self.row_offset = 0;
124 }
125
126 pub fn jump_bottom(&mut self) {
127 if self.rows.is_empty() {
128 return;
129 }
130 let last = self.rows.len() - 1;
131 self.state.select(Some(last));
132 self.ensure_visible(last);
133 }
134
135 pub fn render(&mut self, frame: &mut Frame<'_>, area: Rect, search: &SearchState) {
136 self.view_height = area.height as usize;
137 let columns = self.visible_columns();
138 let header_cells = columns
139 .iter()
140 .map(|col| Cell::from(col.name.clone()).style(header_style()));
141 let mut header = vec![Cell::from("#").style(header_style())];
142 header.extend(header_cells);
143
144 let row_range = self.visible_row_range();
145 let rows = self.rows[row_range.clone()]
146 .iter()
147 .enumerate()
148 .map(|(idx, row)| {
149 let row_index = self.row_offset + idx;
150 let mut cells = Vec::with_capacity(columns.len() + 1);
151 cells.push(Cell::from((row_index + 1).to_string()));
152 for (col_index, value) in row.columns.iter().enumerate().skip(self.col_offset) {
153 if col_index >= self.col_offset + columns.len() {
154 break;
155 }
156 let text = format_value(value);
157 let mut cell = Cell::from(text.clone());
158 if search.matches_cell(row_index, col_index, &text) {
159 cell = cell.style(Style::default().fg(Color::Yellow));
160 }
161 cells.push(cell);
162 }
163 TuiRow::new(cells)
164 });
165
166 let widths = self.column_widths(&columns);
167 let mut constraints = Vec::with_capacity(widths.len() + 1);
168 constraints.push(Constraint::Length(4));
169 constraints.extend(widths.into_iter().map(|w| Constraint::Length(w as u16)));
170
171 let table = Table::new(rows, constraints)
172 .header(TuiRow::new(header))
173 .block(Block::default().borders(Borders::ALL).title("Results"))
174 .highlight_style(Style::default().add_modifier(Modifier::REVERSED))
175 .highlight_symbol("▌");
176
177 frame.render_stateful_widget(table, area, &mut self.state);
178 }
179
180 fn visible_row_range(&self) -> std::ops::Range<usize> {
181 if self.rows.is_empty() {
182 return 0..0;
183 }
184 let max_rows = self.view_height.saturating_sub(3).max(1);
185 let end = (self.row_offset + max_rows).min(self.rows.len());
186 self.row_offset..end
187 }
188
189 fn ensure_visible(&mut self, row: usize) {
190 if row < self.row_offset {
191 self.row_offset = row;
192 }
193 let max_visible = self.view_height.saturating_sub(3).max(1);
194 if row >= self.row_offset + max_visible {
195 self.row_offset = row.saturating_sub(max_visible - 1);
196 }
197 }
198
199 fn visible_columns(&self) -> Vec<Column> {
200 if self.columns.is_empty() {
201 return Vec::new();
202 }
203 let start = self.col_offset.min(self.columns.len() - 1);
204 self.columns[start..].to_vec()
205 }
206
207 fn column_widths(&self, columns: &[Column]) -> Vec<usize> {
208 columns
209 .iter()
210 .enumerate()
211 .map(|(idx, col)| {
212 let width = col.name.len();
213 let mut max_width = width;
214 for row in &self.rows {
215 if let Some(value) = row.columns.get(self.col_offset + idx) {
216 let value_len = format_value(value).len();
217 max_width = max_width.max(value_len);
218 }
219 }
220 max_width.clamp(4, MAX_CELL_WIDTH)
221 })
222 .collect()
223 }
224}
225
226fn header_style() -> Style {
227 Style::default()
228 .fg(Color::Cyan)
229 .add_modifier(Modifier::BOLD)
230}
231
232pub fn format_value(value: &Value) -> String {
233 match value {
234 Value::Null => "NULL".to_string(),
235 Value::Bool(b) => b.to_string(),
236 Value::Int(i) => i.to_string(),
237 Value::Float(f) => format!("{:.6}", f),
238 Value::Text(s) => s.clone(),
239 Value::Bytes(b) => {
240 let hex: String = b
241 .iter()
242 .take(32)
243 .map(|byte| format!("{:02x}", byte))
244 .collect();
245 if b.len() > 32 {
246 format!("{}...", hex)
247 } else {
248 hex
249 }
250 }
251 Value::Vector(v) => {
252 if v.len() <= 4 {
253 format!(
254 "[{}]",
255 v.iter()
256 .map(|x| format!("{:.4}", x))
257 .collect::<Vec<_>>()
258 .join(", ")
259 )
260 } else {
261 format!(
262 "[{}, ... ({} dims)]",
263 v.iter()
264 .take(3)
265 .map(|x| format!("{:.4}", x))
266 .collect::<Vec<_>>()
267 .join(", "),
268 v.len()
269 )
270 }
271 }
272 }
273}