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