sql_cli/ui/state/
state_coordinator.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3use std::sync::Arc;
4
5use crate::app_state_container::AppStateContainer;
6use crate::buffer::{AppMode, Buffer, BufferAPI, BufferManager};
7use crate::config::config::Config;
8use crate::data::data_view::DataView;
9use crate::sql::hybrid_parser::HybridParser;
10use crate::ui::viewport_manager::ViewportManager;
11use crate::widgets::search_modes_widget::SearchMode;
12
13use tracing::{debug, error};
14
15/// StateCoordinator manages and synchronizes all state components in the TUI
16/// This centralizes state management and reduces coupling in the main TUI
17pub struct StateCoordinator {
18    /// Core application state
19    pub state_container: AppStateContainer,
20
21    /// Shadow state for tracking mode transitions
22    pub shadow_state: Rc<RefCell<crate::ui::state::shadow_state::ShadowStateManager>>,
23
24    /// Viewport manager for display state
25    pub viewport_manager: Rc<RefCell<Option<ViewportManager>>>,
26
27    /// SQL parser with schema information
28    pub hybrid_parser: HybridParser,
29}
30
31impl StateCoordinator {
32    // ========== STATIC METHODS FOR DELEGATION ==========
33    // These methods work with references and can be called without owning the components
34    // This allows incremental migration from EnhancedTuiApp
35
36    /// Static version of sync_mode that works with references
37    pub fn sync_mode_with_refs(
38        state_container: &mut AppStateContainer,
39        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
40        mode: AppMode,
41        trigger: &str,
42    ) {
43        debug!(
44            "StateCoordinator::sync_mode_with_refs: Setting mode to {:?} with trigger '{}'",
45            mode, trigger
46        );
47
48        // Set in AppStateContainer
49        state_container.set_mode(mode.clone());
50
51        // Set in current buffer
52        if let Some(buffer) = state_container.buffers_mut().current_mut() {
53            buffer.set_mode(mode.clone());
54        }
55
56        // Observe in shadow state
57        shadow_state.borrow_mut().observe_mode_change(mode, trigger);
58    }
59
60    /// Static version of update_parser_for_current_buffer
61    pub fn update_parser_with_refs(state_container: &AppStateContainer, parser: &mut HybridParser) {
62        if let Some(dataview) = state_container.get_buffer_dataview() {
63            let table_name = dataview.source().name.clone();
64            let columns = dataview.source().column_names();
65
66            debug!(
67                "StateCoordinator: Updating parser with {} columns for table '{}'",
68                columns.len(),
69                table_name
70            );
71            parser.update_single_table(table_name, columns);
72        }
73    }
74
75    // ========== CONSTRUCTORS ==========
76
77    pub fn new(
78        state_container: AppStateContainer,
79        shadow_state: Rc<RefCell<crate::ui::state::shadow_state::ShadowStateManager>>,
80        viewport_manager: Rc<RefCell<Option<ViewportManager>>>,
81        hybrid_parser: HybridParser,
82    ) -> Self {
83        Self {
84            state_container,
85            shadow_state,
86            viewport_manager,
87            hybrid_parser,
88        }
89    }
90
91    // ========== MODE SYNCHRONIZATION ==========
92
93    /// Synchronize mode across all state containers
94    /// This ensures AppStateContainer, Buffer, and ShadowState are all in sync
95    pub fn sync_mode(&mut self, mode: AppMode, trigger: &str) {
96        debug!(
97            "StateCoordinator::sync_mode: Setting mode to {:?} with trigger '{}'",
98            mode, trigger
99        );
100
101        // Set in AppStateContainer
102        self.state_container.set_mode(mode.clone());
103
104        // Set in current buffer
105        if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
106            buffer.set_mode(mode.clone());
107        }
108
109        // Observe in shadow state
110        self.shadow_state
111            .borrow_mut()
112            .observe_mode_change(mode, trigger);
113    }
114
115    /// Alternative mode setter that goes through shadow state
116    pub fn set_mode_via_shadow_state(&mut self, mode: AppMode, trigger: &str) {
117        if let Some(buffer) = self.state_container.buffers_mut().current_mut() {
118            debug!(
119                "StateCoordinator::set_mode_via_shadow_state: Setting mode to {:?} with trigger '{}'",
120                mode, trigger
121            );
122            self.shadow_state
123                .borrow_mut()
124                .set_mode(mode, buffer, trigger);
125        } else {
126            error!(
127                "StateCoordinator::set_mode_via_shadow_state: No buffer available! Cannot set mode to {:?}",
128                mode
129            );
130        }
131    }
132
133    // ========== BUFFER SYNCHRONIZATION ==========
134
135    /// Synchronize all state after buffer switch
136    /// This should be called after any buffer switch operation
137    pub fn sync_after_buffer_switch(&mut self) {
138        // For now, just update the parser
139        // TODO: Add viewport sync when we refactor viewport management
140        self.update_parser_for_current_buffer();
141    }
142
143    /// Update parser schema from current buffer's DataView
144    pub fn update_parser_for_current_buffer(&mut self) {
145        // Update parser schema from DataView
146        if let Some(dataview) = self.state_container.get_buffer_dataview() {
147            let table_name = dataview.source().name.clone();
148            let columns = dataview.source().column_names();
149
150            debug!(
151                "StateCoordinator: Updating parser with {} columns for table '{}'",
152                columns.len(),
153                table_name
154            );
155            self.hybrid_parser.update_single_table(table_name, columns);
156        }
157    }
158
159    // ========== SEARCH MODE SYNCHRONIZATION ==========
160
161    /// Enter a search mode with proper state synchronization
162    pub fn enter_search_mode(&mut self, mode: SearchMode) -> String {
163        debug!("StateCoordinator::enter_search_mode: {:?}", mode);
164
165        // Determine the trigger for this search mode
166        let trigger = match mode {
167            SearchMode::ColumnSearch => "backslash_column_search",
168            SearchMode::Search => "data_search_started",
169            SearchMode::FuzzyFilter => "fuzzy_filter_started",
170            SearchMode::Filter => "filter_started",
171        };
172
173        // Sync mode across all state containers
174        self.sync_mode(mode.to_app_mode(), trigger);
175
176        // Also observe the search mode start in shadow state for search-specific tracking
177        let search_type = match mode {
178            SearchMode::ColumnSearch => crate::ui::state::shadow_state::SearchType::Column,
179            SearchMode::Search => crate::ui::state::shadow_state::SearchType::Data,
180            SearchMode::FuzzyFilter | SearchMode::Filter => {
181                crate::ui::state::shadow_state::SearchType::Fuzzy
182            }
183        };
184        self.shadow_state
185            .borrow_mut()
186            .observe_search_start(search_type, trigger);
187
188        trigger.to_string()
189    }
190
191    // ========== SEARCH CANCELLATION ==========
192
193    /// Cancel search and properly restore state
194    /// This handles all the complex state synchronization when Escape is pressed during search
195    pub fn cancel_search(&mut self) -> (Option<String>, Option<usize>) {
196        debug!("StateCoordinator::cancel_search: Canceling search and restoring state");
197
198        // Clear search state in state container
199        self.state_container.clear_search();
200
201        // Observe search end in shadow state
202        self.shadow_state
203            .borrow_mut()
204            .observe_search_end("search_cancelled");
205
206        // Switch back to Results mode with proper synchronization
207        self.sync_mode(AppMode::Results, "search_cancelled");
208
209        // Return saved SQL and cursor position for restoration
210        // This would come from search widget's saved state
211        (None, None)
212    }
213
214    /// Static version for delegation pattern with vim search adapter
215    pub fn cancel_search_with_refs(
216        state_container: &mut AppStateContainer,
217        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
218        vim_search_adapter: Option<
219            &RefCell<crate::ui::search::vim_search_adapter::VimSearchAdapter>,
220        >,
221    ) {
222        debug!("StateCoordinator::cancel_search_with_refs: Canceling search and clearing all search state");
223
224        // Clear vim search adapter if provided
225        if let Some(adapter) = vim_search_adapter {
226            debug!("Clearing vim search adapter state");
227            adapter.borrow_mut().clear();
228        }
229
230        // Clear search pattern in state container
231        state_container.set_search_pattern(String::new());
232        state_container.clear_search();
233
234        // Also clear column search state
235        state_container.clear_column_search();
236
237        // Observe search end in shadow state
238        shadow_state
239            .borrow_mut()
240            .observe_search_end("search_cancelled");
241
242        // Sync back to Results mode
243        Self::sync_mode_with_refs(
244            state_container,
245            shadow_state,
246            AppMode::Results,
247            "vim_search_cancelled",
248        );
249    }
250
251    /// Check if 'n' key should navigate to next search match
252    /// Returns true only if there's an active search (not cancelled with Escape)
253    pub fn should_handle_next_match(
254        state_container: &AppStateContainer,
255        vim_search_adapter: Option<
256            &RefCell<crate::ui::search::vim_search_adapter::VimSearchAdapter>,
257        >,
258    ) -> bool {
259        // 'n' should only work if there's a search pattern AND it hasn't been cancelled
260        let has_search = !state_container.get_search_pattern().is_empty();
261        let pattern = state_container.get_search_pattern();
262
263        // Check if vim search is active or navigating
264        // After Escape, this will be false
265        let vim_active = if let Some(adapter) = vim_search_adapter {
266            let adapter_ref = adapter.borrow();
267            adapter_ref.is_active() || adapter_ref.is_navigating()
268        } else {
269            false
270        };
271
272        debug!(
273            "StateCoordinator::should_handle_next_match: pattern='{}', vim_active={}, result={}",
274            pattern,
275            vim_active,
276            has_search && vim_active
277        );
278
279        // Only handle if search exists AND hasn't been cancelled with Escape
280        has_search && vim_active
281    }
282
283    /// Check if 'N' key should navigate to previous search match
284    /// Returns true only if there's an active search (not cancelled with Escape)
285    pub fn should_handle_previous_match(
286        state_container: &AppStateContainer,
287        vim_search_adapter: Option<
288            &RefCell<crate::ui::search::vim_search_adapter::VimSearchAdapter>,
289        >,
290    ) -> bool {
291        // 'N' should only work if there's a search pattern AND it hasn't been cancelled
292        let has_search = !state_container.get_search_pattern().is_empty();
293        let pattern = state_container.get_search_pattern();
294
295        // Check if vim search is active or navigating
296        // After Escape, this will be false
297        let vim_active = if let Some(adapter) = vim_search_adapter {
298            let adapter_ref = adapter.borrow();
299            adapter_ref.is_active() || adapter_ref.is_navigating()
300        } else {
301            false
302        };
303
304        debug!(
305            "StateCoordinator::should_handle_previous_match: pattern='{}', vim_active={}, result={}",
306            pattern, vim_active, has_search && vim_active
307        );
308
309        // Only handle if search exists AND hasn't been cancelled with Escape
310        has_search && vim_active
311    }
312
313    /// Complete a search operation (after Apply/Enter is pressed)
314    /// This keeps the pattern for n/N navigation but marks search as complete
315    pub fn complete_search_with_refs(
316        state_container: &mut AppStateContainer,
317        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
318        vim_search_adapter: Option<
319            &RefCell<crate::ui::search::vim_search_adapter::VimSearchAdapter>,
320        >,
321        mode: AppMode,
322        trigger: &str,
323    ) {
324        debug!(
325            "StateCoordinator::complete_search_with_refs: Completing search, switching to {:?}",
326            mode
327        );
328
329        // Note: We intentionally DO NOT clear the search pattern here
330        // The pattern remains available for n/N navigation
331
332        // Mark vim search adapter as not actively searching
333        // but keep the matches for navigation
334        if let Some(adapter) = vim_search_adapter {
335            debug!("Marking vim search as complete but keeping matches");
336            adapter.borrow_mut().mark_search_complete();
337        }
338
339        // Observe search completion in shadow state
340        shadow_state
341            .borrow_mut()
342            .observe_search_end("search_completed");
343
344        // Switch to the target mode (usually Results)
345        Self::sync_mode_with_refs(state_container, shadow_state, mode, trigger);
346    }
347
348    // ========== FILTER MANAGEMENT ==========
349
350    /// Apply text filter and coordinate all state updates
351    /// Returns the number of matching rows
352    pub fn apply_text_filter_with_refs(
353        state_container: &mut AppStateContainer,
354        pattern: &str,
355    ) -> usize {
356        let case_insensitive = state_container.is_case_insensitive();
357
358        debug!(
359            "StateCoordinator::apply_text_filter_with_refs: Applying text filter with pattern '{}', case_sensitive: {}",
360            pattern, !case_insensitive
361        );
362
363        // Apply filter to DataView and get results
364        let rows_after = if let Some(dataview) = state_container.get_buffer_dataview_mut() {
365            let rows_before = dataview.row_count();
366            dataview.apply_text_filter(pattern, !case_insensitive);
367            let rows_after = dataview.row_count();
368            debug!(
369                "Text filter: {} rows before, {} rows after",
370                rows_before, rows_after
371            );
372            rows_after
373        } else {
374            debug!("No DataView available for text filtering");
375            0
376        };
377
378        // Update status message
379        let status = if pattern.is_empty() {
380            "Filter cleared".to_string()
381        } else {
382            format!("Filter applied: '{}' - {} matches", pattern, rows_after)
383        };
384        state_container.set_status_message(status);
385
386        debug!(
387            "StateCoordinator: Text filter applied - {} matches for pattern '{}'",
388            rows_after, pattern
389        );
390
391        rows_after
392    }
393
394    /// Apply fuzzy filter and coordinate all state updates
395    /// Returns (match_count, filter_indices)
396    pub fn apply_fuzzy_filter_with_refs(
397        state_container: &mut AppStateContainer,
398        viewport_manager: &RefCell<Option<ViewportManager>>,
399    ) -> (usize, Vec<usize>) {
400        let pattern = state_container.get_fuzzy_filter_pattern();
401        let case_insensitive = state_container.is_case_insensitive();
402
403        debug!(
404            "StateCoordinator::apply_fuzzy_filter_with_refs: Applying fuzzy filter with pattern '{}', case_insensitive: {}",
405            pattern, case_insensitive
406        );
407
408        // Apply filter to DataView and get results
409        let (match_count, indices) =
410            if let Some(dataview) = state_container.get_buffer_dataview_mut() {
411                dataview.apply_fuzzy_filter(&pattern, case_insensitive);
412                let match_count = dataview.row_count();
413                let indices = dataview.get_fuzzy_filter_indices();
414                (match_count, indices)
415            } else {
416                (0, Vec::new())
417            };
418
419        // Update state based on filter results
420        if pattern.is_empty() {
421            state_container.set_fuzzy_filter_active(false);
422            state_container.set_status_message("Fuzzy filter cleared".to_string());
423        } else {
424            state_container.set_fuzzy_filter_active(true);
425            state_container.set_status_message(format!("Fuzzy filter: {} matches", match_count));
426
427            // Reset navigation to first match if we have results
428            if match_count > 0 {
429                // Get current column offset to preserve horizontal scroll
430                let col_offset = state_container.get_scroll_offset().1;
431
432                // Reset to first row of filtered results
433                state_container.set_selected_row(Some(0));
434                state_container.set_scroll_offset((0, col_offset));
435                state_container.set_table_selected_row(Some(0));
436
437                // Update navigation state
438                let mut nav = state_container.navigation_mut();
439                nav.selected_row = 0;
440                nav.scroll_offset.0 = 0;
441
442                // Update ViewportManager if present
443                if let Ok(mut vm_borrow) = viewport_manager.try_borrow_mut() {
444                    if let Some(ref mut vm) = *vm_borrow {
445                        vm.set_crosshair_row(0);
446                        vm.set_scroll_offset(0, col_offset);
447                        debug!(
448                            "StateCoordinator: Reset viewport to first match (row 0) with {} total matches",
449                            match_count
450                        );
451                    }
452                }
453            }
454        }
455
456        debug!(
457            "StateCoordinator: Fuzzy filter applied - {} matches, pattern: '{}'",
458            match_count, pattern
459        );
460
461        (match_count, indices)
462    }
463
464    // ========== TABLE STATE MANAGEMENT ==========
465
466    /// Reset all table-related state to initial values
467    /// This is typically called when switching data sources or after queries
468    pub fn reset_table_state_with_refs(
469        state_container: &mut AppStateContainer,
470        viewport_manager: &RefCell<Option<ViewportManager>>,
471    ) {
472        debug!("StateCoordinator::reset_table_state_with_refs: Resetting all table state");
473
474        // Reset navigation state
475        state_container.navigation_mut().reset();
476        state_container.set_table_selected_row(Some(0));
477        state_container.reset_navigation_state();
478
479        // Reset ViewportManager if it exists
480        if let Ok(mut vm_borrow) = viewport_manager.try_borrow_mut() {
481            if let Some(ref mut vm) = *vm_borrow {
482                vm.reset_crosshair();
483                debug!("StateCoordinator: Reset ViewportManager crosshair position");
484            }
485        }
486
487        // Clear filter state
488        state_container.filter_mut().clear();
489
490        // Clear search state
491        {
492            let mut search = state_container.search_mut();
493            search.pattern.clear();
494            search.current_match = 0;
495            search.matches.clear();
496            search.is_active = false;
497        }
498
499        // Clear fuzzy filter state
500        state_container.clear_fuzzy_filter_state();
501
502        // Clear column search state (added for completeness)
503        state_container.clear_column_search();
504
505        debug!("StateCoordinator: Table state reset complete");
506    }
507
508    // ========== DATAVIEW MANAGEMENT ==========
509
510    /// Add a new DataView and coordinate all necessary state updates
511    /// This centralizes the complex logic of adding a new data source
512    pub fn add_dataview_with_refs(
513        state_container: &mut AppStateContainer,
514        viewport_manager: &RefCell<Option<ViewportManager>>,
515        dataview: DataView,
516        source_name: &str,
517        config: &Config,
518    ) -> Result<(), anyhow::Error> {
519        debug!(
520            "StateCoordinator::add_dataview_with_refs: Adding DataView for '{}'",
521            source_name
522        );
523
524        // Create a new buffer with the DataView
525        let buffer_id = state_container.buffers().all_buffers().len() + 1;
526        let mut buffer = crate::buffer::Buffer::new(buffer_id);
527
528        // Set the DataView directly
529        buffer.set_dataview(Some(dataview.clone()));
530
531        // Use just the filename for the buffer name, not the full path
532        let buffer_name = std::path::Path::new(source_name)
533            .file_name()
534            .and_then(|s| s.to_str())
535            .unwrap_or(source_name)
536            .to_string();
537        buffer.set_name(buffer_name.clone());
538
539        // Apply config settings to the buffer
540        buffer.set_case_insensitive(config.behavior.case_insensitive_default);
541        buffer.set_compact_mode(config.display.compact_mode);
542        buffer.set_show_row_numbers(config.display.show_row_numbers);
543
544        debug!(
545            "StateCoordinator: Created buffer '{}' with {} rows, {} columns",
546            buffer_name,
547            dataview.row_count(),
548            dataview.column_count()
549        );
550
551        // Add the buffer and switch to it
552        state_container.buffers_mut().add_buffer(buffer);
553        let new_index = state_container.buffers().all_buffers().len() - 1;
554        state_container.buffers_mut().switch_to(new_index);
555
556        // Update state container with the DataView
557        state_container.set_dataview(Some(dataview.clone()));
558
559        // Update viewport manager with the new DataView
560        // Replace the entire ViewportManager with a new one for the DataView
561        *viewport_manager.borrow_mut() = Some(ViewportManager::new(Arc::new(dataview.clone())));
562
563        debug!("StateCoordinator: Created new ViewportManager for DataView");
564
565        // Update navigation state with data dimensions
566        let row_count = dataview.row_count();
567        let column_count = dataview.column_count();
568        state_container.update_data_size(row_count, column_count);
569
570        debug!(
571            "StateCoordinator: DataView '{}' successfully added and all state synchronized",
572            buffer_name
573        );
574
575        Ok(())
576    }
577
578    // ========== QUERY MANAGEMENT ==========
579
580    /// Set SQL query and update all related state
581    /// This centralizes the complex logic of setting up SQL query state
582    pub fn set_sql_query_with_refs(
583        state_container: &mut AppStateContainer,
584        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
585        parser: &mut HybridParser,
586        table_name: &str,
587        raw_table_name: &str,
588        config: &Config,
589    ) -> String {
590        debug!(
591            "StateCoordinator::set_sql_query_with_refs: Setting query for table '{}'",
592            table_name
593        );
594
595        // Create the initial SQL query
596        let auto_query = format!("SELECT * FROM {}", table_name);
597
598        // Update the hybrid parser with the table information
599        if let Some(dataview) = state_container
600            .buffers()
601            .current()
602            .and_then(|b| b.get_dataview())
603        {
604            let columns = dataview.column_names();
605            parser.update_single_table(table_name.to_string(), columns);
606
607            // Set status message
608            let display_msg = if raw_table_name != table_name {
609                format!(
610                    "Loaded '{}' as table '{}' with {} columns. Query pre-populated.",
611                    raw_table_name,
612                    table_name,
613                    dataview.column_count()
614                )
615            } else {
616                format!(
617                    "Loaded table '{}' with {} columns. Query pre-populated.",
618                    table_name,
619                    dataview.column_count()
620                )
621            };
622            state_container.set_status_message(display_msg);
623        }
624
625        // Set initial mode based on config
626        let initial_mode = match config.behavior.start_mode.to_lowercase().as_str() {
627            "results" => AppMode::Results,
628            "command" => AppMode::Command,
629            _ => AppMode::Results, // Default to results if invalid config
630        };
631
632        // Sync mode across all state containers
633        Self::sync_mode_with_refs(
634            state_container,
635            shadow_state,
636            initial_mode.clone(),
637            "initial_load_from_config",
638        );
639
640        debug!(
641            "StateCoordinator: SQL query set to '{}', mode set to {:?}",
642            auto_query, initial_mode
643        );
644
645        auto_query
646    }
647
648    /// Handle query execution and all related state changes
649    /// Returns true if application should exit
650    pub fn handle_execute_query_with_refs(
651        state_container: &mut AppStateContainer,
652        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
653        query: &str,
654    ) -> Result<bool, anyhow::Error> {
655        debug!(
656            "StateCoordinator::handle_execute_query_with_refs: Processing query '{}'",
657            query
658        );
659
660        let trimmed = query.trim();
661
662        if trimmed.is_empty() {
663            state_container
664                .set_status_message("Empty query - please enter a SQL command".to_string());
665            return Ok(false);
666        }
667
668        // Check for special commands
669        if trimmed == ":help" {
670            state_container.set_help_visible(true);
671            Self::sync_mode_with_refs(
672                state_container,
673                shadow_state,
674                AppMode::Help,
675                "help_requested",
676            );
677            state_container.set_status_message("Help Mode - Press ESC to return".to_string());
678            Ok(false)
679        } else if trimmed == ":exit" || trimmed == ":quit" || trimmed == ":q" {
680            Ok(true) // Signal exit
681        } else if trimmed == ":tui" {
682            state_container.set_status_message("Already in TUI mode".to_string());
683            Ok(false)
684        } else {
685            // Regular SQL query - execution handled by TUI
686            state_container.set_status_message(format!("Processing query: '{}'", trimmed));
687            Ok(false)
688        }
689    }
690
691    // ========== QUERY EXECUTION SYNCHRONIZATION ==========
692
693    /// Switch to Results mode after successful query execution
694    pub fn switch_to_results_after_query(&mut self) {
695        self.sync_mode(AppMode::Results, "execute_query_success");
696    }
697
698    /// Static version for delegation
699    pub fn switch_to_results_after_query_with_refs(
700        state_container: &mut AppStateContainer,
701        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
702    ) {
703        Self::sync_mode_with_refs(
704            state_container,
705            shadow_state,
706            AppMode::Results,
707            "execute_query_success",
708        );
709    }
710
711    // ========== SEARCH STATE TRANSITIONS ==========
712
713    /// Apply filter search with proper state coordination
714    pub fn apply_filter_search_with_refs(
715        state_container: &mut AppStateContainer,
716        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
717        pattern: &str,
718    ) {
719        debug!(
720            "StateCoordinator::apply_filter_search_with_refs: Applying filter with pattern '{}'",
721            pattern
722        );
723
724        // Update filter pattern in multiple places for consistency
725        state_container.set_filter_pattern(pattern.to_string());
726        state_container
727            .filter_mut()
728            .set_pattern(pattern.to_string());
729
730        // Log the state before and after
731        let before_count = state_container
732            .get_buffer_dataview()
733            .map(|v| v.source().row_count())
734            .unwrap_or(0);
735
736        debug!(
737            "StateCoordinator: Filter search - case_insensitive={}, rows_before={}",
738            state_container.is_case_insensitive(),
739            before_count
740        );
741
742        // Note: The actual apply_filter() call will be done by TUI
743        // as it has the implementation
744
745        debug!(
746            "StateCoordinator: Filter pattern set to '{}', mode={:?}",
747            pattern,
748            shadow_state.borrow().get_mode()
749        );
750    }
751
752    /// Apply fuzzy filter search with proper state coordination
753    pub fn apply_fuzzy_filter_search_with_refs(
754        state_container: &mut AppStateContainer,
755        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
756        pattern: &str,
757    ) {
758        debug!(
759            "StateCoordinator::apply_fuzzy_filter_search_with_refs: Applying fuzzy filter with pattern '{}'",
760            pattern
761        );
762
763        let before_count = state_container
764            .get_buffer_dataview()
765            .map(|v| v.source().row_count())
766            .unwrap_or(0);
767
768        // Set the fuzzy filter pattern
769        state_container.set_fuzzy_filter_pattern(pattern.to_string());
770
771        debug!(
772            "StateCoordinator: Fuzzy filter - rows_before={}, pattern='{}'",
773            before_count, pattern
774        );
775
776        // Note: The actual apply_fuzzy_filter() call will be done by TUI
777        // After applying, we can check the results
778
779        debug!(
780            "StateCoordinator: Fuzzy filter pattern set, mode={:?}",
781            shadow_state.borrow().get_mode()
782        );
783    }
784
785    /// Apply column search with proper state coordination
786    pub fn apply_column_search_with_refs(
787        state_container: &mut AppStateContainer,
788        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
789        pattern: &str,
790    ) {
791        debug!(
792            "StateCoordinator::apply_column_search_with_refs: Starting column search with pattern '{}'",
793            pattern
794        );
795
796        // Start column search through AppStateContainer
797        state_container.start_column_search(pattern.to_string());
798
799        // Ensure we stay in ColumnSearch mode
800        let current_mode = shadow_state.borrow().get_mode();
801        if current_mode != AppMode::ColumnSearch {
802            debug!(
803                "StateCoordinator: WARNING - Mode was {:?}, restoring to ColumnSearch",
804                current_mode
805            );
806            Self::sync_mode_with_refs(
807                state_container,
808                shadow_state,
809                AppMode::ColumnSearch,
810                "column_search_mode_restore",
811            );
812        }
813
814        debug!(
815            "StateCoordinator: Column search started with pattern '{}'",
816            pattern
817        );
818    }
819
820    // ========== HISTORY SEARCH COORDINATION ==========
821
822    /// Start history search with proper state transitions
823    pub fn start_history_search_with_refs(
824        state_container: &mut AppStateContainer,
825        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
826        current_input: String,
827    ) -> (String, usize) {
828        debug!("StateCoordinator::start_history_search_with_refs: Starting history search");
829
830        let mut input_to_use = current_input;
831
832        // If in Results mode, switch to Command mode first
833        if shadow_state.borrow().is_in_results_mode() {
834            let last_query = state_container.get_last_query();
835            if !last_query.is_empty() {
836                input_to_use = last_query.clone();
837                debug!(
838                    "StateCoordinator: Using last query for history search: '{}'",
839                    last_query
840                );
841            }
842
843            // Transition to Command mode
844            state_container.set_mode(AppMode::Command);
845            shadow_state
846                .borrow_mut()
847                .observe_mode_change(AppMode::Command, "history_search_from_results");
848            state_container.set_table_selected_row(None);
849        }
850
851        // Start history search with the input
852        state_container.start_history_search(input_to_use.clone());
853
854        // Note: update_history_matches_in_container() will be called by TUI
855        // as it has the schema context implementation
856
857        // Get match count for status
858        let match_count = state_container.history_search().matches.len();
859        state_container.set_status_message(format!("History search: {} matches", match_count));
860
861        // Switch to History mode
862        state_container.set_mode(AppMode::History);
863        shadow_state
864            .borrow_mut()
865            .observe_mode_change(AppMode::History, "history_search_started");
866
867        debug!(
868            "StateCoordinator: History search started with {} matches, mode=History",
869            match_count
870        );
871
872        (input_to_use, match_count)
873    }
874
875    // ========== NAVIGATION COORDINATION ==========
876
877    /// Coordinate goto first row with vim search state
878    pub fn goto_first_row_with_refs(
879        state_container: &mut AppStateContainer,
880        vim_search_adapter: Option<
881            &RefCell<crate::ui::search::vim_search_adapter::VimSearchAdapter>,
882        >,
883        viewport_manager: Option<&RefCell<Option<ViewportManager>>>,
884    ) {
885        debug!("StateCoordinator::goto_first_row_with_refs: Going to first row");
886
887        // Set position to first row
888        state_container.set_table_selected_row(Some(0));
889        state_container.set_scroll_offset((0, 0));
890
891        // If vim search is active and navigating, reset to first match
892        if let Some(adapter) = vim_search_adapter {
893            let is_navigating = adapter.borrow().is_navigating();
894
895            if is_navigating {
896                if let Some(viewport_ref) = viewport_manager {
897                    let mut vim_search_mut = adapter.borrow_mut();
898                    let mut viewport_borrow = viewport_ref.borrow_mut();
899                    if let Some(ref mut viewport) = *viewport_borrow {
900                        if let Some(first_match) = vim_search_mut.reset_to_first_match(viewport) {
901                            debug!(
902                                "StateCoordinator: Reset vim search to first match at ({}, {})",
903                                first_match.row, first_match.col
904                            );
905                        }
906                    }
907                }
908            }
909        }
910    }
911
912    /// Coordinate goto last row
913    pub fn goto_last_row_with_refs(state_container: &mut AppStateContainer) {
914        debug!("StateCoordinator::goto_last_row_with_refs: Going to last row");
915
916        // Get total rows from dataview if available
917        if let Some(dataview) = state_container.get_buffer_dataview() {
918            let last_row = dataview.row_count().saturating_sub(1);
919            state_container.set_table_selected_row(Some(last_row));
920
921            // Adjust scroll to show last row
922            // This is simplified - actual viewport calculation would be more complex
923            let scroll_row = last_row.saturating_sub(20); // Assume ~20 visible rows
924            state_container.set_scroll_offset((scroll_row, 0));
925        }
926    }
927
928    /// Coordinate goto specific row
929    pub fn goto_row_with_refs(state_container: &mut AppStateContainer, row: usize) {
930        debug!("StateCoordinator::goto_row_with_refs: Going to row {}", row);
931
932        // Validate row is within bounds
933        if let Some(dataview) = state_container.get_buffer_dataview() {
934            let max_row = dataview.row_count().saturating_sub(1);
935            let target_row = row.min(max_row);
936
937            state_container.set_table_selected_row(Some(target_row));
938
939            // Adjust scroll if needed
940            let current_scroll = state_container.get_scroll_offset().0;
941            if target_row < current_scroll || target_row > current_scroll + 20 {
942                // Center the target row in viewport
943                let new_scroll = target_row.saturating_sub(10);
944                state_container.set_scroll_offset((new_scroll, 0));
945            }
946        }
947    }
948
949    // ========== STATE ACCESS ==========
950
951    /// Get reference to AppStateContainer
952    pub fn state_container(&self) -> &AppStateContainer {
953        &self.state_container
954    }
955
956    /// Get mutable reference to AppStateContainer
957    pub fn state_container_mut(&mut self) -> &mut AppStateContainer {
958        &mut self.state_container
959    }
960
961    /// Get reference to current buffer
962    pub fn current_buffer(&self) -> Option<&Buffer> {
963        self.state_container.buffers().current()
964    }
965
966    /// Get mutable reference to current buffer
967    pub fn current_buffer_mut(&mut self) -> Option<&mut Buffer> {
968        self.state_container.buffers_mut().current_mut()
969    }
970
971    /// Get reference to buffer manager
972    pub fn buffers(&self) -> &BufferManager {
973        self.state_container.buffers()
974    }
975
976    /// Get mutable reference to buffer manager
977    pub fn buffers_mut(&mut self) -> &mut BufferManager {
978        self.state_container.buffers_mut()
979    }
980
981    /// Get reference to hybrid parser
982    pub fn parser(&self) -> &HybridParser {
983        &self.hybrid_parser
984    }
985
986    /// Get mutable reference to hybrid parser
987    pub fn parser_mut(&mut self) -> &mut HybridParser {
988        &mut self.hybrid_parser
989    }
990}