use crate::components::manager::ComponentManager;
use crate::app::AppState;
use crate::ui::style::{self, Emphasis};
use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::widgets::{Paragraph, List, ListItem, Clear, ListState};
use ratatui::text::{Line, Span};
use crate::components::manager::utils;
pub fn render_feedback(_manager: &ComponentManager, frame: &mut Frame, area: Rect, state: &AppState) {
if let Some(ref feedback) = state.feedback_message {
let footer_style = style::text(&state.theme, Emphasis::Success);
let block = style::pane_block(&state.theme, "Status", false);
let paragraph = Paragraph::new(feedback.clone())
.style(footer_style)
.block(block);
let footer = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(0), Constraint::Length(3)])
.split(area);
frame.render_widget(paragraph, footer[1]);
return;
}
if let Some(ref error_guidance) = state.error_guidance {
let formatted = error_guidance.format_display();
let lines: Vec<Line> = formatted
.lines()
.map(|line| {
if line.starts_with(" [") && line.contains("]") {
let parts: Vec<&str> = line.splitn(2, "] ").collect();
if parts.len() == 2 {
let key_part = parts[0].trim_start_matches(" [");
let label = parts[1];
Line::from(vec![
Span::styled(" [", style::text(&state.theme, Emphasis::Normal)),
Span::styled(key_part, style::text(&state.theme, Emphasis::Warning)),
Span::styled("] ", style::text(&state.theme, Emphasis::Normal)),
Span::styled(label, style::text(&state.theme, Emphasis::Normal)),
])
} else {
Line::from(line)
}
} else if line.starts_with(" •") {
let prefix = " • ";
let char_count = prefix.chars().count();
let rest: String = line.chars().skip(char_count).collect();
Line::from(vec![
Span::styled(prefix, style::text(&state.theme, Emphasis::Muted)),
Span::styled(rest, style::text(&state.theme, Emphasis::Normal)),
])
} else if line == "Suggestions:" || line == "Quick actions:" {
Line::from(vec![
Span::styled(line, style::text(&state.theme, Emphasis::Header)),
])
} else {
Line::from(line)
}
})
.collect();
let error_style = style::text(&state.theme, Emphasis::Error);
let block = style::pane_block(&state.theme, "Error", false);
let paragraph = Paragraph::new(lines)
.style(error_style)
.block(block);
let content_height = formatted.lines().count().max(3);
let height = (content_height + 2).min(area.height as usize) as u16;
let footer = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(0), Constraint::Length(height)])
.split(area);
frame.render_widget(paragraph, footer[1]);
return;
}
if let Some(ref error) = state.last_status_error {
let footer_style = style::text(&state.theme, Emphasis::Error);
let block = style::pane_block(&state.theme, "Status", false);
let paragraph = Paragraph::new(error.clone())
.style(footer_style)
.block(block);
let footer = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(0), Constraint::Length(3)])
.split(area);
frame.render_widget(paragraph, footer[1]);
}
}
pub fn render_palette(_manager: &ComponentManager, frame: &mut Frame, area: Rect, state: &AppState) {
let (items, selected, title) = if state.theme_picker {
let max_idx = state.available_themes.len().saturating_sub(1);
let selected = state.theme_picker_selected.min(max_idx);
let items: Vec<ListItem> = state
.available_themes
.iter()
.enumerate()
.map(|(i, name)| {
let style = if i == selected {
style::selection(&state.theme)
} else {
style::text(&state.theme, Emphasis::Header)
};
ListItem::new(name.clone()).style(style)
})
.collect();
let title = format!(
"Themes [{} / {}]",
selected.saturating_add(1),
state.available_themes.len().max(1)
);
(items, selected, title)
} else {
(Vec::new(), 0, "Palette".to_string())
};
let popup = utils::center_rect(60, 40, area);
frame.render_widget(Clear, popup);
let mut list_state = ListState::default();
list_state.select(Some(selected));
let list = List::new(items)
.style(style::body_style(&state.theme))
.block(style::pane_block(&state.theme, title, true));
frame.render_stateful_widget(list, popup, &mut list_state);
}
pub fn render_theme_picker(_manager: &ComponentManager, frame: &mut Frame, area: Rect, state: &AppState) {
let max_idx = state.available_themes.len().saturating_sub(1);
let selected = state.theme_picker_selected.min(max_idx);
let items: Vec<ListItem> = state
.available_themes
.iter()
.enumerate()
.map(|(i, name)| {
let style = if i == selected {
style::selection(&state.theme)
} else {
style::body_style(&state.theme)
};
ListItem::new(name.clone()).style(style)
})
.collect();
let title = format!(
"Themes [{} / {}]",
selected.saturating_add(1),
state.available_themes.len().max(1)
);
let popup = utils::center_rect(40, 60, area);
frame.render_widget(Clear, popup);
let list = List::new(items)
.style(style::body_style(&state.theme))
.block(style::pane_block(&state.theme, title, true));
frame.render_widget(list, popup);
}
pub fn render_op_log(_manager: &ComponentManager, frame: &mut Frame, area: Rect, state: &AppState) {
let popup = utils::center_rect(60, 30, area);
frame.render_widget(Clear, popup);
let lines: Vec<ratatui::widgets::ListItem> = state
.op_log
.iter()
.rev()
.take(10)
.rev()
.map(|l| ratatui::widgets::ListItem::new(l.clone()).style(style::text(&state.theme, style::Emphasis::Muted)))
.collect();
let title = "Op log (last 10) (Esc/q/Enter=close)";
let list = ratatui::widgets::List::new(lines)
.style(style::body_style(&state.theme))
.block(style::pane_block(&state.theme, title.to_string(), true));
frame.render_widget(list, popup);
}