sql_cli/data/
datatable_buffer.rs

1use crate::api_client::QueryResponse;
2use crate::app_state_container::ColumnSearchState;
3use crate::buffer::{
4    AppMode, BufferAPI, ColumnStatistics, EditMode, FilterState, FuzzyFilterState, SearchState,
5    SortOrder, SortState,
6};
7// REMOVED: CsvApiClient import - legacy data access
8use crate::data::data_view::DataView;
9use crate::datatable::DataTable;
10use crate::datatable_view::{DataTableView, SortOrder as ViewSortOrder};
11use crate::input_manager::{create_single_line, InputManager};
12use anyhow::Result;
13use crossterm::event::KeyEvent;
14use ratatui::style::Color;
15use ratatui::widgets::TableState;
16use std::path::PathBuf;
17use std::sync::Arc;
18use tracing::debug;
19use tui_input::Input;
20
21/// A Buffer implementation backed by DataTable
22/// This allows us to integrate our clean DataTable architecture with the existing enhanced TUI
23pub struct DataTableBuffer {
24    // --- Identity ---
25    id: usize,
26    file_path: Option<PathBuf>,
27    name: String,
28    modified: bool,
29
30    // --- DataTable Backend ---
31    view: DataTableView,
32    // --- V51: DataView support ---
33    dataview: Option<DataView>,
34
35    // --- UI State (compatible with existing Buffer) ---
36    mode: AppMode,
37    edit_mode: EditMode,
38    input: Input, // Legacy - kept for compatibility
39    input_manager: Box<dyn InputManager>,
40    table_state: TableState,
41    last_results_row: Option<usize>,
42    last_scroll_offset: (usize, usize),
43
44    // --- Query State ---
45    last_query: String,
46    status_message: String,
47
48    // --- Filter/Search State ---
49    sort_state: SortState,
50    filter_state: FilterState,
51    fuzzy_filter_state: FuzzyFilterState,
52    search_state: SearchState,
53    column_search_state: ColumnSearchState,
54    column_stats: Option<ColumnStatistics>,
55    // REMOVED: filtered_data - DataView handles filtering
56
57    // --- View State ---
58    column_widths: Vec<u16>,
59    scroll_offset: (usize, usize),
60    current_column: usize,
61    // REMOVED: pinned_columns - DataView handles this now
62    compact_mode: bool,
63    viewport_lock: bool,
64    viewport_lock_row: Option<usize>,
65    show_row_numbers: bool,
66    case_insensitive: bool,
67
68    // --- Misc State ---
69    undo_stack: Vec<(String, usize)>,
70    redo_stack: Vec<(String, usize)>,
71    kill_ring: String,
72    last_visible_rows: usize,
73    last_query_source: Option<String>,
74
75    // --- Syntax Highlighting ---
76    highlighted_text_cache: Option<Vec<(String, Color)>>,
77    last_highlighted_text: String,
78
79    // --- Input State Stack ---
80    saved_input_state: Option<(String, usize)>,
81}
82
83impl DataTableBuffer {
84    /// Create a new DataTableBuffer from a DataTable
85    pub fn new(id: usize, table: DataTable) -> Self {
86        let name = table.name.clone();
87        let view = DataTableView::new(table);
88
89        Self {
90            // --- Identity ---
91            id,
92            file_path: None,
93            name,
94            modified: false,
95
96            // --- DataTable Backend ---
97            view,
98            // --- V51: DataView support ---
99            dataview: None,
100
101            // --- UI State ---
102            mode: AppMode::Command,
103            edit_mode: EditMode::SingleLine,
104            input: Input::default(),
105            input_manager: create_single_line(String::new()),
106            table_state: TableState::default(),
107            last_results_row: None,
108            last_scroll_offset: (0, 0),
109
110            // --- Query State ---
111            last_query: String::new(),
112            status_message: String::new(),
113
114            // --- Filter/Search State ---
115            sort_state: SortState {
116                column: None,
117                order: SortOrder::None,
118            },
119            filter_state: FilterState::default(),
120            fuzzy_filter_state: FuzzyFilterState::default(),
121            search_state: SearchState::default(),
122            column_search_state: ColumnSearchState::default(),
123            column_stats: None,
124            // filtered_data removed - DataView handles filtering
125
126            // --- View State ---
127            column_widths: Vec::new(),
128            scroll_offset: (0, 0),
129            current_column: 0,
130            compact_mode: false,
131            viewport_lock: false,
132            viewport_lock_row: None,
133            show_row_numbers: false,
134            case_insensitive: false,
135
136            // --- Misc State ---
137            undo_stack: Vec::new(),
138            redo_stack: Vec::new(),
139            kill_ring: String::new(),
140            last_visible_rows: 0,
141            last_query_source: None,
142
143            // --- Syntax Highlighting ---
144            highlighted_text_cache: None,
145            last_highlighted_text: String::new(),
146
147            // --- Input State Stack ---
148            saved_input_state: None,
149        }
150    }
151
152    /// Create from file path
153    pub fn from_file(id: usize, file_path: PathBuf) -> anyhow::Result<Self> {
154        let table = crate::datatable_loaders::load_json_to_datatable(&file_path, "data")?;
155        let mut buffer = Self::new(id, table);
156        buffer.file_path = Some(file_path.clone());
157        buffer.name = file_path
158            .file_stem()
159            .and_then(|s| s.to_str())
160            .unwrap_or("data")
161            .to_string();
162        Ok(buffer)
163    }
164
165    /// Update column widths from the DataTableView
166    fn update_column_widths(&mut self) {
167        // Update column widths based on the data
168        self.column_widths = self.calculate_column_widths();
169    }
170
171    /// Calculate column widths from the DataTable
172    fn calculate_column_widths(&self) -> Vec<u16> {
173        let table = self.view.table();
174        let mut widths = Vec::new();
175
176        for (col_idx, column) in table.columns.iter().enumerate() {
177            let mut max_width = column.name.len() as u16;
178
179            // Sample some rows to determine width
180            let sample_size = 50.min(table.row_count());
181            for row_idx in 0..sample_size {
182                if let Some(value) = table.get_value(row_idx, col_idx) {
183                    let value_len = value.to_string().len() as u16;
184                    max_width = max_width.max(value_len);
185                }
186            }
187
188            // Add some padding and limit maximum width
189            widths.push((max_width + 2).min(50));
190        }
191
192        widths
193    }
194
195    /// Sync sort state between buffer and view
196    fn sync_sort_to_view(&mut self) {
197        if let Some(column) = self.sort_state.column {
198            let view_order = match self.sort_state.order {
199                SortOrder::Ascending => ViewSortOrder::Ascending,
200                SortOrder::Descending => ViewSortOrder::Descending,
201                SortOrder::None => return, // Don't sort
202            };
203
204            self.view.apply_sort(column, view_order);
205            self.update_column_widths();
206        }
207    }
208
209    /// Sync filter state to view
210    fn sync_filter_to_view(&mut self) {
211        if self.filter_state.active && !self.filter_state.pattern.is_empty() {
212            self.view.apply_filter(
213                self.filter_state.pattern.clone(),
214                None, // Search all columns for now
215                !self.case_insensitive,
216            );
217            self.update_column_widths();
218        } else {
219            self.view.clear_filter();
220            self.update_column_widths();
221        }
222    }
223
224    /// Get the underlying DataTable (read-only access)
225    pub fn table(&self) -> &DataTable {
226        self.view.table()
227    }
228
229    /// Get the DataTableView (read-only access)  
230    pub fn view(&self) -> &DataTableView {
231        &self.view
232    }
233}
234
235impl BufferAPI for DataTableBuffer {
236    // --- Identity ---
237    fn get_id(&self) -> usize {
238        self.id
239    }
240
241    // --- Query and Results ---
242    fn get_query(&self) -> String {
243        self.input_manager.get_text()
244    }
245
246    fn set_query(&mut self, query: String) {
247        self.input_manager.set_text(query.clone());
248        self.input = Input::new(query.clone()).with_cursor(query.len());
249    }
250
251    // V50: Removed get_results/set_results - DataTable methods are used instead
252
253    fn get_last_query(&self) -> String {
254        self.last_query.clone()
255    }
256
257    fn set_last_query(&mut self, query: String) {
258        self.last_query = query;
259    }
260
261    // --- V47: DataTable Access ---
262    fn get_datatable(&self) -> Option<&DataTable> {
263        Some(self.view.get_datatable())
264    }
265
266    fn get_datatable_mut(&mut self) -> Option<&mut DataTable> {
267        Some(self.view.get_datatable_mut())
268    }
269
270    fn has_datatable(&self) -> bool {
271        true // DataTableBuffer always has a DataTable
272    }
273
274    fn get_original_source(&self) -> Option<&DataTable> {
275        // DataTableBuffer doesn't maintain separate original source
276        // Return the view's source table
277        Some(self.view.table())
278    }
279
280    fn set_datatable(&mut self, datatable: Option<Arc<DataTable>>) {
281        // V50: DataTableBuffer manages its own view, not direct DataTable storage
282        // This is a no-op for compatibility
283        if let Some(dt) = datatable {
284            debug!(
285                "V50: DataTableBuffer received DataTable with {} rows",
286                dt.row_count()
287            );
288        }
289    }
290
291    fn set_results_as_datatable(&mut self, _response: Option<QueryResponse>) -> Result<(), String> {
292        // V50: DataTableBuffer doesn't need QueryResponse conversion
293        // It manages data through DataTableView
294        Ok(())
295    }
296
297    // --- V51: DataView support ---
298    fn get_dataview(&self) -> Option<&DataView> {
299        self.dataview.as_ref()
300    }
301    fn get_dataview_mut(&mut self) -> Option<&mut DataView> {
302        self.dataview.as_mut()
303    }
304    fn set_dataview(&mut self, dataview: Option<DataView>) {
305        debug!(
306            "V51: Setting DataView with {} rows in DataTableBuffer",
307            dataview.as_ref().map(|v| v.row_count()).unwrap_or(0)
308        );
309        self.dataview = dataview;
310    }
311    fn has_dataview(&self) -> bool {
312        self.dataview.is_some()
313    }
314
315    // --- Mode and Status ---
316    fn get_mode(&self) -> AppMode {
317        self.mode.clone()
318    }
319
320    fn set_mode(&mut self, mode: AppMode) {
321        self.mode = mode;
322    }
323
324    fn get_edit_mode(&self) -> EditMode {
325        self.edit_mode.clone()
326    }
327
328    fn set_edit_mode(&mut self, mode: EditMode) {
329        self.edit_mode = mode;
330    }
331
332    fn get_status_message(&self) -> String {
333        self.status_message.clone()
334    }
335
336    fn set_status_message(&mut self, message: String) {
337        self.status_message = message;
338    }
339
340    // --- Table Navigation ---
341    fn get_selected_row(&self) -> Option<usize> {
342        self.last_results_row
343    }
344
345    fn set_selected_row(&mut self, row: Option<usize>) {
346        self.last_results_row = row;
347    }
348
349    fn get_current_column(&self) -> usize {
350        self.current_column
351    }
352
353    fn set_current_column(&mut self, col: usize) {
354        self.current_column = col;
355    }
356
357    fn get_scroll_offset(&self) -> (usize, usize) {
358        self.scroll_offset
359    }
360
361    fn set_scroll_offset(&mut self, offset: (usize, usize)) {
362        self.scroll_offset = offset;
363    }
364
365    fn get_last_results_row(&self) -> Option<usize> {
366        self.last_results_row
367    }
368
369    fn set_last_results_row(&mut self, row: Option<usize>) {
370        self.last_results_row = row;
371    }
372
373    fn get_last_scroll_offset(&self) -> (usize, usize) {
374        self.last_scroll_offset
375    }
376
377    fn set_last_scroll_offset(&mut self, offset: (usize, usize)) {
378        self.last_scroll_offset = offset;
379    }
380
381    // --- Filtering ---
382    fn get_filter_pattern(&self) -> String {
383        self.filter_state.pattern.clone()
384    }
385
386    fn set_filter_pattern(&mut self, pattern: String) {
387        self.filter_state.pattern = pattern;
388        self.sync_filter_to_view();
389    }
390
391    fn is_filter_active(&self) -> bool {
392        self.filter_state.active
393    }
394
395    fn set_filter_active(&mut self, active: bool) {
396        self.filter_state.active = active;
397        self.sync_filter_to_view();
398    }
399
400    // REMOVED: get_filtered_data/set_filtered_data - DataView handles filtering
401
402    // --- Fuzzy Filter ---
403    fn get_fuzzy_filter_pattern(&self) -> String {
404        self.fuzzy_filter_state.pattern.clone()
405    }
406
407    fn set_fuzzy_filter_pattern(&mut self, pattern: String) {
408        self.fuzzy_filter_state.pattern = pattern;
409        // TODO: Implement fuzzy filtering in DataTableView if needed
410    }
411
412    fn is_fuzzy_filter_active(&self) -> bool {
413        self.fuzzy_filter_state.active
414    }
415
416    fn set_fuzzy_filter_active(&mut self, active: bool) {
417        self.fuzzy_filter_state.active = active;
418    }
419
420    fn get_fuzzy_filter_indices(&self) -> &Vec<usize> {
421        &self.fuzzy_filter_state.filtered_indices
422    }
423
424    fn set_fuzzy_filter_indices(&mut self, indices: Vec<usize>) {
425        self.fuzzy_filter_state.filtered_indices = indices;
426    }
427
428    fn clear_fuzzy_filter(&mut self) {
429        self.fuzzy_filter_state.active = false;
430        self.fuzzy_filter_state.pattern.clear();
431        self.fuzzy_filter_state.filtered_indices.clear();
432    }
433
434    // --- Search ---
435    fn get_search_pattern(&self) -> String {
436        self.search_state.pattern.clone()
437    }
438
439    fn set_search_pattern(&mut self, pattern: String) {
440        self.search_state.pattern = pattern.clone();
441        // Sync with DataTableView search
442        if !pattern.is_empty() {
443            self.view.start_search(pattern, !self.case_insensitive);
444        }
445    }
446
447    fn get_search_matches(&self) -> Vec<(usize, usize)> {
448        self.search_state.matches.clone()
449    }
450
451    fn set_search_matches(&mut self, matches: Vec<(usize, usize)>) {
452        self.search_state.matches = matches;
453    }
454
455    fn get_current_match(&self) -> Option<(usize, usize)> {
456        self.search_state.current_match
457    }
458
459    fn set_current_match(&mut self, match_pos: Option<(usize, usize)>) {
460        self.search_state.current_match = match_pos;
461    }
462
463    fn get_search_match_index(&self) -> usize {
464        self.search_state.match_index
465    }
466
467    fn set_search_match_index(&mut self, index: usize) {
468        self.search_state.match_index = index;
469    }
470
471    fn clear_search_state(&mut self) {
472        self.search_state.pattern.clear();
473        self.search_state.matches.clear();
474        self.search_state.current_match = None;
475        self.search_state.match_index = 0;
476        self.view.clear_search();
477    }
478
479    // --- Column Search ---
480    // Column search methods: MIGRATED to AppStateContainer
481
482    // --- Column Statistics ---
483    fn get_column_stats(&self) -> Option<&ColumnStatistics> {
484        self.column_stats.as_ref()
485    }
486
487    fn set_column_stats(&mut self, stats: Option<ColumnStatistics>) {
488        self.column_stats = stats;
489    }
490
491    // --- Sorting ---
492    fn get_sort_column(&self) -> Option<usize> {
493        self.sort_state.column
494    }
495
496    fn set_sort_column(&mut self, column: Option<usize>) {
497        self.sort_state.column = column;
498        self.sync_sort_to_view();
499    }
500
501    fn get_sort_order(&self) -> SortOrder {
502        self.sort_state.order
503    }
504
505    fn set_sort_order(&mut self, order: SortOrder) {
506        self.sort_state.order = order;
507        self.sync_sort_to_view();
508    }
509
510    // --- Buffer Management ---
511    fn get_name(&self) -> String {
512        self.name.clone()
513    }
514
515    fn set_name(&mut self, name: String) {
516        self.name = name;
517    }
518
519    fn get_file_path(&self) -> Option<&PathBuf> {
520        self.file_path.as_ref()
521    }
522
523    fn set_file_path(&mut self, path: Option<String>) {
524        self.file_path = path.map(PathBuf::from);
525    }
526
527    fn is_modified(&self) -> bool {
528        self.modified
529    }
530
531    fn set_modified(&mut self, modified: bool) {
532        self.modified = modified;
533    }
534
535    fn get_last_query_source(&self) -> Option<String> {
536        self.last_query_source.clone()
537    }
538
539    fn set_last_query_source(&mut self, source: Option<String>) {
540        self.last_query_source = source;
541    }
542
543    // REMOVED: CSV/Cache methods - legacy data access patterns
544
545    // --- View State ---
546    fn get_column_widths(&self) -> &Vec<u16> {
547        &self.column_widths
548    }
549
550    fn set_column_widths(&mut self, widths: Vec<u16>) {
551        self.column_widths = widths;
552    }
553
554    // REMOVED: pinned_columns methods - DataView handles this now
555
556    // REMOVED: hidden_columns methods - DataView handles column visibility
557
558    fn is_compact_mode(&self) -> bool {
559        self.compact_mode
560    }
561
562    fn set_compact_mode(&mut self, compact: bool) {
563        self.compact_mode = compact;
564    }
565
566    fn is_viewport_lock(&self) -> bool {
567        self.viewport_lock
568    }
569
570    fn set_viewport_lock(&mut self, locked: bool) {
571        self.viewport_lock = locked;
572    }
573
574    fn get_viewport_lock_row(&self) -> Option<usize> {
575        self.viewport_lock_row
576    }
577
578    fn set_viewport_lock_row(&mut self, row: Option<usize>) {
579        self.viewport_lock_row = row;
580    }
581
582    fn is_show_row_numbers(&self) -> bool {
583        self.show_row_numbers
584    }
585
586    fn set_show_row_numbers(&mut self, show: bool) {
587        self.show_row_numbers = show;
588    }
589
590    fn is_case_insensitive(&self) -> bool {
591        self.case_insensitive
592    }
593
594    fn set_case_insensitive(&mut self, insensitive: bool) {
595        self.case_insensitive = insensitive;
596    }
597
598    // --- Input State ---
599    fn get_input_value(&self) -> String {
600        self.input_manager.get_text()
601    }
602
603    fn set_input_value(&mut self, value: String) {
604        self.input_manager.set_text(value.clone());
605        self.input = Input::new(value.clone()).with_cursor(value.len());
606    }
607
608    fn get_input_cursor(&self) -> usize {
609        self.input_manager.get_cursor_position()
610    }
611
612    fn set_input_cursor(&mut self, pos: usize) {
613        self.input_manager.set_cursor_position(pos);
614    }
615
616    // --- Misc ---
617    fn get_kill_ring(&self) -> String {
618        self.kill_ring.clone()
619    }
620
621    fn set_kill_ring(&mut self, text: String) {
622        self.kill_ring = text;
623    }
624
625    fn get_last_visible_rows(&self) -> usize {
626        self.last_visible_rows
627    }
628
629    fn set_last_visible_rows(&mut self, rows: usize) {
630        self.last_visible_rows = rows;
631    }
632
633    // --- Advanced Operations ---
634    fn apply_filter(&mut self) -> Result<()> {
635        self.sync_filter_to_view();
636        Ok(())
637    }
638
639    fn apply_sort(&mut self) -> Result<()> {
640        self.sync_sort_to_view();
641        Ok(())
642    }
643
644    fn search(&mut self) -> Result<()> {
645        // Search functionality handled by sync_filter_to_view for now
646        Ok(())
647    }
648
649    fn clear_filters(&mut self) {
650        self.filter_state.active = false;
651        self.filter_state.pattern.clear();
652        self.view.clear_filter();
653    }
654
655    fn get_row_count(&self) -> usize {
656        self.view.table().row_count()
657    }
658
659    fn get_column_count(&self) -> usize {
660        self.view.table().column_count()
661    }
662
663    fn get_column_names(&self) -> Vec<String> {
664        self.view
665            .table()
666            .columns
667            .iter()
668            .map(|col| col.name.clone())
669            .collect()
670    }
671
672    // REMOVED: has_cached_data - legacy data access
673
674    // --- Undo/Redo ---
675    fn get_undo_stack(&self) -> &Vec<(String, usize)> {
676        &self.undo_stack
677    }
678
679    fn push_undo(&mut self, state: (String, usize)) {
680        self.undo_stack.push(state);
681        // Limit undo stack size
682        if self.undo_stack.len() > 100 {
683            self.undo_stack.remove(0);
684        }
685    }
686
687    fn pop_undo(&mut self) -> Option<(String, usize)> {
688        self.undo_stack.pop()
689    }
690
691    fn get_redo_stack(&self) -> &Vec<(String, usize)> {
692        &self.redo_stack
693    }
694
695    fn push_redo(&mut self, state: (String, usize)) {
696        self.redo_stack.push(state);
697        // Limit redo stack size
698        if self.redo_stack.len() > 100 {
699            self.redo_stack.remove(0);
700        }
701    }
702
703    fn pop_redo(&mut self) -> Option<(String, usize)> {
704        self.redo_stack.pop()
705    }
706
707    fn clear_redo(&mut self) {
708        self.redo_stack.clear();
709    }
710
711    // --- Additional required methods ---
712    fn is_kill_ring_empty(&self) -> bool {
713        self.kill_ring.is_empty()
714    }
715
716    fn perform_undo(&mut self) -> bool {
717        if let Some((text, cursor)) = self.pop_undo() {
718            // Save current state for redo
719            let current_state = (self.get_input_value(), self.get_input_cursor());
720            self.push_redo(current_state);
721
722            // Restore previous state
723            self.set_input_value(text);
724            self.set_input_cursor(cursor);
725            true
726        } else {
727            false
728        }
729    }
730
731    fn perform_redo(&mut self) -> bool {
732        if let Some((text, cursor)) = self.pop_redo() {
733            // Save current state for undo
734            let current_state = (self.get_input_value(), self.get_input_cursor());
735            self.push_undo(current_state);
736
737            // Restore state
738            self.set_input_value(text);
739            self.set_input_cursor(cursor);
740            true
741        } else {
742            false
743        }
744    }
745
746    fn save_state_for_undo(&mut self) {
747        let current_state = (self.get_input_value(), self.get_input_cursor());
748        self.push_undo(current_state);
749    }
750
751    fn debug_dump(&self) -> String {
752        format!(
753            "DataTableBuffer {{ id: {}, name: \"{}\", rows: {}, cols: {} }}",
754            self.id,
755            self.name,
756            self.get_row_count(),
757            self.get_column_count()
758        )
759    }
760
761    fn get_input_text(&self) -> String {
762        self.input_manager.get_text()
763    }
764
765    fn set_input_text(&mut self, text: String) {
766        self.input_manager.set_text(text.clone());
767        self.input = Input::new(text.clone()).with_cursor(text.len());
768    }
769
770    fn handle_input_key(&mut self, event: KeyEvent) -> bool {
771        self.input_manager.handle_key_event(event)
772    }
773
774    fn switch_input_mode(&mut self, _multiline: bool) {
775        // Always use single-line mode
776        self.edit_mode = EditMode::SingleLine;
777        let current_text = self.input_manager.get_text();
778        self.input_manager = create_single_line(current_text);
779    }
780
781    fn get_input_cursor_position(&self) -> usize {
782        self.input_manager.get_cursor_position()
783    }
784
785    fn set_input_cursor_position(&mut self, position: usize) {
786        self.input_manager.set_cursor_position(position);
787    }
788
789    fn is_input_multiline(&self) -> bool {
790        matches!(self.edit_mode, EditMode::MultiLine)
791    }
792
793    fn navigate_history_up(&mut self, _history: &[String]) -> bool {
794        // For now, delegate to input manager if it supports history
795        // TODO: Implement proper history navigation
796        false
797    }
798
799    fn navigate_history_down(&mut self, _history: &[String]) -> bool {
800        // For now, delegate to input manager if it supports history
801        // TODO: Implement proper history navigation
802        false
803    }
804
805    fn reset_history_navigation(&mut self) {
806        // TODO: Implement history navigation reset
807    }
808
809    fn clear_results(&mut self) {
810        // Clear the DataTable or reset to empty state
811        // DataView handles all filtering now
812    }
813}