use crate::buffer::{AppMode, BufferAPI, SortState};
use crate::debug_info::DebugInfo;
use crate::hybrid_parser::HybridParser;
use crate::where_parser;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::{
layout::Rect,
style::{Color, Style},
text::{Line, Text},
widgets::{Block, Borders, Paragraph, Wrap},
Frame,
};
pub struct DebugWidget {
content: String,
scroll_offset: u16,
max_scroll: u16,
}
impl DebugWidget {
#[must_use]
pub fn new() -> Self {
Self {
content: String::new(),
scroll_offset: 0,
max_scroll: 0,
}
}
pub fn generate_debug(
&mut self,
buffer: &dyn BufferAPI,
buffer_count: usize,
buffer_index: usize,
buffer_names: Vec<String>,
hybrid_parser: &HybridParser,
sort_state: &SortState,
input_text: &str,
cursor_pos: usize,
visual_cursor: usize,
api_url: &str,
) {
let mut debug_info = DebugInfo::generate_full_debug_simple(
buffer,
buffer_count,
buffer_index,
buffer_names,
hybrid_parser,
sort_state,
input_text,
cursor_pos,
visual_cursor,
api_url,
);
if input_text.to_lowercase().contains(" where ") {
let where_ast_info = match Self::parse_where_clause_ast(input_text) {
Ok(ast_str) => ast_str,
Err(e) => format!(
"\n========== WHERE CLAUSE AST ==========\nError parsing WHERE clause: {e}\n"
),
};
debug_info.push_str(&where_ast_info);
}
self.content = debug_info;
self.scroll_offset = 0;
self.update_max_scroll();
}
pub fn generate_pretty_sql(&mut self, query: &str) {
if !query.trim().is_empty() {
let debug_text = format!(
"Pretty SQL Query\n{}\n\n{}",
"=".repeat(50),
crate::recursive_parser::format_sql_pretty_compact(query, 5).join("\n")
);
self.content = debug_text;
self.scroll_offset = 0;
self.update_max_scroll();
}
}
pub fn generate_test_case(&mut self, buffer: &dyn BufferAPI) {
self.content = DebugInfo::generate_test_case(buffer);
self.scroll_offset = 0;
self.update_max_scroll();
}
pub fn handle_key(&mut self, key: KeyEvent) -> bool {
match key.code {
KeyCode::Up | KeyCode::Char('k') => {
self.scroll_up(1);
false
}
KeyCode::Down | KeyCode::Char('j') => {
self.scroll_down(1);
false
}
KeyCode::PageUp => {
self.scroll_up(10);
false
}
KeyCode::PageDown => {
self.scroll_down(10);
false
}
KeyCode::Home | KeyCode::Char('g') => {
self.scroll_to_top();
false
}
KeyCode::End | KeyCode::Char('G') => {
self.scroll_to_bottom();
false
}
KeyCode::Esc | KeyCode::Char('q') => true,
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
true
}
_ => false,
}
}
pub fn render(&self, f: &mut Frame, area: Rect, mode: AppMode) {
let visible_height = area.height.saturating_sub(2) as usize;
let visible_lines = self.get_visible_lines(visible_height);
let debug_text = Text::from(visible_lines);
let total_lines = self.content.lines().count();
let start = self.scroll_offset as usize;
let end = (start + visible_height).min(total_lines);
let has_parse_error = self.content.contains("❌ PARSE ERROR ❌");
let (border_color, title_prefix) = if has_parse_error {
(Color::Red, "⚠️ Parser Debug Info [PARSE ERROR] ")
} else {
(Color::Yellow, "Parser Debug Info ")
};
let title = match mode {
AppMode::Debug => format!(
"{}- Lines {}-{} of {} (↑↓/jk: scroll, PgUp/PgDn: page, Home/g: top, End/G: bottom, q/Esc: exit)",
title_prefix,
start + 1,
end,
total_lines
),
AppMode::PrettyQuery => {
"Pretty SQL Query (F6) - ↑↓ to scroll, Esc/q to close".to_string()
}
_ => "Debug Info".to_string(),
};
let debug_paragraph = Paragraph::new(debug_text)
.block(
Block::default()
.borders(Borders::ALL)
.title(title)
.border_style(Style::default().fg(border_color)),
)
.style(Style::default().fg(Color::White))
.wrap(Wrap { trim: false });
f.render_widget(debug_paragraph, area);
}
#[must_use]
pub fn get_visible_lines(&self, height: usize) -> Vec<Line<'static>> {
let lines: Vec<&str> = self.content.lines().collect();
let start = self.scroll_offset as usize;
let end = (start + height).min(lines.len());
lines[start..end]
.iter()
.map(|line| Line::from((*line).to_string()))
.collect()
}
pub fn scroll_up(&mut self, amount: u16) {
self.scroll_offset = self.scroll_offset.saturating_sub(amount);
}
pub fn scroll_down(&mut self, amount: u16) {
self.scroll_offset = (self.scroll_offset + amount).min(self.max_scroll);
}
pub fn scroll_to_top(&mut self) {
self.scroll_offset = 0;
}
pub fn scroll_to_bottom(&mut self) {
self.scroll_offset = self.max_scroll;
}
fn update_max_scroll(&mut self) {
let line_count = self.content.lines().count() as u16;
self.max_scroll = line_count.saturating_sub(10); }
#[must_use]
pub fn get_content(&self) -> &str {
&self.content
}
pub fn set_content(&mut self, content: String) {
self.content = content;
self.scroll_offset = 0;
self.update_max_scroll();
}
fn parse_where_clause_ast(query: &str) -> Result<String, String> {
let lower_query = query.to_lowercase();
let where_pos = lower_query.find(" where ");
if let Some(pos) = where_pos {
let where_start = pos + 7; let where_clause = &query[where_start..];
let end_keywords = ["order by", "group by", "limit", "offset", ";"];
let mut where_end = where_clause.len();
for keyword in &end_keywords {
if let Some(keyword_pos) = where_clause.to_lowercase().find(keyword) {
where_end = where_end.min(keyword_pos);
}
}
let where_only = where_clause[..where_end].trim();
match where_parser::WhereParser::parse(where_only) {
Ok(ast) => {
let mut result = String::from("\n========== WHERE CLAUSE AST ==========\n");
result.push_str(&format!("Input: {where_only}\n"));
result.push_str(&format!("Parsed AST:\n{ast:#?}\n"));
Ok(result)
}
Err(e) => Err(format!("Failed to parse WHERE clause: {e}")),
}
} else {
Err("No WHERE clause found in query".to_string())
}
}
}
impl Default for DebugWidget {
fn default() -> Self {
Self::new()
}
}