bzzz-cli 0.1.0

Bzzz CLI - Command line interface for Agent orchestration
//! List command - Display runs, agents, patterns, or templates

use anyhow::Result;
use std::path::PathBuf;

use crate::commands::output::{
    AgentCardOutput, ListPatternsOutput, ListRunEntry, ListRunsOutput, ListTemplatesOutput,
    ListWorkersOutput, OutputFormat, PatternEntry, WorkerEntry,
};
use crate::run_registry::RunRegistry;
use bzzz_core::protocol::SwarmFile;
use bzzz_core::runtime::A2ARuntime;

/// List all runs or only running runs
pub async fn execute(running_only: bool, output_format: OutputFormat) -> Result<()> {
    let registry = RunRegistry::default();

    let filter = if running_only {
        Some(bzzz_core::RunStatus::Running)
    } else {
        None
    };

    let runs = registry.list(filter)?;

    if output_format == OutputFormat::Json {
        let entries: Vec<ListRunEntry> = runs
            .iter()
            .map(|run| ListRunEntry {
                run_id: run.run_id.clone(),
                status: format!("{:?}", run.status),
                spec_path: run.spec_path.display().to_string(),
                pid: run.pid,
            })
            .collect();
        let out = ListRunsOutput { runs: entries };
        println!("{}", serde_json::to_string(&out)?);
        return Ok(());
    }

    println!("Runs:");

    if runs.is_empty() {
        println!("  No runs found");
        if running_only {
            println!("  Tip: Remove --running to see all runs");
        }
    } else {
        for run in runs {
            let status_icon = match run.status {
                bzzz_core::RunStatus::Running => "[Running]",
                bzzz_core::RunStatus::Completed => "[Completed]",
                bzzz_core::RunStatus::Failed => "[Failed]",
                bzzz_core::RunStatus::Cancelled => "[Cancelled]",
                bzzz_core::RunStatus::Timeout => "[Timeout]",
                bzzz_core::RunStatus::Pending => "[Pending]",
            };

            let pid_str = run.pid.map(|p| format!("(pid:{})", p)).unwrap_or_default();

            println!(
                "  {} {} - {:?} {} {}",
                status_icon,
                run.run_id,
                run.status,
                run.spec_path.display(),
                pid_str
            );
        }

        println!();
        println!("  Use `bzzz status --id <run_id>` for details");
    }

    Ok(())
}

/// List workers (agents) from a SwarmFile
pub async fn execute_agents(file: PathBuf, output_format: OutputFormat) -> Result<()> {
    let swarm = SwarmFile::from_yaml_file(&file)?;

    if output_format == OutputFormat::Json {
        let workers: Vec<WorkerEntry> = swarm
            .workers
            .iter()
            .map(|worker| {
                let worker_type = if worker.a2a.is_some() {
                    "a2a"
                } else if worker.spec.is_some() {
                    "spec"
                } else {
                    "unknown"
                };
                let runtime = worker
                    .runtime
                    .map(|r| format!("{:?}", r))
                    .or_else(|| swarm.runtime.map(|r| format!("{:?}", r)))
                    .unwrap_or_else(|| "default".to_string());
                WorkerEntry {
                    name: worker.name.clone(),
                    worker_type: worker_type.to_string(),
                    runtime,
                }
            })
            .collect();
        let out = ListWorkersOutput { workers };
        println!("{}", serde_json::to_string(&out)?);
        return Ok(());
    }

    println!("Workers in {}:", file.display());
    println!();

    if swarm.workers.is_empty() {
        println!("  No workers defined");
        return Ok(());
    }

    println!("  {:<20} {:<10} {:<20}", "Name", "Type", "Runtime");
    println!("  {:<20} {:<10} {:<20}", "----", "----", "------");

    for worker in &swarm.workers {
        let worker_type = if worker.a2a.is_some() {
            "a2a"
        } else if worker.spec.is_some() {
            "spec"
        } else {
            "unknown"
        };

        let runtime = worker
            .runtime
            .map(|r| format!("{:?}", r))
            .or_else(|| swarm.runtime.map(|r| format!("{:?}", r)))
            .unwrap_or_else(|| "default".to_string());

        println!("  {:<20} {:<10} {:<20}", worker.name, worker_type, runtime);
    }

    println!();
    println!("  Total: {} workers", swarm.workers.len());

    Ok(())
}

