sql_cli/
state_manager.rs

1use crate::buffer::{AppMode, BufferAPI};
2use serde_json::Value;
3use std::collections::HashMap;
4use std::time::Instant;
5
6#[derive(Debug, Clone)]
7pub struct ModeState {
8    pub mode: AppMode,
9    pub context: StateContext,
10    pub timestamp: Instant,
11}
12
13#[derive(Debug, Clone, Default)]
14pub struct StateContext {
15    pub input_text: String,
16    pub cursor_position: usize,
17    pub scroll_offset: (usize, usize),
18
19    pub selected_row: Option<usize>,
20    pub selected_column: usize,
21
22    pub search_pattern: Option<String>,
23    pub filter_pattern: Option<String>,
24    pub fuzzy_filter_pattern: Option<String>,
25    pub column_search_pattern: Option<String>,
26
27    pub table_scroll: (usize, usize),
28    pub column_widths: Vec<u16>,
29
30    pub custom_data: HashMap<String, Value>,
31}
32
33pub struct StateManager {
34    mode_stack: Vec<ModeState>,
35    current_context: StateContext,
36    max_stack_size: usize,
37}
38
39impl Default for StateManager {
40    fn default() -> Self {
41        Self::new()
42    }
43}
44
45impl StateManager {
46    #[must_use]
47    pub fn new() -> Self {
48        Self {
49            mode_stack: Vec::new(),
50            current_context: StateContext::default(),
51            max_stack_size: 10,
52        }
53    }
54
55    pub fn push_mode(&mut self, new_mode: AppMode, buffer: &mut dyn BufferAPI) {
56        let current_state = ModeState {
57            mode: buffer.get_mode(),
58            context: self.capture_context(buffer),
59            timestamp: Instant::now(),
60        };
61
62        self.mode_stack.push(current_state);
63        if self.mode_stack.len() > self.max_stack_size {
64            self.mode_stack.remove(0);
65        }
66
67        self.transition_to_mode(new_mode, buffer);
68    }
69
70    pub fn pop_mode(&mut self, buffer: &mut dyn BufferAPI) -> bool {
71        if let Some(previous_state) = self.mode_stack.pop() {
72            self.restore_context(&previous_state.context, buffer);
73            buffer.set_mode(previous_state.mode);
74            true
75        } else {
76            buffer.set_mode(AppMode::Command);
77            false
78        }
79    }
80
81    #[must_use]
82    pub fn peek_previous_mode(&self) -> Option<AppMode> {
83        self.mode_stack.last().map(|state| state.mode.clone())
84    }
85
86    pub fn save_current_state(&mut self, buffer: &dyn BufferAPI) {
87        self.current_context = self.capture_context(buffer);
88    }
89
90    pub fn restore_current_state(&self, buffer: &mut dyn BufferAPI) {
91        self.restore_context(&self.current_context, buffer);
92    }
93
94    #[must_use]
95    pub fn get_stack_depth(&self) -> usize {
96        self.mode_stack.len()
97    }
98
99    pub fn clear_stack(&mut self) {
100        self.mode_stack.clear();
101    }
102
103    fn capture_context(&self, buffer: &dyn BufferAPI) -> StateContext {
104        StateContext {
105            input_text: buffer.get_input_text(),
106            cursor_position: buffer.get_input_cursor_position(),
107            scroll_offset: buffer.get_scroll_offset(),
108            selected_row: buffer.get_selected_row(),
109            selected_column: buffer.get_current_column(),
110            search_pattern: if buffer.get_search_pattern().is_empty() {
111                None
112            } else {
113                Some(buffer.get_search_pattern())
114            },
115            filter_pattern: if buffer.get_filter_pattern().is_empty() {
116                None
117            } else {
118                Some(buffer.get_filter_pattern())
119            },
120            fuzzy_filter_pattern: if buffer.get_fuzzy_filter_pattern().is_empty() {
121                None
122            } else {
123                Some(buffer.get_fuzzy_filter_pattern())
124            },
125            column_search_pattern: {
126                // Column search migrated to AppStateContainer
127                None
128            },
129            table_scroll: buffer.get_scroll_offset(),
130            column_widths: Vec::new(), // TODO: Implement when column width tracking is added
131            custom_data: HashMap::new(),
132        }
133    }
134
135    fn restore_context(&self, context: &StateContext, buffer: &mut dyn BufferAPI) {
136        buffer.set_input_text(context.input_text.clone());
137        buffer.set_input_cursor_position(context.cursor_position);
138        buffer.set_scroll_offset(context.scroll_offset);
139
140        if let Some(row) = context.selected_row {
141            buffer.set_selected_row(Some(row));
142        }
143        buffer.set_current_column(context.selected_column);
144
145        if let Some(pattern) = &context.search_pattern {
146            buffer.set_search_pattern(pattern.clone());
147        }
148        if let Some(pattern) = &context.filter_pattern {
149            buffer.set_filter_pattern(pattern.clone());
150        }
151        if let Some(pattern) = &context.fuzzy_filter_pattern {
152            buffer.set_fuzzy_filter_pattern(pattern.clone());
153        }
154        if let Some(pattern) = &context.column_search_pattern {
155            // Column search migrated to AppStateContainer
156            // buffer.set_column_search_pattern(pattern.clone());
157        }
158    }
159
160    fn transition_to_mode(&self, mode: AppMode, buffer: &mut dyn BufferAPI) {
161        buffer.set_mode(mode.clone());
162
163        match mode {
164            AppMode::Search => {
165                buffer.set_search_pattern(String::new());
166            }
167            AppMode::Filter => {
168                buffer.set_filter_pattern(String::new());
169            }
170            AppMode::FuzzyFilter => {
171                buffer.set_fuzzy_filter_pattern(String::new());
172                buffer.set_fuzzy_filter_active(false);
173            }
174            AppMode::ColumnSearch => {
175                // Column search migrated to AppStateContainer
176                // buffer.set_column_search_pattern(String::new());
177            }
178            _ => {}
179        }
180    }
181
182    #[must_use]
183    pub fn format_debug_info(&self) -> String {
184        let mut info = String::from("========== STATE MANAGER ==========\n");
185        info.push_str(&format!("Stack Depth: {}\n", self.mode_stack.len()));
186
187        if !self.mode_stack.is_empty() {
188            info.push_str("\nMode Stack (oldest to newest):\n");
189            for (i, state) in self.mode_stack.iter().enumerate() {
190                info.push_str(&format!("  [{}] {:?}\n", i, state.mode));
191            }
192        }
193
194        info.push_str("\nCurrent Mode Context:\n");
195        info.push_str(&format!(
196            "  Input Length: {}\n",
197            self.current_context.input_text.len()
198        ));
199        info.push_str(&format!(
200            "  Cursor: {}\n",
201            self.current_context.cursor_position
202        ));
203        info.push_str(&format!(
204            "  Selected Row: {:?}\n",
205            self.current_context.selected_row
206        ));
207        info.push_str(&format!(
208            "  Selected Column: {}\n",
209            self.current_context.selected_column
210        ));
211
212        info
213    }
214}