use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph, Wrap};
use ratatui::Frame;
use crate::wiz::types::{WizPhase, WizState};
pub fn draw_wiz(frame: &mut Frame, state: &WizState) {
let area = frame.area();
match state.phase {
WizPhase::Writing => {
let para = Paragraph::new("Writing configuration...")
.style(Style::default().fg(Color::Cyan));
frame.render_widget(para, area);
return;
}
WizPhase::Done => {
let (text, color) = if state.error.is_some() {
(
format!(
"Error writing config: {}",
state.error.as_deref().unwrap_or("unknown")
),
Color::Red,
)
} else {
("Configuration saved!".to_string(), Color::Green)
};
let para = Paragraph::new(text).style(Style::default().fg(color));
frame.render_widget(para, area);
return;
}
WizPhase::Aborted => {
let para = Paragraph::new("Wizard aborted.")
.style(Style::default().fg(Color::DarkGray));
frame.render_widget(para, area);
return;
}
_ => {}
}
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(2), Constraint::Length(2), Constraint::Min(5), Constraint::Min(2), ])
.split(area);
draw_header(frame, chunks[0]);
draw_tab_bar(frame, state, chunks[1]);
if state.show_review {
draw_review(frame, state, chunks[2]);
} else {
draw_question(frame, state, chunks[2]);
}
draw_footer(frame, state, chunks[3]);
}
fn draw_header(frame: &mut Frame, area: Rect) {
let para = Paragraph::new("O7 Configuration Wizard")
.style(Style::default().add_modifier(Modifier::BOLD));
frame.render_widget(para, area);
}
fn draw_tab_bar(frame: &mut Frame, state: &WizState, area: Rect) {
let mut spans: Vec<Span> = Vec::new();
for (i, q) in state.questions.iter().enumerate() {
let is_selected = !state.show_review && i == state.selected_tab;
let has_answer = state.answers.get(&q.id).map(|s| !s.is_empty()).unwrap_or(false);
let color = if is_selected {
Color::Cyan
} else if has_answer {
Color::Green
} else {
Color::DarkGray
};
let style = if is_selected {
Style::default().fg(color).add_modifier(Modifier::BOLD | Modifier::REVERSED)
} else {
Style::default().fg(color)
};
spans.push(Span::styled(format!(" {} ", i + 1), style));
spans.push(Span::raw(" "));
}
let review_style = if state.show_review {
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD | Modifier::REVERSED)
} else {
Style::default().fg(Color::DarkGray)
};
spans.push(Span::styled(" Review ", review_style));
let line = Line::from(spans);
let para = Paragraph::new(line);
frame.render_widget(para, area);
}
fn draw_question(frame: &mut Frame, state: &WizState, area: Rect) {
let q = &state.questions[state.selected_tab];
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(2), Constraint::Min(3)])
.split(area);
let prompt = Paragraph::new(q.prompt.as_str())
.style(Style::default().add_modifier(Modifier::BOLD));
frame.render_widget(prompt, chunks[0]);
let buffer = &state.text_buffer;
let cursor = state.text_cursor;
let content_line = if buffer.is_empty() {
Line::from(vec![
Span::raw("> "),
Span::styled("Type your answer...", Style::default().fg(Color::DarkGray)),
])
} else {
let chars: Vec<char> = buffer.chars().collect();
let before: String = chars[..cursor].iter().collect();
let after: String = chars[cursor..].iter().collect();
Line::from(vec![
Span::raw("> "),
Span::raw(before),
Span::styled("|", Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
Span::raw(after),
])
};
let text_widget = Paragraph::new(content_line);
frame.render_widget(text_widget, chunks[1]);
}
fn draw_review(frame: &mut Frame, state: &WizState, area: Rect) {
let block = Block::default()
.title(" Review your answers ")
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Cyan));
let items: Vec<ListItem> = state
.questions
.iter()
.map(|q| {
let answer = state.answers.get(&q.id).map(|s| s.as_str()).unwrap_or("---");
let prompt_span = Span::styled(
format!("{}: ", q.prompt),
Style::default().fg(Color::DarkGray),
);
let answer_span = Span::styled(answer, Style::default().fg(Color::Green));
ListItem::new(Line::from(vec![prompt_span, answer_span]))
})
.collect();
let list = List::new(items).block(block);
frame.render_widget(list, area);
}
fn draw_footer(frame: &mut Frame, state: &WizState, area: Rect) {
let hints = if state.show_review {
vec![
("Enter", "Save config"),
("S+Tab", "Back"),
("Esc", "Abort"),
]
} else {
vec![
("Tab", "Next"),
("S+Tab", "Prev"),
("Left/Right", "Cursor"),
("^W", "Delete word"),
("Enter", "Confirm"),
("Esc", "Abort"),
]
};
let para = Paragraph::new(render_hint_line(&hints))
.wrap(Wrap { trim: false });
frame.render_widget(para, area);
}
fn render_hint_line(hints: &[(&str, &str)]) -> Line<'static> {
let mut spans = Vec::new();
for (idx, (key, label)) in hints.iter().enumerate() {
if idx > 0 {
spans.push(Span::styled(" | ", Style::default().fg(Color::DarkGray)));
}
spans.push(Span::styled(
key.to_string(),
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD),
));
spans.push(Span::raw(": "));
spans.push(Span::styled(
label.to_string(),
Style::default().fg(Color::Gray),
));
}
Line::from(spans)
}