sql_cli/ui/debug/
context.rs

1//! Debug context trait for extracting debug functionality from the main TUI
2//!
3//! This module provides a trait that encapsulates all debug-related operations,
4//! allowing them to be organized separately from the main TUI logic.
5
6use crate::app_state_container::AppStateContainer;
7use crate::buffer::{AppMode, Buffer, BufferAPI, BufferManager};
8use crate::ui::state::shadow_state::ShadowStateManager;
9use crate::ui::viewport_manager::ViewportManager;
10use crate::widgets::debug_widget::DebugWidget;
11use std::cell::RefCell;
12
13/// Context trait for debug-related functionality
14/// This extracts debug operations into a cohesive interface
15pub trait DebugContext {
16    // Core accessors needed by debug operations
17    fn buffer(&self) -> &dyn BufferAPI;
18    fn buffer_mut(&mut self) -> &mut dyn BufferAPI;
19    fn get_debug_widget(&self) -> &DebugWidget;
20    fn get_debug_widget_mut(&mut self) -> &mut DebugWidget;
21    fn get_shadow_state(&self) -> &RefCell<ShadowStateManager>;
22
23    // Additional accessors for viewport and buffer management
24    fn get_buffer_manager(&self) -> &BufferManager;
25    fn get_viewport_manager(&self) -> &RefCell<Option<ViewportManager>>;
26    fn get_state_container(&self) -> &AppStateContainer;
27    fn get_state_container_mut(&mut self) -> &mut AppStateContainer;
28
29    // Additional accessors for the full debug implementation
30    fn get_navigation_timings(&self) -> &Vec<String>;
31    fn get_render_timings(&self) -> &Vec<String>;
32    fn debug_current_buffer(&mut self);
33    fn get_input_cursor(&self) -> usize;
34    fn get_visual_cursor(&self) -> (usize, usize);
35    fn get_input_text(&self) -> String;
36
37    // The complete toggle_debug_mode implementation from EnhancedTuiApp
38    fn toggle_debug_mode(&mut self) {
39        // Check if we're exiting debug mode
40        if self.buffer().get_mode() == AppMode::Debug {
41            // Exit debug mode - use a helper to avoid borrow issues
42            self.set_mode_via_shadow_state(AppMode::Command, "debug_toggle_exit");
43            return;
44        }
45
46        // Entering debug mode - collect all the data we need
47        let (
48            previous_mode,
49            last_query,
50            input_text,
51            selected_row,
52            current_column,
53            results_count,
54            filtered_count,
55        ) = self.collect_current_state();
56
57        // Switch to debug mode - use a helper to avoid borrow issues
58        self.set_mode_via_shadow_state(AppMode::Debug, "debug_toggle_enter");
59
60        // Generate full debug information
61        self.debug_current_buffer();
62        let cursor_pos = self.get_input_cursor();
63        let visual_cursor = self.get_visual_cursor().1;
64        let query = self.get_input_text();
65
66        // Use the appropriate query for parser debug based on mode
67        let query_for_parser = if previous_mode == AppMode::Results && !last_query.is_empty() {
68            last_query.clone()
69        } else if !query.is_empty() {
70            query.clone()
71        } else if !last_query.is_empty() {
72            last_query.clone()
73        } else {
74            query.clone()
75        };
76
77        // Generate debug info using helper methods
78        let mut debug_info = self.debug_generate_parser_info(&query_for_parser);
79
80        // Add comprehensive buffer state
81        debug_info.push_str(&self.debug_generate_buffer_state(
82            previous_mode,
83            &last_query,
84            &input_text,
85            cursor_pos,
86            visual_cursor,
87        ));
88
89        // Add results state if in Results mode
90        debug_info.push_str(&self.debug_generate_results_state(
91            results_count,
92            filtered_count,
93            selected_row,
94            current_column,
95        ));
96
97        // Add DataTable schema and DataView state
98        debug_info.push_str(&self.debug_generate_datatable_schema());
99        debug_info.push_str(&self.debug_generate_dataview_state());
100
101        // Add memory tracking history
102        debug_info.push_str(&self.debug_generate_memory_info());
103
104        // Add navigation timing statistics
105        debug_info.push_str(&self.format_navigation_timing());
106
107        // Add render timing statistics
108        debug_info.push_str(&self.format_render_timing());
109
110        // Add viewport and navigation information
111        debug_info.push_str(&self.debug_generate_viewport_state());
112        debug_info.push_str(&self.debug_generate_navigation_state());
113
114        // Add buffer manager state info
115        debug_info.push_str(&self.format_buffer_manager_state());
116
117        // Add viewport efficiency metrics
118        debug_info.push_str(&self.debug_generate_viewport_efficiency());
119
120        // Add key chord handler debug info
121        debug_info.push_str(&self.debug_generate_key_chord_info());
122
123        // Add search modes widget debug info
124        debug_info.push_str(&self.debug_generate_search_modes_info());
125
126        // Add column search state if active
127        debug_info.push_str(&self.debug_generate_column_search_state());
128
129        // Add trace logs from ring buffer
130        debug_info.push_str(&self.debug_generate_trace_logs());
131
132        // Add DebugService logs (our StateManager logs!)
133        debug_info.push_str(&self.debug_generate_state_logs());
134
135        // Add AppStateContainer debug dump if available
136        debug_info.push_str(&self.debug_generate_state_container_info());
137
138        // Add Shadow State debug info
139        debug_info.push_str("\n========== SHADOW STATE MANAGER ==========\n");
140        debug_info.push_str(&self.get_shadow_state().borrow().debug_info());
141        debug_info.push_str("\n==========================================\n");
142
143        // Store the debug info in the widget
144        self.get_debug_widget_mut().set_content(debug_info.clone());
145
146        // Copy to clipboard
147        match self
148            .get_state_container_mut()
149            .write_to_clipboard(&debug_info)
150        {
151            Ok(()) => {
152                let status_msg = format!(
153                    "DEBUG INFO copied to clipboard ({} chars)!",
154                    debug_info.len()
155                );
156                self.buffer_mut().set_status_message(status_msg);
157            }
158            Err(e) => {
159                let status_msg = format!("Clipboard error: {e}");
160                self.buffer_mut().set_status_message(status_msg);
161            }
162        }
163    }
164
165    // Required helper methods that implementations must provide
166    fn get_buffer_mut_if_available(&mut self) -> Option<&mut Buffer>;
167    fn set_mode_via_shadow_state(&mut self, mode: AppMode, trigger: &str);
168    fn collect_current_state(
169        &self,
170    ) -> (AppMode, String, String, Option<usize>, usize, usize, usize);
171    fn format_buffer_manager_state(&self) -> String;
172    fn debug_generate_viewport_efficiency(&self) -> String;
173    fn debug_generate_key_chord_info(&self) -> String;
174    fn debug_generate_search_modes_info(&self) -> String;
175    fn debug_generate_state_container_info(&self) -> String;
176
177    // Helper methods with default implementations
178    fn format_navigation_timing(&self) -> String {
179        let mut result = String::from("\n========== NAVIGATION TIMING ==========\n");
180        let timings = self.get_navigation_timings();
181        if timings.is_empty() {
182            result.push_str("No navigation timing data yet (press j/k to navigate)\n");
183        } else {
184            result.push_str(&format!("Last {} navigation timings:\n", timings.len()));
185            for timing in timings {
186                result.push_str(&format!("  {timing}\n"));
187            }
188            // Calculate average
189            let total_ms: f64 = timings
190                .iter()
191                .filter_map(|s| self.debug_extract_timing(s))
192                .sum();
193            if !timings.is_empty() {
194                let avg_ms = total_ms / timings.len() as f64;
195                result.push_str(&format!("Average navigation time: {avg_ms:.3}ms\n"));
196            }
197        }
198        result
199    }
200
201    fn format_render_timing(&self) -> String {
202        let mut result = String::from("\n========== RENDER TIMING ==========\n");
203        let timings = self.get_render_timings();
204        if timings.is_empty() {
205            result.push_str("No render timing data yet\n");
206        } else {
207            result.push_str(&format!("Last {} render timings:\n", timings.len()));
208            for timing in timings {
209                result.push_str(&format!("  {timing}\n"));
210            }
211            // Calculate average
212            let total_ms: f64 = timings
213                .iter()
214                .filter_map(|s| self.debug_extract_timing(s))
215                .sum();
216            if !timings.is_empty() {
217                let avg_ms = total_ms / timings.len() as f64;
218                result.push_str(&format!("Average render time: {avg_ms:.3}ms\n"));
219            }
220        }
221        result
222    }
223
224    fn debug_extract_timing(&self, s: &str) -> Option<f64> {
225        // Extract timing value from a string like "Navigation: 1.234ms"
226        if let Some(ms_pos) = s.find("ms") {
227            let start = s[..ms_pos].rfind(' ').map_or(0, |p| p + 1);
228            s[start..ms_pos].parse().ok()
229        } else {
230            None
231        }
232    }
233
234    // Collect all debug information (simplified version for the trait default)
235    fn collect_debug_info(&self) -> String;
236
237    // Debug generation methods with default implementations where possible
238
239    // Simple methods that don't need TUI state - can be default implementations
240    fn debug_generate_memory_info(&self) -> String {
241        let mut output = String::from("\n========== MEMORY USAGE ==========\n");
242
243        // Get current memory
244        let current_mb = crate::utils::memory_tracker::get_memory_mb();
245        output.push_str(&format!("Current Memory: {current_mb} MB\n"));
246
247        // Perform memory audit - we always have a buffer
248        let buffer = self.buffer();
249        let audits = crate::utils::memory_audit::perform_memory_audit(
250            buffer.get_datatable(),
251            buffer.get_original_source(),
252            buffer.get_dataview(),
253        );
254
255        output.push_str("\nMemory Breakdown:\n");
256        for audit in &audits {
257            output.push_str(&format!(
258                "  {}: {:.2} MB - {}\n",
259                audit.component,
260                audit.mb(),
261                audit.description
262            ));
263        }
264
265        // Check for duplication warning
266        if audits.iter().any(|a| a.component.contains("DUPLICATE")) {
267            output.push_str("\n⚠️  WARNING: Memory duplication detected!\n");
268            output.push_str("  DataTable is being stored twice (datatable + original_source)\n");
269            output.push_str("  Consider using Arc<DataTable> to share data\n");
270        }
271
272        // Add memory history
273        output.push_str(&format!(
274            "\nMemory History:\n{}",
275            crate::utils::memory_tracker::format_memory_history()
276        ));
277
278        output
279    }
280
281    // Note: debug_extract_timing is already defined above
282
283    // Simple formatting methods that can have default implementations
284    fn debug_generate_buffer_state(
285        &self,
286        mode: AppMode,
287        last_query: &str,
288        input_text: &str,
289        cursor_pos: usize,
290        visual_cursor: usize,
291    ) -> String {
292        format!(
293            "\n========== BUFFER STATE ==========\n\
294            Current Mode: {mode:?}\n\
295            Last Executed Query: '{last_query}'\n\
296            Input Text: '{input_text}'\n\
297            Input Cursor: {cursor_pos}\n\
298            Visual Cursor: {visual_cursor}\n"
299        )
300    }
301
302    fn debug_generate_results_state(
303        &self,
304        results_count: usize,
305        filtered_count: usize,
306        selected_row: Option<usize>,
307        current_column: usize,
308    ) -> String {
309        format!(
310            "\n========== RESULTS STATE ==========\n\
311            Total Results: {results_count}\n\
312            Filtered Results: {filtered_count}\n\
313            Selected Row: {selected_row:?}\n\
314            Current Column: {current_column}\n"
315        )
316    }
317
318    // Viewport state can be a default implementation now that we have buffer_manager and viewport_manager
319    fn debug_generate_viewport_state(&self) -> String {
320        let mut debug_info = String::new();
321        if let Some(buffer) = self.get_buffer_manager().current() {
322            debug_info.push_str("\n========== VIEWPORT STATE ==========\n");
323            let (scroll_row, scroll_col) = buffer.get_scroll_offset();
324            debug_info.push_str(&format!(
325                "Scroll Offset: row={scroll_row}, col={scroll_col}\n"
326            ));
327            debug_info.push_str(&format!(
328                "Current Column: {}\n",
329                buffer.get_current_column()
330            ));
331            debug_info.push_str(&format!("Selected Row: {:?}\n", buffer.get_selected_row()));
332            debug_info.push_str(&format!("Viewport Lock: {}\n", buffer.is_viewport_lock()));
333            if let Some(lock_row) = buffer.get_viewport_lock_row() {
334                debug_info.push_str(&format!("Viewport Lock Row: {lock_row}\n"));
335            }
336
337            // Add ViewportManager crosshair position
338            if let Some(ref viewport_manager) = *self.get_viewport_manager().borrow() {
339                let visual_row = viewport_manager.get_crosshair_row();
340                let visual_col = viewport_manager.get_crosshair_col();
341                debug_info.push_str(&format!(
342                    "ViewportManager Crosshair (visual): row={visual_row}, col={visual_col}\n"
343                ));
344
345                // Also show viewport-relative position
346                if let Some((viewport_row, viewport_col)) =
347                    viewport_manager.get_crosshair_viewport_position()
348                {
349                    debug_info.push_str(&format!(
350                        "Crosshair in viewport (relative): row={viewport_row}, col={viewport_col}\n"
351                    ));
352                }
353            }
354
355            // Show visible area calculation
356            if let Some(dataview) = buffer.get_dataview() {
357                let total_rows = dataview.row_count();
358                let total_cols = dataview.column_count();
359                let visible_rows = buffer.get_last_visible_rows();
360                debug_info.push_str("\nVisible Area:\n");
361                debug_info.push_str(&format!(
362                    "  Total Data: {total_rows} rows × {total_cols} columns\n"
363                ));
364                debug_info.push_str(&format!("  Visible Rows in Terminal: {visible_rows}\n"));
365
366                // Calculate what section is being viewed
367                if total_rows > 0 && visible_rows > 0 {
368                    let start_row = scroll_row.min(total_rows.saturating_sub(1));
369                    let end_row = (scroll_row + visible_rows).min(total_rows);
370                    let percent_start = (start_row as f64 / total_rows as f64 * 100.0) as u32;
371                    let percent_end = (end_row as f64 / total_rows as f64 * 100.0) as u32;
372                    debug_info.push_str(&format!(
373                        "  Viewing rows {}-{} ({}%-{}% of data)\n",
374                        start_row + 1,
375                        end_row,
376                        percent_start,
377                        percent_end
378                    ));
379                }
380
381                if total_cols > 0 {
382                    let visible_cols_estimate = 10; // Estimate based on typical column widths
383                    let start_col = scroll_col.min(total_cols.saturating_sub(1));
384                    let end_col = (scroll_col + visible_cols_estimate).min(total_cols);
385                    debug_info.push_str(&format!(
386                        "  Viewing columns {}-{} of {}\n",
387                        start_col + 1,
388                        end_col,
389                        total_cols
390                    ));
391                }
392            }
393        }
394        debug_info
395    }
396
397    // DataView state can be a default implementation now that we have buffer_manager
398    fn debug_generate_dataview_state(&self) -> String {
399        let mut debug_info = String::new();
400        if let Some(buffer) = self.get_buffer_manager().current() {
401            if let Some(dataview) = buffer.get_dataview() {
402                debug_info.push_str("\n========== DATAVIEW STATE ==========\n");
403
404                // Add the detailed column mapping info
405                debug_info.push_str(&dataview.get_column_debug_info());
406                debug_info.push('\n');
407
408                // Show visible columns in order with both indices
409                let visible_columns = dataview.column_names();
410                let column_mappings = dataview.get_column_index_mapping();
411                debug_info.push_str(&format!(
412                    "Visible Columns ({}) with Index Mapping:\n",
413                    visible_columns.len()
414                ));
415                for (visible_idx, col_name, datatable_idx) in &column_mappings {
416                    debug_info.push_str(&format!(
417                        "  V[{visible_idx:3}] → DT[{datatable_idx:3}] : {col_name}\n"
418                    ));
419                }
420
421                // Show row information
422                debug_info.push_str(&format!("\nVisible Rows: {}\n", dataview.row_count()));
423
424                // Show internal visible_columns array (source column indices)
425                debug_info.push_str("\n--- Internal State ---\n");
426
427                // Get the visible_columns indices from DataView
428                let visible_indices = dataview.get_visible_column_indices();
429                debug_info.push_str(&format!("visible_columns array: {visible_indices:?}\n"));
430
431                // Show pinned columns
432                let pinned_names = dataview.get_pinned_column_names();
433                if pinned_names.is_empty() {
434                    debug_info.push_str("Pinned Columns: None\n");
435                } else {
436                    debug_info.push_str(&format!("Pinned Columns ({}):\n", pinned_names.len()));
437                    for (idx, name) in pinned_names.iter().enumerate() {
438                        // Find source index for this pinned column
439                        let source_idx = dataview.source().get_column_index(name).unwrap_or(999);
440                        debug_info
441                            .push_str(&format!("  [{idx}] {name} (source_idx: {source_idx})\n"));
442                    }
443                }
444
445                // Show sort state
446                let sort_state = dataview.get_sort_state();
447                match sort_state.order {
448                    crate::data::data_view::SortOrder::None => {
449                        debug_info.push_str("Sort State: None\n");
450                    }
451                    crate::data::data_view::SortOrder::Ascending => {
452                        if let Some(col_idx) = sort_state.column {
453                            let col_name = visible_columns
454                                .get(col_idx)
455                                .map_or("unknown", std::string::String::as_str);
456                            debug_info.push_str(&format!(
457                                "Sort State: Ascending on column '{col_name}' (idx: {col_idx})\n"
458                            ));
459                        }
460                    }
461                    crate::data::data_view::SortOrder::Descending => {
462                        if let Some(col_idx) = sort_state.column {
463                            let col_name = visible_columns
464                                .get(col_idx)
465                                .map_or("unknown", std::string::String::as_str);
466                            debug_info.push_str(&format!(
467                                "Sort State: Descending on column '{col_name}' (idx: {col_idx})\n"
468                            ));
469                        }
470                    }
471                }
472            }
473        }
474        debug_info
475    }
476
477    // DataTable schema can be a default implementation now that we have buffer_manager
478    fn debug_generate_datatable_schema(&self) -> String {
479        let mut debug_info = String::new();
480        if let Some(buffer) = self.get_buffer_manager().current() {
481            if let Some(dataview) = buffer.get_dataview() {
482                let datatable = dataview.source();
483                debug_info.push_str("\n========== DATATABLE SCHEMA ==========\n");
484                debug_info.push_str(&datatable.get_schema_summary());
485            }
486        }
487        debug_info
488    }
489
490    // Rendering methods with default implementations
491    fn render_debug(&self, f: &mut ratatui::Frame, area: ratatui::layout::Rect) {
492        self.get_debug_widget().render(f, area, AppMode::Debug);
493    }
494
495    fn render_pretty_query(&self, f: &mut ratatui::Frame, area: ratatui::layout::Rect) {
496        self.get_debug_widget()
497            .render(f, area, AppMode::PrettyQuery);
498    }
499
500    // Methods that need to be implemented by the TUI (need access to TUI fields)
501    fn debug_generate_parser_info(&self, query: &str) -> String;
502    fn debug_generate_navigation_state(&self) -> String;
503    fn debug_generate_column_search_state(&self) -> String;
504    fn debug_generate_trace_logs(&self) -> String;
505    fn debug_generate_state_logs(&self) -> String;
506}