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(&format!("Last {} navigation timings:\n", timings.len()));
183            for timing in timings {
184                result.push_str(&format!("  {}\n", timing));
185            }
186            // Calculate average
187            let total_ms: f64 = timings
188                .iter()
189                .filter_map(|s| self.debug_extract_timing(s))
190                .sum();
191            if timings.len() > 0 {
192                let avg_ms = total_ms / timings.len() as f64;
193                result.push_str(&format!("Average navigation time: {:.3}ms\n", avg_ms));
194            }
195        } else {
196            result.push_str("No navigation timing data yet (press j/k to navigate)\n");
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(&format!("Last {} render timings:\n", timings.len()));
206            for timing in timings {
207                result.push_str(&format!("  {}\n", timing));
208            }
209            // Calculate average
210            let total_ms: f64 = timings
211                .iter()
212                .filter_map(|s| self.debug_extract_timing(s))
213                .sum();
214            if timings.len() > 0 {
215                let avg_ms = total_ms / timings.len() as f64;
216                result.push_str(&format!("Average render time: {:.3}ms\n", avg_ms));
217            }
218        } else {
219            result.push_str("No render timing data yet\n");
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(|p| p + 1).unwrap_or(0);
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: {} MB\n", current_mb));
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: {:?}\n\
295            Last Executed Query: '{}'\n\
296            Input Text: '{}'\n\
297            Input Cursor: {}\n\
298            Visual Cursor: {}\n",
299            mode, last_query, input_text, cursor_pos, visual_cursor
300        )
301    }
302
303    fn debug_generate_results_state(
304        &self,
305        results_count: usize,
306        filtered_count: usize,
307        selected_row: Option<usize>,
308        current_column: usize,
309    ) -> String {
310        format!(
311            "\n========== RESULTS STATE ==========\n\
312            Total Results: {}\n\
313            Filtered Results: {}\n\
314            Selected Row: {:?}\n\
315            Current Column: {}\n",
316            results_count, filtered_count, selected_row, current_column
317        )
318    }
319
320    // Viewport state can be a default implementation now that we have buffer_manager and viewport_manager
321    fn debug_generate_viewport_state(&self) -> String {
322        let mut debug_info = String::new();
323        if let Some(buffer) = self.get_buffer_manager().current() {
324            debug_info.push_str("\n========== VIEWPORT STATE ==========\n");
325            let (scroll_row, scroll_col) = buffer.get_scroll_offset();
326            debug_info.push_str(&format!(
327                "Scroll Offset: row={}, col={}\n",
328                scroll_row, scroll_col
329            ));
330            debug_info.push_str(&format!(
331                "Current Column: {}\n",
332                buffer.get_current_column()
333            ));
334            debug_info.push_str(&format!("Selected Row: {:?}\n", buffer.get_selected_row()));
335            debug_info.push_str(&format!("Viewport Lock: {}\n", buffer.is_viewport_lock()));
336            if let Some(lock_row) = buffer.get_viewport_lock_row() {
337                debug_info.push_str(&format!("Viewport Lock Row: {}\n", lock_row));
338            }
339
340            // Add ViewportManager crosshair position
341            if let Some(ref viewport_manager) = *self.get_viewport_manager().borrow() {
342                let visual_row = viewport_manager.get_crosshair_row();
343                let visual_col = viewport_manager.get_crosshair_col();
344                debug_info.push_str(&format!(
345                    "ViewportManager Crosshair (visual): row={}, col={}\n",
346                    visual_row, visual_col
347                ));
348
349                // Also show viewport-relative position
350                if let Some((viewport_row, viewport_col)) =
351                    viewport_manager.get_crosshair_viewport_position()
352                {
353                    debug_info.push_str(&format!(
354                        "Crosshair in viewport (relative): row={}, col={}\n",
355                        viewport_row, viewport_col
356                    ));
357                }
358            }
359
360            // Show visible area calculation
361            if let Some(dataview) = buffer.get_dataview() {
362                let total_rows = dataview.row_count();
363                let total_cols = dataview.column_count();
364                let visible_rows = buffer.get_last_visible_rows();
365                debug_info.push_str(&format!("\nVisible Area:\n"));
366                debug_info.push_str(&format!(
367                    "  Total Data: {} rows × {} columns\n",
368                    total_rows, total_cols
369                ));
370                debug_info.push_str(&format!("  Visible Rows in Terminal: {}\n", visible_rows));
371
372                // Calculate what section is being viewed
373                if total_rows > 0 && visible_rows > 0 {
374                    let start_row = scroll_row.min(total_rows.saturating_sub(1));
375                    let end_row = (scroll_row + visible_rows).min(total_rows);
376                    let percent_start = (start_row as f64 / total_rows as f64 * 100.0) as u32;
377                    let percent_end = (end_row as f64 / total_rows as f64 * 100.0) as u32;
378                    debug_info.push_str(&format!(
379                        "  Viewing rows {}-{} ({}%-{}% of data)\n",
380                        start_row + 1,
381                        end_row,
382                        percent_start,
383                        percent_end
384                    ));
385                }
386
387                if total_cols > 0 {
388                    let visible_cols_estimate = 10; // Estimate based on typical column widths
389                    let start_col = scroll_col.min(total_cols.saturating_sub(1));
390                    let end_col = (scroll_col + visible_cols_estimate).min(total_cols);
391                    debug_info.push_str(&format!(
392                        "  Viewing columns {}-{} of {}\n",
393                        start_col + 1,
394                        end_col,
395                        total_cols
396                    ));
397                }
398            }
399        }
400        debug_info
401    }
402
403    // DataView state can be a default implementation now that we have buffer_manager
404    fn debug_generate_dataview_state(&self) -> String {
405        let mut debug_info = String::new();
406        if let Some(buffer) = self.get_buffer_manager().current() {
407            if let Some(dataview) = buffer.get_dataview() {
408                debug_info.push_str("\n========== DATAVIEW STATE ==========\n");
409
410                // Add the detailed column mapping info
411                debug_info.push_str(&dataview.get_column_debug_info());
412                debug_info.push_str("\n");
413
414                // Show visible columns in order with both indices
415                let visible_columns = dataview.column_names();
416                let column_mappings = dataview.get_column_index_mapping();
417                debug_info.push_str(&format!(
418                    "Visible Columns ({}) with Index Mapping:\n",
419                    visible_columns.len()
420                ));
421                for (visible_idx, col_name, datatable_idx) in &column_mappings {
422                    debug_info.push_str(&format!(
423                        "  V[{:3}] → DT[{:3}] : {}\n",
424                        visible_idx, datatable_idx, col_name
425                    ));
426                }
427
428                // Show row information
429                debug_info.push_str(&format!("\nVisible Rows: {}\n", dataview.row_count()));
430
431                // Show internal visible_columns array (source column indices)
432                debug_info.push_str("\n--- Internal State ---\n");
433
434                // Get the visible_columns indices from DataView
435                let visible_indices = dataview.get_visible_column_indices();
436                debug_info.push_str(&format!("visible_columns array: {:?}\n", visible_indices));
437
438                // Show pinned columns
439                let pinned_names = dataview.get_pinned_column_names();
440                if !pinned_names.is_empty() {
441                    debug_info.push_str(&format!("Pinned Columns ({}):\n", pinned_names.len()));
442                    for (idx, name) in pinned_names.iter().enumerate() {
443                        // Find source index for this pinned column
444                        let source_idx = dataview.source().get_column_index(name).unwrap_or(999);
445                        debug_info.push_str(&format!(
446                            "  [{}] {} (source_idx: {})\n",
447                            idx, name, source_idx
448                        ));
449                    }
450                } else {
451                    debug_info.push_str("Pinned Columns: None\n");
452                }
453
454                // Show sort state
455                let sort_state = dataview.get_sort_state();
456                match sort_state.order {
457                    crate::data::data_view::SortOrder::None => {
458                        debug_info.push_str("Sort State: None\n");
459                    }
460                    crate::data::data_view::SortOrder::Ascending => {
461                        if let Some(col_idx) = sort_state.column {
462                            let col_name = visible_columns
463                                .get(col_idx)
464                                .map(|s| s.as_str())
465                                .unwrap_or("unknown");
466                            debug_info.push_str(&format!(
467                                "Sort State: Ascending on column '{}' (idx: {})\n",
468                                col_name, col_idx
469                            ));
470                        }
471                    }
472                    crate::data::data_view::SortOrder::Descending => {
473                        if let Some(col_idx) = sort_state.column {
474                            let col_name = visible_columns
475                                .get(col_idx)
476                                .map(|s| s.as_str())
477                                .unwrap_or("unknown");
478                            debug_info.push_str(&format!(
479                                "Sort State: Descending on column '{}' (idx: {})\n",
480                                col_name, col_idx
481                            ));
482                        }
483                    }
484                }
485            }
486        }
487        debug_info
488    }
489
490    // DataTable schema can be a default implementation now that we have buffer_manager
491    fn debug_generate_datatable_schema(&self) -> String {
492        let mut debug_info = String::new();
493        if let Some(buffer) = self.get_buffer_manager().current() {
494            if let Some(dataview) = buffer.get_dataview() {
495                let datatable = dataview.source();
496                debug_info.push_str("\n========== DATATABLE SCHEMA ==========\n");
497                debug_info.push_str(&datatable.get_schema_summary());
498            }
499        }
500        debug_info
501    }
502
503    // Rendering methods with default implementations
504    fn render_debug(&self, f: &mut ratatui::Frame, area: ratatui::layout::Rect) {
505        self.get_debug_widget().render(f, area, AppMode::Debug);
506    }
507
508    fn render_pretty_query(&self, f: &mut ratatui::Frame, area: ratatui::layout::Rect) {
509        self.get_debug_widget()
510            .render(f, area, AppMode::PrettyQuery);
511    }
512
513    // Methods that need to be implemented by the TUI (need access to TUI fields)
514    fn debug_generate_parser_info(&self, query: &str) -> String;
515    fn debug_generate_navigation_state(&self) -> String;
516    fn debug_generate_column_search_state(&self) -> String;
517    fn debug_generate_trace_logs(&self) -> String;
518    fn debug_generate_state_logs(&self) -> String;
519}