Skip to main content

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        viewport_manager: &RefCell<Option<ViewportManager>>,
355        pattern: &str,
356    ) -> usize {
357        let case_insensitive = state_container.is_case_insensitive();
358
359        debug!(
360            "StateCoordinator::apply_text_filter_with_refs: Applying text filter with pattern '{}', case_sensitive: {}",
361            pattern, !case_insensitive
362        );
363
364        // Apply filter to DataView and get results
365        let rows_after = if let Some(dataview) = state_container.get_buffer_dataview_mut() {
366            let rows_before = dataview.row_count();
367            dataview.apply_text_filter(pattern, !case_insensitive);
368            let rows_after = dataview.row_count();
369            debug!(
370                "Text filter: {} rows before, {} rows after",
371                rows_before, rows_after
372            );
373            rows_after
374        } else {
375            debug!("No DataView available for text filtering");
376            0
377        };
378
379        // Reset navigation to the first match, the same way the fuzzy filter does.
380        // Without this the crosshair keeps pointing at wherever it was before the
381        // filter, which can now be past the end of the narrowed view.
382        if rows_after > 0 {
383            // Preserve horizontal scroll
384            let col_offset = state_container.get_scroll_offset().1;
385
386            state_container.set_selected_row(Some(0));
387            state_container.set_scroll_offset((0, col_offset));
388            state_container.set_table_selected_row(Some(0));
389
390            {
391                let mut nav = state_container.navigation_mut();
392                nav.selected_row = 0;
393                nav.scroll_offset.0 = 0;
394            }
395
396            if let Ok(mut vm_borrow) = viewport_manager.try_borrow_mut() {
397                if let Some(ref mut vm) = *vm_borrow {
398                    vm.set_crosshair_row(0);
399                    vm.set_scroll_offset(0, col_offset);
400                    debug!(
401                        "StateCoordinator: Reset viewport to first match (row 0) with {} total matches",
402                        rows_after
403                    );
404                }
405            }
406        }
407
408        // Update status message
409        let status = if pattern.is_empty() {
410            "Filter cleared".to_string()
411        } else {
412            format!("Filter applied: '{pattern}' - {rows_after} matches")
413        };
414        state_container.set_status_message(status);
415
416        debug!(
417            "StateCoordinator: Text filter applied - {} matches for pattern '{}'",
418            rows_after, pattern
419        );
420
421        rows_after
422    }
423
424    /// Apply fuzzy filter and coordinate all state updates
425    /// Returns (`match_count`, `filter_indices`)
426    pub fn apply_fuzzy_filter_with_refs(
427        state_container: &mut AppStateContainer,
428        viewport_manager: &RefCell<Option<ViewportManager>>,
429    ) -> (usize, Vec<usize>) {
430        let pattern = state_container.get_fuzzy_filter_pattern();
431        let case_insensitive = state_container.is_case_insensitive();
432
433        debug!(
434            "StateCoordinator::apply_fuzzy_filter_with_refs: Applying fuzzy filter with pattern '{}', case_insensitive: {}",
435            pattern, case_insensitive
436        );
437
438        // Apply filter to DataView and get results
439        let (match_count, indices) =
440            if let Some(dataview) = state_container.get_buffer_dataview_mut() {
441                dataview.apply_fuzzy_filter(&pattern, case_insensitive);
442                let match_count = dataview.row_count();
443                let indices = dataview.get_fuzzy_filter_indices();
444                (match_count, indices)
445            } else {
446                (0, Vec::new())
447            };
448
449        // Update state based on filter results
450        if pattern.is_empty() {
451            state_container.set_fuzzy_filter_active(false);
452            state_container.set_status_message("Fuzzy filter cleared".to_string());
453        } else {
454            state_container.set_fuzzy_filter_active(true);
455            state_container.set_status_message(format!("Fuzzy filter: {match_count} matches"));
456
457            // Reset navigation to first match if we have results
458            if match_count > 0 {
459                // Get current column offset to preserve horizontal scroll
460                let col_offset = state_container.get_scroll_offset().1;
461
462                // Reset to first row of filtered results
463                state_container.set_selected_row(Some(0));
464                state_container.set_scroll_offset((0, col_offset));
465                state_container.set_table_selected_row(Some(0));
466
467                // Update navigation state
468                let mut nav = state_container.navigation_mut();
469                nav.selected_row = 0;
470                nav.scroll_offset.0 = 0;
471
472                // Update ViewportManager if present
473                if let Ok(mut vm_borrow) = viewport_manager.try_borrow_mut() {
474                    if let Some(ref mut vm) = *vm_borrow {
475                        vm.set_crosshair_row(0);
476                        vm.set_scroll_offset(0, col_offset);
477                        debug!(
478                            "StateCoordinator: Reset viewport to first match (row 0) with {} total matches",
479                            match_count
480                        );
481                    }
482                }
483            }
484        }
485
486        debug!(
487            "StateCoordinator: Fuzzy filter applied - {} matches, pattern: '{}'",
488            match_count, pattern
489        );
490
491        (match_count, indices)
492    }
493
494    // ========== TABLE STATE MANAGEMENT ==========
495
496    /// Reset all table-related state to initial values
497    /// This is typically called when switching data sources or after queries
498    pub fn reset_table_state_with_refs(
499        state_container: &mut AppStateContainer,
500        viewport_manager: &RefCell<Option<ViewportManager>>,
501    ) {
502        debug!("StateCoordinator::reset_table_state_with_refs: Resetting all table state");
503
504        // Reset navigation state
505        state_container.navigation_mut().reset();
506        state_container.set_table_selected_row(Some(0));
507        state_container.reset_navigation_state();
508
509        // Reset ViewportManager if it exists
510        if let Ok(mut vm_borrow) = viewport_manager.try_borrow_mut() {
511            if let Some(ref mut vm) = *vm_borrow {
512                vm.reset_crosshair();
513                debug!("StateCoordinator: Reset ViewportManager crosshair position");
514            }
515        }
516
517        // Clear filter state
518        state_container.filter_mut().clear();
519
520        // Clear search state
521        {
522            let mut search = state_container.search_mut();
523            search.pattern.clear();
524            search.current_match = 0;
525            search.matches.clear();
526            search.is_active = false;
527        }
528
529        // Clear fuzzy filter state
530        state_container.clear_fuzzy_filter_state();
531
532        // Clear column search state (added for completeness)
533        state_container.clear_column_search();
534
535        debug!("StateCoordinator: Table state reset complete");
536    }
537
538    // ========== DATAVIEW MANAGEMENT ==========
539
540    /// Add a new `DataView` and coordinate all necessary state updates
541    /// This centralizes the complex logic of adding a new data source
542    pub fn add_dataview_with_refs(
543        state_container: &mut AppStateContainer,
544        viewport_manager: &RefCell<Option<ViewportManager>>,
545        dataview: DataView,
546        source_name: &str,
547        config: &Config,
548    ) -> Result<(), anyhow::Error> {
549        debug!(
550            "StateCoordinator::add_dataview_with_refs: Adding DataView for '{}'",
551            source_name
552        );
553
554        // Create a new buffer with the DataView
555        let buffer_id = state_container.buffers().all_buffers().len() + 1;
556        let mut buffer = crate::buffer::Buffer::new(buffer_id);
557
558        // Set the DataView directly
559        buffer.set_dataview(Some(dataview.clone()));
560
561        // Use just the filename for the buffer name, not the full path
562        let buffer_name = std::path::Path::new(source_name)
563            .file_name()
564            .and_then(|s| s.to_str())
565            .unwrap_or(source_name)
566            .to_string();
567        buffer.set_name(buffer_name.clone());
568
569        // Apply config settings to the buffer
570        buffer.set_case_insensitive(config.behavior.case_insensitive_default);
571        buffer.set_compact_mode(config.display.compact_mode);
572        buffer.set_show_row_numbers(config.display.show_row_numbers);
573
574        debug!(
575            "StateCoordinator: Created buffer '{}' with {} rows, {} columns",
576            buffer_name,
577            dataview.row_count(),
578            dataview.column_count()
579        );
580
581        // Add the buffer and switch to it
582        state_container.buffers_mut().add_buffer(buffer);
583        let new_index = state_container.buffers().all_buffers().len() - 1;
584        state_container.buffers_mut().switch_to(new_index);
585
586        // Update state container with the DataView
587        state_container.set_dataview(Some(dataview.clone()));
588
589        // Update viewport manager with the new DataView
590        // Replace the entire ViewportManager with a new one for the DataView
591        *viewport_manager.borrow_mut() = Some(ViewportManager::new(Arc::new(dataview.clone())));
592
593        debug!("StateCoordinator: Created new ViewportManager for DataView");
594
595        // Update navigation state with data dimensions
596        let row_count = dataview.row_count();
597        let column_count = dataview.column_count();
598        state_container.update_data_size(row_count, column_count);
599
600        debug!(
601            "StateCoordinator: DataView '{}' successfully added and all state synchronized",
602            buffer_name
603        );
604
605        Ok(())
606    }
607
608    // ========== QUERY MANAGEMENT ==========
609
610    /// Set SQL query and update all related state
611    /// This centralizes the complex logic of setting up SQL query state
612    pub fn set_sql_query_with_refs(
613        state_container: &mut AppStateContainer,
614        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
615        parser: &mut HybridParser,
616        table_name: &str,
617        raw_table_name: &str,
618        config: &Config,
619    ) -> String {
620        debug!(
621            "StateCoordinator::set_sql_query_with_refs: Setting query for table '{}'",
622            table_name
623        );
624
625        // Create the initial SQL query
626        let auto_query = format!("SELECT * FROM {table_name}");
627
628        // Update the hybrid parser with the table information
629        if let Some(dataview) = state_container
630            .buffers()
631            .current()
632            .and_then(|b| b.get_dataview())
633        {
634            let columns = dataview.column_names();
635            parser.update_single_table(table_name.to_string(), columns);
636
637            // Set status message
638            let display_msg = if raw_table_name == table_name {
639                format!(
640                    "Loaded table '{}' with {} columns. Query pre-populated.",
641                    table_name,
642                    dataview.column_count()
643                )
644            } else {
645                format!(
646                    "Loaded '{}' as table '{}' with {} columns. Query pre-populated.",
647                    raw_table_name,
648                    table_name,
649                    dataview.column_count()
650                )
651            };
652            state_container.set_status_message(display_msg);
653        }
654
655        // Set initial mode based on config
656        let initial_mode = match config.behavior.start_mode.to_lowercase().as_str() {
657            "results" => AppMode::Results,
658            "command" => AppMode::Command,
659            _ => AppMode::Results, // Default to results if invalid config
660        };
661
662        // Sync mode across all state containers
663        Self::sync_mode_with_refs(
664            state_container,
665            shadow_state,
666            initial_mode.clone(),
667            "initial_load_from_config",
668        );
669
670        debug!(
671            "StateCoordinator: SQL query set to '{}', mode set to {:?}",
672            auto_query, initial_mode
673        );
674
675        auto_query
676    }
677
678    /// Handle query execution and all related state changes
679    /// Returns true if application should exit
680    pub fn handle_execute_query_with_refs(
681        state_container: &mut AppStateContainer,
682        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
683        query: &str,
684    ) -> Result<bool, anyhow::Error> {
685        debug!(
686            "StateCoordinator::handle_execute_query_with_refs: Processing query '{}'",
687            query
688        );
689
690        let trimmed = query.trim();
691
692        if trimmed.is_empty() {
693            state_container
694                .set_status_message("Empty query - please enter a SQL command".to_string());
695            return Ok(false);
696        }
697
698        // Check for special commands
699        if trimmed == ":help" {
700            state_container.set_help_visible(true);
701            Self::sync_mode_with_refs(
702                state_container,
703                shadow_state,
704                AppMode::Help,
705                "help_requested",
706            );
707            state_container.set_status_message("Help Mode - Press ESC to return".to_string());
708            Ok(false)
709        } else if trimmed == ":exit" || trimmed == ":quit" || trimmed == ":q" {
710            Ok(true) // Signal exit
711        } else if trimmed == ":tui" {
712            state_container.set_status_message("Already in TUI mode".to_string());
713            Ok(false)
714        } else {
715            // Regular SQL query - execution handled by TUI
716            state_container.set_status_message(format!("Processing query: '{trimmed}'"));
717            Ok(false)
718        }
719    }
720
721    // ========== QUERY EXECUTION SYNCHRONIZATION ==========
722
723    /// Switch to Results mode after successful query execution
724    pub fn switch_to_results_after_query(&mut self) {
725        self.sync_mode(AppMode::Results, "execute_query_success");
726    }
727
728    /// Static version for delegation
729    pub fn switch_to_results_after_query_with_refs(
730        state_container: &mut AppStateContainer,
731        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
732    ) {
733        Self::sync_mode_with_refs(
734            state_container,
735            shadow_state,
736            AppMode::Results,
737            "execute_query_success",
738        );
739    }
740
741    // ========== SEARCH STATE TRANSITIONS ==========
742
743    /// Apply filter search with proper state coordination
744    pub fn apply_filter_search_with_refs(
745        state_container: &mut AppStateContainer,
746        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
747        pattern: &str,
748    ) {
749        debug!(
750            "StateCoordinator::apply_filter_search_with_refs: Applying filter with pattern '{}'",
751            pattern
752        );
753
754        // Update filter pattern in multiple places for consistency
755        state_container.set_filter_pattern(pattern.to_string());
756        state_container
757            .filter_mut()
758            .set_pattern(pattern.to_string());
759
760        // Log the state before and after
761        let before_count = state_container
762            .get_buffer_dataview()
763            .map_or(0, |v| v.source().row_count());
764
765        debug!(
766            "StateCoordinator: Filter search - case_insensitive={}, rows_before={}",
767            state_container.is_case_insensitive(),
768            before_count
769        );
770
771        // Note: The actual apply_filter() call will be done by TUI
772        // as it has the implementation
773
774        debug!(
775            "StateCoordinator: Filter pattern set to '{}', mode={:?}",
776            pattern,
777            shadow_state.borrow().get_mode()
778        );
779    }
780
781    /// Apply fuzzy filter search with proper state coordination
782    pub fn apply_fuzzy_filter_search_with_refs(
783        state_container: &mut AppStateContainer,
784        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
785        pattern: &str,
786    ) {
787        debug!(
788            "StateCoordinator::apply_fuzzy_filter_search_with_refs: Applying fuzzy filter with pattern '{}'",
789            pattern
790        );
791
792        let before_count = state_container
793            .get_buffer_dataview()
794            .map_or(0, |v| v.source().row_count());
795
796        // Set the fuzzy filter pattern
797        state_container.set_fuzzy_filter_pattern(pattern.to_string());
798
799        debug!(
800            "StateCoordinator: Fuzzy filter - rows_before={}, pattern='{}'",
801            before_count, pattern
802        );
803
804        // Note: The actual apply_fuzzy_filter() call will be done by TUI
805        // After applying, we can check the results
806
807        debug!(
808            "StateCoordinator: Fuzzy filter pattern set, mode={:?}",
809            shadow_state.borrow().get_mode()
810        );
811    }
812
813    /// Apply column search with proper state coordination
814    pub fn apply_column_search_with_refs(
815        state_container: &mut AppStateContainer,
816        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
817        pattern: &str,
818    ) {
819        debug!(
820            "StateCoordinator::apply_column_search_with_refs: Starting column search with pattern '{}'",
821            pattern
822        );
823
824        // Start column search through AppStateContainer
825        state_container.start_column_search(pattern.to_string());
826
827        // Ensure we stay in ColumnSearch mode
828        let current_mode = shadow_state.borrow().get_mode();
829        if current_mode != AppMode::ColumnSearch {
830            debug!(
831                "StateCoordinator: WARNING - Mode was {:?}, restoring to ColumnSearch",
832                current_mode
833            );
834            Self::sync_mode_with_refs(
835                state_container,
836                shadow_state,
837                AppMode::ColumnSearch,
838                "column_search_mode_restore",
839            );
840        }
841
842        debug!(
843            "StateCoordinator: Column search started with pattern '{}'",
844            pattern
845        );
846    }
847
848    // ========== HISTORY SEARCH COORDINATION ==========
849
850    /// Start history search with proper state transitions
851    pub fn start_history_search_with_refs(
852        state_container: &mut AppStateContainer,
853        shadow_state: &RefCell<crate::ui::state::shadow_state::ShadowStateManager>,
854        current_input: String,
855    ) -> (String, usize) {
856        debug!("StateCoordinator::start_history_search_with_refs: Starting history search");
857
858        let mut input_to_use = current_input;
859
860        // If in Results mode, switch to Command mode first
861        if shadow_state.borrow().is_in_results_mode() {
862            let last_query = state_container.get_last_query();
863            if !last_query.is_empty() {
864                input_to_use = last_query.clone();
865                debug!(
866                    "StateCoordinator: Using last query for history search: '{}'",
867                    last_query
868                );
869            }
870
871            // Transition to Command mode
872            state_container.set_mode(AppMode::Command);
873            shadow_state
874                .borrow_mut()
875                .observe_mode_change(AppMode::Command, "history_search_from_results");
876            state_container.set_table_selected_row(None);
877        }
878
879        // Start history search with the input
880        state_container.start_history_search(input_to_use.clone());
881
882        // Note: update_history_matches_in_container() will be called by TUI
883        // as it has the schema context implementation
884
885        // Get match count for status
886        let match_count = state_container.history_search().matches.len();
887        state_container.set_status_message(format!("History search: {match_count} matches"));
888
889        // Switch to History mode
890        state_container.set_mode(AppMode::History);
891        shadow_state
892            .borrow_mut()
893            .observe_mode_change(AppMode::History, "history_search_started");
894
895        debug!(
896            "StateCoordinator: History search started with {} matches, mode=History",
897            match_count
898        );
899
900        (input_to_use, match_count)
901    }
902
903    // ========== NAVIGATION COORDINATION ==========
904
905    /// Coordinate goto first row with vim search state
906    pub fn goto_first_row_with_refs(
907        state_container: &mut AppStateContainer,
908        vim_search_adapter: Option<
909            &RefCell<crate::ui::search::vim_search_adapter::VimSearchAdapter>,
910        >,
911        viewport_manager: Option<&RefCell<Option<ViewportManager>>>,
912    ) {
913        debug!("StateCoordinator::goto_first_row_with_refs: Going to first row");
914
915        // Set position to first row
916        state_container.set_table_selected_row(Some(0));
917        state_container.set_scroll_offset((0, 0));
918
919        // If vim search is active and navigating, reset to first match
920        if let Some(adapter) = vim_search_adapter {
921            let is_navigating = adapter.borrow().is_navigating();
922
923            if is_navigating {
924                if let Some(viewport_ref) = viewport_manager {
925                    let mut vim_search_mut = adapter.borrow_mut();
926                    let mut viewport_borrow = viewport_ref.borrow_mut();
927                    if let Some(ref mut viewport) = *viewport_borrow {
928                        if let Some(first_match) = vim_search_mut.reset_to_first_match(viewport) {
929                            debug!(
930                                "StateCoordinator: Reset vim search to first match at ({}, {})",
931                                first_match.row, first_match.col
932                            );
933                        }
934                    }
935                }
936            }
937        }
938    }
939
940    /// Coordinate goto last row
941    pub fn goto_last_row_with_refs(state_container: &mut AppStateContainer) {
942        debug!("StateCoordinator::goto_last_row_with_refs: Going to last row");
943
944        // Get total rows from dataview if available
945        if let Some(dataview) = state_container.get_buffer_dataview() {
946            let last_row = dataview.row_count().saturating_sub(1);
947            state_container.set_table_selected_row(Some(last_row));
948
949            // Adjust scroll to show last row
950            // This is simplified - actual viewport calculation would be more complex
951            let scroll_row = last_row.saturating_sub(20); // Assume ~20 visible rows
952            state_container.set_scroll_offset((scroll_row, 0));
953        }
954    }
955
956    /// Coordinate goto specific row
957    pub fn goto_row_with_refs(state_container: &mut AppStateContainer, row: usize) {
958        debug!("StateCoordinator::goto_row_with_refs: Going to row {}", row);
959
960        // Validate row is within bounds
961        if let Some(dataview) = state_container.get_buffer_dataview() {
962            let max_row = dataview.row_count().saturating_sub(1);
963            let target_row = row.min(max_row);
964
965            state_container.set_table_selected_row(Some(target_row));
966
967            // Adjust scroll if needed
968            let current_scroll = state_container.get_scroll_offset().0;
969            if target_row < current_scroll || target_row > current_scroll + 20 {
970                // Center the target row in viewport
971                let new_scroll = target_row.saturating_sub(10);
972                state_container.set_scroll_offset((new_scroll, 0));
973            }
974        }
975    }
976
977    // ========== STATE ACCESS ==========
978
979    /// Get reference to `AppStateContainer`
980    pub fn state_container(&self) -> &AppStateContainer {
981        &self.state_container
982    }
983
984    /// Get mutable reference to `AppStateContainer`
985    pub fn state_container_mut(&mut self) -> &mut AppStateContainer {
986        &mut self.state_container
987    }
988
989    /// Get reference to current buffer
990    pub fn current_buffer(&self) -> Option<&Buffer> {
991        self.state_container.buffers().current()
992    }
993
994    /// Get mutable reference to current buffer
995    pub fn current_buffer_mut(&mut self) -> Option<&mut Buffer> {
996        self.state_container.buffers_mut().current_mut()
997    }
998
999    /// Get reference to buffer manager
1000    pub fn buffers(&self) -> &BufferManager {
1001        self.state_container.buffers()
1002    }
1003
1004    /// Get mutable reference to buffer manager
1005    pub fn buffers_mut(&mut self) -> &mut BufferManager {
1006        self.state_container.buffers_mut()
1007    }
1008
1009    /// Get reference to hybrid parser
1010    pub fn parser(&self) -> &HybridParser {
1011        &self.hybrid_parser
1012    }
1013
1014    /// Get mutable reference to hybrid parser
1015    pub fn parser_mut(&mut self) -> &mut HybridParser {
1016        &mut self.hybrid_parser
1017    }
1018}