/// List all supported orchestration patterns
pub fn execute_patterns(output_format: OutputFormat) -> Result<()> {
    let patterns = [
        ("sequence", "Execute workers in sequential order"),
        ("parallel", "Execute workers concurrently"),
        ("conditional", "Execute based on condition expression"),
        ("loop", "Iterate over items with a worker"),
        ("delegate", "Delegate to another Swarm or dynamic worker"),
        ("supervisor", "Monitor and restart workers on failure"),
        ("compete", "First worker to complete wins"),
        ("escalation", "Escalate to backup on primary failure"),
        ("alongside", "Run side workers alongside main flow"),
    ];

    if output_format == OutputFormat::Json {
        let entries: Vec<PatternEntry> = patterns
            .iter()
            .map(|(name, desc)| PatternEntry {
                name: name.to_string(),
                description: desc.to_string(),
            })
            .collect();
        let out = ListPatternsOutput { patterns: entries };
        println!("{}", serde_json::to_string(&out)?);
        return Ok(());
    }

    println!("Supported Orchestration Patterns:");
    println!();

    for (name, desc) in patterns {
        println!("  {:<15} - {}", name, desc);
    }

    println!();
    println!("  Total: 9 patterns");
    println!();
    println!("  Flow patterns: sequence, parallel, conditional, loop");
    println!("  Composition patterns: delegate, supervisor, compete, escalation, alongside");

    Ok(())
}

/// List available template files
pub fn execute_templates(output_format: OutputFormat) -> Result<()> {
    let templates_dir = std::path::Path::new("templates");

    if output_format == OutputFormat::Json {
        let names: Vec<String> = if templates_dir.exists() {
            std::fs::read_dir(templates_dir)?
                .filter_map(|e| e.ok())
                .filter(|e| {
                    let path = e.path();
                    let ext = path.extension().and_then(|s| s.to_str());
                    ext == Some("yaml") || ext == Some("yml")
                })
                .map(|e| e.file_name().to_string_lossy().to_string())
                .collect()
        } else {
            vec![]
        };
        let out = ListTemplatesOutput { templates: names };
        println!("{}", serde_json::to_string(&out)?);
        return Ok(());
    }

    println!("Available Templates:");
    println!();

    if !templates_dir.exists() {
        println!("  No templates directory found");
        println!();
        println!("  Tip: Create a 'templates/' directory in your project root");
        println!("       Add SwarmFile fragments (.swarm.yaml files) for reuse");
        return Ok(());
    }

    let entries: Vec<_> = std::fs::read_dir(templates_dir)?
        .filter_map(|e| e.ok())
        .filter(|e| {
            let path = e.path();
            let ext = path.extension().and_then(|s| s.to_str());
            ext == Some("yaml") || ext == Some("yml")
        })
        .collect();

    if entries.is_empty() {
        println!("  No template files found in templates/");
        println!();
        println!("  Tip: Add .swarm.yaml files to templates/ directory");
        return Ok(());
    }

    for entry in &entries {
        let name = entry.file_name();
        let name_str = name.to_string_lossy();
        println!("  {}", name_str);
    }

    println!();
    println!("  Total: {} templates", entries.len());
    println!();
    println!("  Location: templates/");

    Ok(())
}

/// Discover A2A Agent Card from a URL
pub async fn execute_agents_discovery(url: String, output_format: OutputFormat) -> Result<()> {
    let card = A2ARuntime::discover_agent_card(&url).await?;

    if output_format == OutputFormat::Json {
        let out = AgentCardOutput {
            name: card.name,
            url: card.url,
            description: card.description,
            capabilities: card.capabilities,
        };
        println!("{}", serde_json::to_string(&out)?);
        return Ok(());
    }

    println!("A2A Agent Card discovered from {}", url);
    println!();

    println!("  Name: {}", card.name);
    println!("  URL:  {}", card.url);

    if let Some(desc) = &card.description {
        println!("  Description: {}", desc);
    }

    if let Some(capabilities) = &card.capabilities {
        if !capabilities.is_empty() {
            println!();
            println!("  Capabilities:");
            for cap in capabilities {
                println!("    - {}", cap);
            }
        }
    }

    println!();
    println!("  Discovery: {}/.well-known/agent.json", url.trim_end_matches('/'));

    Ok(())
}