1use crate::buffer::{BufferAPI, SortOrder, SortState};
2use crate::hybrid_parser::HybridParser;
3use chrono::Local;
4use serde_json::Value;
5
6pub struct DebugInfo;
8
9impl DebugInfo {
10 pub fn generate_full_debug_simple(
12 buffer: &dyn BufferAPI,
13 buffer_count: usize,
14 buffer_index: usize,
15 buffer_names: Vec<String>,
16 hybrid_parser: &HybridParser,
17 sort_state: &SortState,
18 input_text: &str,
19 cursor_pos: usize,
20 visual_cursor: usize,
21 api_url: &str,
22 ) -> String {
23 let mut debug_info = String::new();
24
25 debug_info.push_str(&hybrid_parser.get_detailed_debug_info(input_text, cursor_pos));
27
28 let input_state = format!(
30 "\n========== INPUT STATE ==========\n\
31 Input Value Length: {}\n\
32 Cursor Position: {}\n\
33 Visual Cursor: {}\n\
34 Input Mode: Command\n",
35 input_text.len(),
36 cursor_pos,
37 visual_cursor
38 );
39 debug_info.push_str(&input_state);
40
41 let dataset_info = if let Some(dataview) = buffer.get_dataview() {
43 let table_name = dataview.source().name.clone();
44 let columns = dataview.column_names();
45 format!(
46 "\n========== DATASET INFO ==========\n\
47 Table Name: {}\n\
48 Visible Columns ({}): {}\n\
49 Hidden Columns: {}\n",
50 table_name,
51 columns.len(),
52 columns.join(", "),
53 dataview.get_hidden_column_names().len()
54 )
55 } else {
56 "\n========== DATASET INFO ==========\nNo DataView available\n".to_string()
57 };
58 debug_info.push_str(&dataset_info);
59
60 let data_stats = if let Some(dataview) = buffer.get_dataview() {
62 let total_rows = dataview.source().row_count();
63 let filtered_rows = dataview.row_count();
64 format!(
65 "\n========== CURRENT DATA ==========\n\
66 Total Rows Loaded: {}\n\
67 Filtered Rows: {}\n\
68 Has Filter: {}\n\
69 Current Column: {}\n\
70 Sort State: {}\n",
71 total_rows,
72 filtered_rows,
73 dataview.has_filter(),
74 buffer.get_current_column(),
75 match sort_state {
76 SortState {
77 column: Some(col),
78 order,
79 } => format!(
80 "Column {} - {}",
81 col,
82 match order {
83 SortOrder::Ascending => "Ascending",
84 SortOrder::Descending => "Descending",
85 SortOrder::None => "None",
86 }
87 ),
88 _ => "None".to_string(),
89 }
90 )
91 } else {
92 "\n========== CURRENT DATA ==========\nNo DataView available\n".to_string()
93 };
94 debug_info.push_str(&data_stats);
95
96 let status_line_info = format!(
98 "\n========== STATUS LINE INFO ==========\n\
99 Current Mode: {:?}\n\
100 Case Insensitive: {}\n\
101 Compact Mode: {}\n\
102 Viewport Lock: {}\n\
103 Data Source: {}\n",
104 buffer.get_mode(),
105 buffer.is_case_insensitive(),
106 buffer.is_compact_mode(),
107 buffer.is_viewport_lock(),
108 buffer.get_last_query_source().unwrap_or("None".to_string()),
109 );
110 debug_info.push_str(&status_line_info);
111
112 debug_info.push_str("\n========== BUFFER MANAGER STATE ==========\n");
114 debug_info.push_str(&format!("Buffer Manager: INITIALIZED\n"));
115 debug_info.push_str(&format!("Number of Buffers: {}\n", buffer_count));
116 debug_info.push_str(&format!("Current Buffer Index: {}\n", buffer_index));
117 debug_info.push_str(&format!("Has Multiple Buffers: {}\n", buffer_count > 1));
118
119 for (i, name) in buffer_names.iter().enumerate() {
121 let is_current = i == buffer_index;
122 debug_info.push_str(&format!(
123 "Buffer {}: {} {}\n",
124 i + 1,
125 name,
126 if is_current { "[CURRENT]" } else { "" }
127 ));
128 }
129
130 debug_info
131 }
132
133 pub fn generate_debug_context(buffer: &dyn BufferAPI) -> String {
135 let mut context = String::new();
136 let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S");
137
138 context.push_str(&format!("=== TUI Debug Context - {} ===\n\n", timestamp));
139
140 context.push_str("CURRENT QUERY:\n");
142 let query = buffer.get_query();
143 let last_query = buffer.get_last_query();
144 let current_query = if !query.is_empty() {
145 &query
146 } else {
147 &last_query
148 };
149 context.push_str(&format!("{}\n\n", current_query));
150
151 context.push_str("BUFFER STATE:\n");
153 context.push_str(&format!("- ID: {}\n", buffer.get_id()));
154 context.push_str(&format!(
155 "- File: {}\n",
156 buffer
157 .get_file_path()
158 .map(|p| p.to_string_lossy().to_string())
159 .unwrap_or_else(|| "memory".to_string())
160 ));
161 context.push_str(&format!("- Mode: {:?}\n", buffer.get_mode()));
162 context.push_str(&format!(
163 "- Case Insensitive: {}\n",
164 buffer.is_case_insensitive()
165 ));
166
167 if let Some(datatable) = buffer.get_datatable() {
169 context.push_str(&format!("\nRESULTS INFO:\n"));
170 context.push_str(&format!("- Total rows: {}\n", datatable.row_count()));
171 context.push_str(&format!("- Columns: {}\n", datatable.column_count()));
172 context.push_str(&format!(
173 "- Column names: {}\n",
174 datatable.column_names().join(", ")
175 ));
176
177 if buffer.is_filter_active() {
179 context.push_str(&format!("\nFILTER:\n"));
180 context.push_str(&format!("- Pattern: {}\n", buffer.get_filter_pattern()));
181 if let Some(dataview) = buffer.get_dataview() {
182 context.push_str(&format!("- Filtered rows: {}\n", dataview.row_count()));
183 }
184 }
185
186 if buffer.is_fuzzy_filter_active() {
187 context.push_str(&format!("\nFUZZY FILTER:\n"));
188 context.push_str(&format!(
189 "- Pattern: {}\n",
190 buffer.get_fuzzy_filter_pattern()
191 ));
192 let indices = buffer.get_fuzzy_filter_indices();
193 context.push_str(&format!("- Matched rows: {}\n", indices.len()));
194 }
195 }
196
197 context.push_str("\nNAVIGATION:\n");
199 context.push_str(&format!("- Current row: {:?}\n", buffer.get_selected_row()));
200 context.push_str(&format!(
201 "- Current column: {}\n",
202 buffer.get_current_column()
203 ));
204 context.push_str(&format!(
205 "- Scroll offset: ({}, {})\n",
206 buffer.get_scroll_offset().0,
207 buffer.get_scroll_offset().1
208 ));
209
210 context
211 }
212
213 pub fn generate_test_case(buffer: &dyn BufferAPI) -> String {
215 let query = buffer.get_query();
216 let last_query = buffer.get_last_query();
217 let current_query = if !query.is_empty() {
218 &query
219 } else {
220 &last_query
221 };
222
223 let mut test_case = String::new();
224 let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S");
225
226 test_case.push_str(&format!(
228 "// Test case generated from TUI session at {}\n",
229 timestamp
230 ));
231 test_case.push_str(&format!(
232 "// Buffer: {} (ID: {})\n",
233 buffer
234 .get_file_path()
235 .map(|p| p.to_string_lossy().to_string())
236 .unwrap_or_else(|| "memory".to_string()),
237 buffer.get_id()
238 ));
239
240 if let Some(datatable) = buffer.get_datatable() {
241 test_case.push_str(&format!(
242 "// Results: {} rows, {} columns\n",
243 datatable.row_count(),
244 datatable.column_count()
245 ));
246 }
247
248 test_case.push_str("\n#[test]\n");
249 test_case.push_str("fn test_yanked_from_tui_session() -> anyhow::Result<()> {\n");
250 test_case.push_str(" let mut harness = QueryReplayHarness::new();\n\n");
251
252 test_case.push_str(" harness.add_query(CapturedQuery {\n");
253 test_case.push_str(&format!(
254 " description: \"Captured from TUI session {}\".to_string(),\n",
255 timestamp
256 ));
257
258 if let Some(file_path) = buffer.get_file_path() {
260 test_case.push_str(&format!(
261 " data_file: \"{}\".to_string(),\n",
262 file_path.to_string_lossy()
263 ));
264 } else {
265 test_case.push_str(" data_file: \"data/trades.json\".to_string(),\n");
266 }
267
268 test_case.push_str(&format!(
270 " query: \"{}\".to_string(),\n",
271 current_query.replace('"', "\\\"")
272 ));
273
274 if let Some(datatable) = buffer.get_datatable() {
276 test_case.push_str(&format!(
277 " expected_row_count: {},\n",
278 datatable.row_count()
279 ));
280
281 test_case.push_str(" expected_columns: vec![\n");
283 for column_name in datatable.column_names() {
284 test_case.push_str(&format!(" \"{}\".to_string(), \n", column_name));
285 }
286 test_case.push_str(" ],\n");
287 } else {
288 test_case.push_str(" expected_row_count: 0,\n");
289 test_case.push_str(" expected_columns: vec![],\n");
290 test_case.push_str(" expected_first_row: None,\n");
291 }
292
293 test_case.push_str(&format!(
294 " case_insensitive: {},\n",
295 buffer.is_case_insensitive()
296 ));
297 test_case.push_str(" });\n\n");
298
299 test_case.push_str(" // Run the test\n");
300 test_case.push_str(" harness.run_all_tests()?;\n\n");
301 test_case.push_str(" println!(\"✅ Yanked query test passed!\");\n");
302 test_case.push_str(" Ok(())\n");
303 test_case.push_str("}\n");
304
305 test_case
306 }
307
308 fn value_to_rust_code(value: &Value) -> String {
310 match value {
311 Value::String(s) => format!(
312 "serde_json::Value::String(\"{}\".to_string())",
313 s.replace('"', "\\\"")
314 ),
315 Value::Number(n) => {
316 if let Some(i) = n.as_i64() {
317 format!("serde_json::Value::Number(serde_json::Number::from({}))", i)
318 } else if let Some(f) = n.as_f64() {
319 format!(
320 "serde_json::Value::Number(serde_json::Number::from_f64({}).unwrap())",
321 f
322 )
323 } else {
324 format!(
325 "serde_json::Value::Number(serde_json::Number::from_str(\"{}\").unwrap())",
326 n
327 )
328 }
329 }
330 Value::Bool(b) => format!("serde_json::Value::Bool({})", b),
331 Value::Null => "serde_json::Value::Null".to_string(),
332 _ => format!("serde_json::json!({})", value),
333 }
334 }
335
336 pub fn generate_buffer_summary(buffer: &dyn BufferAPI) -> String {
338 let mut summary = Vec::new();
339
340 summary.push(format!("Buffer #{}", buffer.get_id()));
341
342 if let Some(path) = buffer.get_file_path() {
343 summary.push(format!(
344 "File: {}",
345 path.file_name().unwrap_or_default().to_string_lossy()
346 ));
347 }
348
349 if let Some(datatable) = buffer.get_datatable() {
350 summary.push(format!("{} rows", datatable.row_count()));
351
352 if buffer.is_filter_active() {
353 if let Some(dataview) = buffer.get_dataview() {
354 summary.push(format!("{} filtered", dataview.row_count()));
355 }
356 }
357
358 if buffer.is_fuzzy_filter_active() {
359 let indices = buffer.get_fuzzy_filter_indices();
360 summary.push(format!("{} fuzzy matches", indices.len()));
361 }
362 }
363
364 summary.join(" | ")
365 }
366
367 pub fn generate_query_debug(query: &str, error: Option<&str>) -> String {
369 let mut debug = String::new();
370 let timestamp = Local::now().format("%H:%M:%S%.3f");
371
372 debug.push_str(&format!("[{}] Query execution:\n", timestamp));
373 debug.push_str(&format!("Query: {}\n", query));
374
375 if let Some(err) = error {
376 debug.push_str(&format!("Error: {}\n", err));
377 } else {
378 debug.push_str("Status: Success\n");
379 }
380
381 debug
382 }
383}
384
385pub struct DebugView {
387 pub content: String,
388 pub scroll_offset: u16,
389}
390
391impl DebugView {
392 pub fn new() -> Self {
393 Self {
394 content: String::new(),
395 scroll_offset: 0,
396 }
397 }
398
399 pub fn set_content(&mut self, content: String) {
400 self.content = content;
401 self.scroll_offset = 0; }
403
404 pub fn scroll_up(&mut self) {
405 self.scroll_offset = self.scroll_offset.saturating_sub(1);
406 }
407
408 pub fn scroll_down(&mut self) {
409 let max_scroll = self.get_max_scroll();
410 if (self.scroll_offset as usize) < max_scroll {
411 self.scroll_offset = self.scroll_offset.saturating_add(1);
412 }
413 }
414
415 pub fn page_up(&mut self) {
416 self.scroll_offset = self.scroll_offset.saturating_sub(10);
417 }
418
419 pub fn page_down(&mut self) {
420 let max_scroll = self.get_max_scroll();
421 self.scroll_offset = (self.scroll_offset + 10).min(max_scroll as u16);
422 }
423
424 pub fn go_to_top(&mut self) {
425 self.scroll_offset = 0;
426 }
427
428 pub fn go_to_bottom(&mut self) {
429 self.scroll_offset = self.get_max_scroll() as u16;
430 }
431
432 pub fn get_max_scroll(&self) -> usize {
433 let line_count = self.content.lines().count();
434 line_count.saturating_sub(10) }
436
437 pub fn get_visible_lines(&self, height: usize) -> Vec<String> {
438 self.content
439 .lines()
440 .skip(self.scroll_offset as usize)
441 .take(height)
442 .map(|s| s.to_string())
443 .collect()
444 }
445}
446
447impl Default for DebugView {
448 fn default() -> Self {
449 Self::new()
450 }
451}