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