use crate::services::build_platforms::BUILD_PLATFORMS;
use crate::services::catalog_client::{self, template_node_defaults};
use crate::services::ComponentCatalogService;
use crate::ui::{InitWizardState, InitWizardTui, NodeOption, TemplateOption};
use anyhow::{Context, 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>, String, Vec<String>);
pub async fn run_wizard_tui(default_name: Option<&str>, catalog: &ComponentCatalogService) -> Result<WizardResult> {
let templates = catalog_client::fetch_template_catalog()
.await
.context("Failed to fetch template catalog - mecha10 init cannot continue without it")?;
let template_options: Vec<TemplateOption> = templates
.iter()
.map(|t| TemplateOption {
id: t.name.clone(),
name: t.name.clone(),
description: t.description.clone(),
nodes: t.nodes.clone(),
})
.collect();
let mut node_entries = catalog_client::fetch_node_catalog()
.await
.context("Failed to fetch node catalog - mecha10 init cannot continue without it")?;
node_entries.sort_by(|a, b| a.name.cmp(&b.name));
let node_ids: Vec<String> = node_entries.iter().map(|e| e.name.clone()).collect();
let node_options: Vec<NodeOption> = node_entries
.iter()
.map(|e| {
let description = catalog
.get(&e.name)
.map(|c| c.description.clone())
.unwrap_or_else(|| format!("@mecha10/{} framework node", e.name));
NodeOption {
id: e.name.clone(),
name: e.name.clone(),
description,
}
})
.collect();
let defaults = template_node_defaults(&templates, &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();
let robot_platform = BUILD_PLATFORMS[result.selected_platform].triple.to_string();
let remote_platforms: Vec<String> = result
.selected_remote_platforms
.iter()
.enumerate()
.filter_map(|(idx, &selected)| {
if selected {
Some(BUILD_PLATFORMS[idx].triple.to_string())
} else {
None
}
})
.collect();
Ok((result.project_name, template, nodes, robot_platform, remote_platforms))
}