use crate::app_state_container::AppStateContainer;
use crate::buffer::{AppMode, Buffer, BufferAPI, BufferManager};
use crate::ui::state::shadow_state::ShadowStateManager;
use crate::ui::viewport_manager::ViewportManager;
use crate::widgets::debug_widget::DebugWidget;
use std::cell::RefCell;
pub trait DebugContext {
fn buffer(&self) -> &dyn BufferAPI;
fn buffer_mut(&mut self) -> &mut dyn BufferAPI;
fn get_debug_widget(&self) -> &DebugWidget;
fn get_debug_widget_mut(&mut self) -> &mut DebugWidget;
fn get_shadow_state(&self) -> &RefCell<ShadowStateManager>;
fn get_buffer_manager(&self) -> &BufferManager;
fn get_viewport_manager(&self) -> &RefCell<Option<ViewportManager>>;
fn get_state_container(&self) -> &AppStateContainer;
fn get_state_container_mut(&mut self) -> &mut AppStateContainer;
fn get_navigation_timings(&self) -> &Vec<String>;
fn get_render_timings(&self) -> &Vec<String>;
fn debug_current_buffer(&mut self);
fn get_input_cursor(&self) -> usize;
fn get_visual_cursor(&self) -> (usize, usize);
fn get_input_text(&self) -> String;
fn toggle_debug_mode(&mut self) {
if self.buffer().get_mode() == AppMode::Debug {
self.set_mode_via_shadow_state(AppMode::Command, "debug_toggle_exit");
return;
}
let (
previous_mode,
last_query,
input_text,
selected_row,
current_column,
results_count,
filtered_count,
) = self.collect_current_state();
self.set_mode_via_shadow_state(AppMode::Debug, "debug_toggle_enter");
self.debug_current_buffer();
let cursor_pos = self.get_input_cursor();
let visual_cursor = self.get_visual_cursor().1;
let query = self.get_input_text();
let query_for_parser = if previous_mode == AppMode::Results && !last_query.is_empty() {
last_query.clone()
} else if !query.is_empty() {
query.clone()
} else if !last_query.is_empty() {
last_query.clone()
} else {
query.clone()
};
let mut debug_info = self.debug_generate_parser_info(&query_for_parser);
debug_info.push_str(&self.debug_generate_buffer_state(
previous_mode,
&last_query,
&input_text,
cursor_pos,
visual_cursor,
));
debug_info.push_str(&self.debug_generate_results_state(
results_count,
filtered_count,
selected_row,
current_column,
));
debug_info.push_str(&self.debug_generate_datatable_schema());
debug_info.push_str(&self.debug_generate_dataview_state());
debug_info.push_str(&self.debug_generate_memory_info());
debug_info.push_str(&self.format_navigation_timing());
debug_info.push_str(&self.format_render_timing());
debug_info.push_str(&self.debug_generate_viewport_state());
debug_info.push_str(&self.debug_generate_navigation_state());
debug_info.push_str(&self.format_buffer_manager_state());
debug_info.push_str(&self.debug_generate_viewport_efficiency());
debug_info.push_str(&self.debug_generate_key_chord_info());
debug_info.push_str(&self.debug_generate_search_modes_info());
debug_info.push_str(&self.debug_generate_column_search_state());
debug_info.push_str(&self.debug_generate_trace_logs());
debug_info.push_str(&self.debug_generate_state_logs());
debug_info.push_str(&self.debug_generate_state_container_info());
debug_info.push_str("\n========== SHADOW STATE MANAGER ==========\n");
debug_info.push_str(&self.get_shadow_state().borrow().debug_info());
debug_info.push_str("\n==========================================\n");
self.get_debug_widget_mut().set_content(debug_info.clone());
match self
.get_state_container_mut()
.write_to_clipboard(&debug_info)
{
Ok(()) => {
let status_msg = format!(
"DEBUG INFO copied to clipboard ({} chars)!",
debug_info.len()
);
self.buffer_mut().set_status_message(status_msg);
}
Err(e) => {
let status_msg = format!("Clipboard error: {e}");
self.buffer_mut().set_status_message(status_msg);
}
}
}
fn get_buffer_mut_if_available(&mut self) -> Option<&mut Buffer>;
fn set_mode_via_shadow_state(&mut self, mode: AppMode, trigger: &str);
fn collect_current_state(
&self,
) -> (AppMode, String, String, Option<usize>, usize, usize, usize);
fn format_buffer_manager_state(&self) -> String;
fn debug_generate_viewport_efficiency(&self) -> String;
fn debug_generate_key_chord_info(&self) -> String;
fn debug_generate_search_modes_info(&self) -> String;
fn debug_generate_state_container_info(&self) -> String;
fn format_navigation_timing(&self) -> String {
let mut result = String::from("\n========== NAVIGATION TIMING ==========\n");
let timings = self.get_navigation_timings();
if timings.is_empty() {
result.push_str("No navigation timing data yet (press j/k to navigate)\n");
} else {
result.push_str(&format!("Last {} navigation timings:\n", timings.len()));
for timing in timings {
result.push_str(&format!(" {timing}\n"));
}
let total_ms: f64 = timings
.iter()
.filter_map(|s| self.debug_extract_timing(s))
.sum();
if !timings.is_empty() {
let avg_ms = total_ms / timings.len() as f64;
result.push_str(&format!("Average navigation time: {avg_ms:.3}ms\n"));
}
}
result
}
fn format_render_timing(&self) -> String {
let mut result = String::from("\n========== RENDER TIMING ==========\n");
let timings = self.get_render_timings();
if timings.is_empty() {
result.push_str("No render timing data yet\n");
} else {
result.push_str(&format!("Last {} render timings:\n", timings.len()));
for timing in timings {
result.push_str(&format!(" {timing}\n"));
}
let total_ms: f64 = timings
.iter()
.filter_map(|s| self.debug_extract_timing(s))
.sum();
if !timings.is_empty() {
let avg_ms = total_ms / timings.len() as f64;
result.push_str(&format!("Average render time: {avg_ms:.3}ms\n"));
}
}
result
}
fn debug_extract_timing(&self, s: &str) -> Option<f64> {
if let Some(ms_pos) = s.find("ms") {
let start = s[..ms_pos].rfind(' ').map_or(0, |p| p + 1);
s[start..ms_pos].parse().ok()
} else {
None
}
}
fn collect_debug_info(&self) -> String;
fn debug_generate_memory_info(&self) -> String {
let mut output = String::from("\n========== MEMORY USAGE ==========\n");
let current_mb = crate::utils::memory_tracker::get_memory_mb();
output.push_str(&format!("Current Memory: {current_mb} MB\n"));
let buffer = self.buffer();
let audits = crate::utils::memory_audit::perform_memory_audit(
buffer.get_datatable(),
buffer.get_original_source(),
buffer.get_dataview(),
);
output.push_str("\nMemory Breakdown:\n");
for audit in &audits {
output.push_str(&format!(
" {}: {:.2} MB - {}\n",
audit.component,
audit.mb(),
audit.description
));
}
if audits.iter().any(|a| a.component.contains("DUPLICATE")) {
output.push_str("\n⚠️ WARNING: Memory duplication detected!\n");
output.push_str(" DataTable is being stored twice (datatable + original_source)\n");
output.push_str(" Consider using Arc<DataTable> to share data\n");
}
output.push_str(&format!(
"\nMemory History:\n{}",
crate::utils::memory_tracker::format_memory_history()
));
output
}
fn debug_generate_buffer_state(
&self,
mode: AppMode,
last_query: &str,
input_text: &str,
cursor_pos: usize,
visual_cursor: usize,
) -> String {
format!(
"\n========== BUFFER STATE ==========\n\
Current Mode: {mode:?}\n\
Last Executed Query: '{last_query}'\n\
Input Text: '{input_text}'\n\
Input Cursor: {cursor_pos}\n\
Visual Cursor: {visual_cursor}\n"
)
}
fn debug_generate_results_state(
&self,
results_count: usize,
filtered_count: usize,
selected_row: Option<usize>,
current_column: usize,
) -> String {
format!(
"\n========== RESULTS STATE ==========\n\
Total Results: {results_count}\n\
Filtered Results: {filtered_count}\n\
Selected Row: {selected_row:?}\n\
Current Column: {current_column}\n"
)
}
fn debug_generate_viewport_state(&self) -> String {
let mut debug_info = String::new();
if let Some(buffer) = self.get_buffer_manager().current() {
debug_info.push_str("\n========== VIEWPORT STATE ==========\n");
let (scroll_row, scroll_col) = buffer.get_scroll_offset();
debug_info.push_str(&format!(
"Scroll Offset: row={scroll_row}, col={scroll_col}\n"
));
debug_info.push_str(&format!(
"Current Column: {}\n",
buffer.get_current_column()
));
debug_info.push_str(&format!("Selected Row: {:?}\n", buffer.get_selected_row()));
debug_info.push_str(&format!("Viewport Lock: {}\n", buffer.is_viewport_lock()));
if let Some(lock_row) = buffer.get_viewport_lock_row() {
debug_info.push_str(&format!("Viewport Lock Row: {lock_row}\n"));
}
if let Some(ref viewport_manager) = *self.get_viewport_manager().borrow() {
let visual_row = viewport_manager.get_crosshair_row();
let visual_col = viewport_manager.get_crosshair_col();
debug_info.push_str(&format!(
"ViewportManager Crosshair (visual): row={visual_row}, col={visual_col}\n"
));
if let Some((viewport_row, viewport_col)) =
viewport_manager.get_crosshair_viewport_position()
{
debug_info.push_str(&format!(
"Crosshair in viewport (relative): row={viewport_row}, col={viewport_col}\n"
));
}
}
if let Some(dataview) = buffer.get_dataview() {
let total_rows = dataview.row_count();
let total_cols = dataview.column_count();
let visible_rows = buffer.get_last_visible_rows();
debug_info.push_str("\nVisible Area:\n");
debug_info.push_str(&format!(
" Total Data: {total_rows} rows × {total_cols} columns\n"
));
debug_info.push_str(&format!(" Visible Rows in Terminal: {visible_rows}\n"));
if total_rows > 0 && visible_rows > 0 {
let start_row = scroll_row.min(total_rows.saturating_sub(1));
let end_row = (scroll_row + visible_rows).min(total_rows);
let percent_start = (start_row as f64 / total_rows as f64 * 100.0) as u32;
let percent_end = (end_row as f64 / total_rows as f64 * 100.0) as u32;
debug_info.push_str(&format!(
" Viewing rows {}-{} ({}%-{}% of data)\n",
start_row + 1,
end_row,
percent_start,
percent_end
));
}
if total_cols > 0 {
let visible_cols_estimate = 10; let start_col = scroll_col.min(total_cols.saturating_sub(1));
let end_col = (scroll_col + visible_cols_estimate).min(total_cols);
debug_info.push_str(&format!(
" Viewing columns {}-{} of {}\n",
start_col + 1,
end_col,
total_cols
));
}
}
}
debug_info
}
fn debug_generate_dataview_state(&self) -> String {
let mut debug_info = String::new();
if let Some(buffer) = self.get_buffer_manager().current() {
if let Some(dataview) = buffer.get_dataview() {
debug_info.push_str("\n========== DATAVIEW STATE ==========\n");
debug_info.push_str(&dataview.get_column_debug_info());
debug_info.push('\n');
let visible_columns = dataview.column_names();
let column_mappings = dataview.get_column_index_mapping();
debug_info.push_str(&format!(
"Visible Columns ({}) with Index Mapping:\n",
visible_columns.len()
));
for (visible_idx, col_name, datatable_idx) in &column_mappings {
debug_info.push_str(&format!(
" V[{visible_idx:3}] → DT[{datatable_idx:3}] : {col_name}\n"
));
}
debug_info.push_str(&format!("\nVisible Rows: {}\n", dataview.row_count()));
debug_info.push_str("\n--- Internal State ---\n");
let visible_indices = dataview.get_visible_column_indices();
debug_info.push_str(&format!("visible_columns array: {visible_indices:?}\n"));
let pinned_names = dataview.get_pinned_column_names();
if pinned_names.is_empty() {
debug_info.push_str("Pinned Columns: None\n");
} else {
debug_info.push_str(&format!("Pinned Columns ({}):\n", pinned_names.len()));
for (idx, name) in pinned_names.iter().enumerate() {
let source_idx = dataview.source().get_column_index(name).unwrap_or(999);
debug_info
.push_str(&format!(" [{idx}] {name} (source_idx: {source_idx})\n"));
}
}
let sort_state = dataview.get_sort_state();
match sort_state.order {
crate::data::data_view::SortOrder::None => {
debug_info.push_str("Sort State: None\n");
}
crate::data::data_view::SortOrder::Ascending => {
if let Some(col_idx) = sort_state.column {
let col_name = visible_columns
.get(col_idx)
.map_or("unknown", std::string::String::as_str);
debug_info.push_str(&format!(
"Sort State: Ascending on column '{col_name}' (idx: {col_idx})\n"
));
}
}
crate::data::data_view::SortOrder::Descending => {
if let Some(col_idx) = sort_state.column {
let col_name = visible_columns
.get(col_idx)
.map_or("unknown", std::string::String::as_str);
debug_info.push_str(&format!(
"Sort State: Descending on column '{col_name}' (idx: {col_idx})\n"
));
}
}
}
}
}
debug_info
}
fn debug_generate_datatable_schema(&self) -> String {
let mut debug_info = String::new();
if let Some(buffer) = self.get_buffer_manager().current() {
if let Some(dataview) = buffer.get_dataview() {
let datatable = dataview.source();
debug_info.push_str("\n========== DATATABLE SCHEMA ==========\n");
debug_info.push_str(&datatable.get_schema_summary());
}
}
debug_info
}
fn render_debug(&self, f: &mut ratatui::Frame, area: ratatui::layout::Rect) {
self.get_debug_widget().render(f, area, AppMode::Debug);
}
fn render_pretty_query(&self, f: &mut ratatui::Frame, area: ratatui::layout::Rect) {
self.get_debug_widget()
.render(f, area, AppMode::PrettyQuery);
}
fn debug_generate_parser_info(&self, query: &str) -> String;
fn debug_generate_navigation_state(&self) -> String;
fn debug_generate_column_search_state(&self) -> String;
fn debug_generate_trace_logs(&self) -> String;
fn debug_generate_state_logs(&self) -> String;
}