use crate::init::{get_all_templates, get_template_defaults};
use crate::services::ComponentCatalogService;
use crate::ui::{InitWizardState, InitWizardTui, NodeOption, TemplateOption};
use anyhow::Result;
use crossterm::{
event::{self, Event},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::CrosstermBackend, Terminal};
use std::io::stdout;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
pub type WizardResult = (String, Option<String>, Vec<String>);
pub fn run_wizard_tui(default_name: Option<&str>, catalog: &ComponentCatalogService) -> Result<WizardResult> {
let templates = get_all_templates();
let template_options: Vec<TemplateOption> = templates
.iter()
.map(|t| TemplateOption {
id: t.id().to_string(),
name: t.name().to_string(),
description: t.description().to_string(),
})
.collect();
let mut node_components = catalog.get_by_category("nodes");
node_components.sort_by(|a, b| a.name.cmp(&b.name));
let node_ids: Vec<String> = node_components.iter().map(|c| c.id.clone()).collect();
let node_options: Vec<NodeOption> = node_components
.iter()
.map(|c| NodeOption {
id: c.id.clone(),
name: c.name.clone(),
description: c.description.clone(),
})
.collect();
let defaults = get_template_defaults(&None, &node_ids);
let mut state = InitWizardState::new(template_options, node_options, defaults);
if let Some(name) = default_name {
state.project_name = name.to_string();
}
enable_raw_mode()?;
let mut stdout_handle = stdout();
execute!(stdout_handle, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout_handle);
let mut terminal = Terminal::new(backend)?;
let mut tui = InitWizardTui::new(state);
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
})?;
let result = loop {
if !running.load(Ordering::SeqCst) || tui.should_quit() {
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
terminal.show_cursor()?;
return Err(anyhow::anyhow!("Wizard cancelled"));
}
if tui.is_confirmed() {
break tui.into_state();
}
if let Err(e) = terminal.draw(|f| tui.draw(f)) {
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
terminal.show_cursor()?;
return Err(e.into());
}
if event::poll(Duration::from_millis(100))? {
if let Event::Key(key_event) = event::read()? {
tui.handle_key(key_event);
}
}
};
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
terminal.show_cursor()?;
let template = result.selected_template.map(|idx| result.templates[idx].id.clone());
let nodes: Vec<String> = result
.selected_nodes
.iter()
.enumerate()
.filter_map(|(idx, &selected)| {
if selected {
result.nodes.get(idx).map(|n| n.id.clone())
} else {
None
}
})
.collect();
Ok((result.project_name, template, nodes))
}