use crate::TuiResult;
use crate::tui::icons;
use std::collections::HashMap;
use std::path::PathBuf;
use wasmind::wasmind_actor_loader::dependency_resolver::{DependencyResolver, ResolvedActor};
use wasmind::wasmind_config::{ActorSource, Config};
#[derive(Debug)]
struct StartupAnalysis {
starting_actors: Vec<String>,
auto_spawn_actors: Vec<String>,
effective_startup_actors: Vec<String>,
has_valid_startup: bool,
}
pub async fn show_status(config_path: Option<PathBuf>) -> TuiResult<()> {
println!("Wasmind Configuration Status");
println!("========================");
let (config, config_file) = if let Some(path) = config_path {
if !path.exists() {
println!("Config: {} (not found)", path.display());
println!("{} Configuration file not found", icons::FAILED_ICON);
return Ok(());
}
let config = wasmind::wasmind_config::load_from_path(path.clone())?;
(config, path)
} else {
let config_file = wasmind::wasmind_config::get_config_file_path()?;
if !config_file.exists() {
println!("Config: {} (not found)", config_file.display());
println!("{} No configuration file found", icons::FAILED_ICON);
return Ok(());
}
let config = wasmind::wasmind_config::load_default_config()?;
(config, config_file)
};
println!(
"Config: {} {} Valid",
config_file.display(),
icons::SUCCESS_ICON
);
println!();
let cache_dir = wasmind::wasmind_config::get_actors_cache_dir()?;
let resolver = DependencyResolver::with_persistent_cache(cache_dir)?;
let resolved_actors = match resolver
.resolve_all(config.actors.clone(), config.actor_overrides.clone())
.await
{
Ok(actors) => actors,
Err(e) => {
println!("{} Dependency resolution failed:", icons::FAILED_ICON);
println!(" {e}");
return Ok(());
}
};
let startup_analysis = analyze_startup_behavior(&config, &resolved_actors);
display_status(&config, &resolved_actors, &startup_analysis);
Ok(())
}
fn analyze_startup_behavior(
config: &Config,
resolved_actors: &HashMap<String, ResolvedActor>,
) -> StartupAnalysis {
let starting_actors = config.starting_actors.clone();
let auto_spawn_actors: Vec<String> = resolved_actors
.values()
.filter(|actor| actor.auto_spawn)
.map(|actor| actor.logical_name.clone())
.collect();
let mut effective_startup_actors = Vec::new();
let mut visited = std::collections::HashSet::new();
for actor_name in &starting_actors {
collect_actor_and_dependencies(
actor_name,
resolved_actors,
&mut effective_startup_actors,
&mut visited,
);
}
for actor_name in &auto_spawn_actors {
collect_actor_and_dependencies(
actor_name,
resolved_actors,
&mut effective_startup_actors,
&mut visited,
);
}
let has_valid_startup = !effective_startup_actors.is_empty();
StartupAnalysis {
starting_actors,
auto_spawn_actors,
effective_startup_actors,
has_valid_startup,
}
}
fn collect_actor_and_dependencies(
actor_name: &str,
resolved_actors: &HashMap<String, ResolvedActor>,
effective_actors: &mut Vec<String>,
visited: &mut std::collections::HashSet<String>,
) {
if visited.contains(actor_name) {
return;
}
visited.insert(actor_name.to_string());
if let Some(actor) = resolved_actors.get(actor_name) {
if !effective_actors.contains(&actor.logical_name) {
effective_actors.push(actor.logical_name.clone());
}
for dep_name in &actor.required_spawn_with {
collect_actor_and_dependencies(dep_name, resolved_actors, effective_actors, visited);
}
}
}
fn truncate_string(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
format!("{}...", &s[..max_len.saturating_sub(3)])
}
}
fn format_config_compact(config: &toml::Table) -> String {
if config.is_empty() {
return "(empty)".to_string();
}
format_toml_table_proper(config, String::new(), 0)
}
fn format_toml_table_proper(table: &toml::Table, section_prefix: String, _depth: usize) -> String {
let mut result = Vec::new();
for (key, value) in table.iter() {
if !matches!(value, toml::Value::Table(_)) {
let formatted_value = format_toml_value_for_display(value);
result.push(format!("{key} = {formatted_value}"));
}
}
for (key, value) in table.iter() {
if let toml::Value::Table(nested_table) = value {
if nested_table.is_empty() {
continue;
}
let section_name = if section_prefix.is_empty() {
key.clone()
} else {
format!("{section_prefix}.{key}")
};
result.push(format!("[{section_name}]"));
let nested_content = format_toml_table_proper(nested_table, section_name, 0);
if !nested_content.is_empty() {
result.push(nested_content);
}
}
}
result.join("\n")
}
fn format_toml_value_for_display(value: &toml::Value) -> String {
match value {
toml::Value::String(s) => {
let truncated = truncate_string(s, 100); format!("\"{truncated}\"")
}
toml::Value::Integer(i) => i.to_string(),
toml::Value::Float(f) => f.to_string(),
toml::Value::Boolean(b) => b.to_string(),
toml::Value::Array(arr) => {
if arr.len() <= 5 {
let items: Vec<String> = arr.iter().map(format_toml_value_for_display).collect();
format!("[{}]", items.join(", "))
} else {
format!("[{} items...]", arr.len())
}
}
toml::Value::Table(_) => "(table)".to_string(), _ => "...".to_string(),
}
}
fn display_status(
_config: &Config,
resolved_actors: &HashMap<String, ResolvedActor>,
analysis: &StartupAnalysis,
) {
println!("Startup Behavior:");
if !analysis.starting_actors.is_empty() {
println!(
"├─ User-configured starting actors: {}",
analysis.starting_actors.join(", ")
);
} else {
println!("├─ User-configured starting actors: (none)");
}
if !analysis.auto_spawn_actors.is_empty() {
println!(
"├─ Auto-spawn actors: {}",
analysis.auto_spawn_actors.join(", ")
);
} else {
println!("├─ Auto-spawn actors: (none)");
}
println!(
"├─ Calculated effective startup actors ({} total): {}",
analysis.effective_startup_actors.len(),
analysis.effective_startup_actors.join(", ")
);
if analysis.has_valid_startup {
println!(
"└─ Validation: {} At least one starting actor configured",
icons::SUCCESS_ICON
);
} else {
println!(
"└─ Validation: {} No starting actors configured",
icons::FAILED_ICON
);
}
println!();
println!("All Actors ({} total):", resolved_actors.len());
println!();
let mut user_actors: Vec<_> = resolved_actors
.values()
.filter(|actor| !actor.is_dependency)
.collect();
let mut dependency_actors: Vec<_> = resolved_actors
.values()
.filter(|actor| actor.is_dependency)
.collect();
user_actors.sort_by(|a, b| a.logical_name.cmp(&b.logical_name));
dependency_actors.sort_by(|a, b| a.logical_name.cmp(&b.logical_name));
for actor in user_actors {
display_actor_info(actor, analysis);
}
for actor in dependency_actors {
display_actor_info(actor, analysis);
}
println!();
println!(
"System Validation: {} All checks passed",
icons::SUCCESS_ICON
);
}
fn display_actor_info(actor: &ResolvedActor, analysis: &StartupAnalysis) {
let mut tags = Vec::new();
if !actor.is_dependency {
tags.push("user-defined");
} else {
tags.push("dependency");
}
if analysis.starting_actors.contains(&actor.logical_name) {
tags.push("starting");
}
if actor.auto_spawn {
tags.push("auto-spawn");
}
let has_config = actor.config.is_some() && !actor.config.as_ref().unwrap().is_empty();
if has_config {
tags.push("overridden");
}
let tags_str = if tags.len() > 1 {
format!(" ({})", tags.join(", "))
} else if !tags.is_empty() {
format!(" ({})", tags[0])
} else {
String::new()
};
println!("{}{}", actor.logical_name, tags_str);
println!(" ID: {}", actor.actor_id);
let source_str = match &actor.source {
ActorSource::Path(path_source) => {
format!("path({})", path_source.path)
}
ActorSource::Git(git_source) => {
if let Some(sub_dir) = &git_source.sub_dir {
format!("git({}, sub_dir={})", git_source.git, sub_dir)
} else {
format!("git({})", git_source.git)
}
}
};
println!(" Source: {source_str}");
if let Some(config) = &actor.config {
let config_display = format_config_compact(config);
println!(" Effective config:");
println!(" ┌─────────────────────────────");
for line in config_display.lines() {
println!(" │ {line}");
}
println!(" └─────────────────────────────");
} else {
println!(" Effective config: (empty)");
}
println!(" ───────────────────────────────────────────────────────────────────────────");
println!();
}