bzzz-cli 0.1.0

Bzzz CLI - Command line interface for Agent orchestration
//! Init command - Generate SwarmFile templates

use std::path::PathBuf;

use anyhow::{bail, Context, Result};

/// Template type for SwarmFile generation
#[derive(Debug, Clone, Copy, Default)]
pub enum TemplateType {
    #[default]
    Minimal,
    Full,
}

/// Pattern type for template generation
#[derive(Debug, Clone, Copy, Default)]
pub enum PatternType {
    #[default]
    Sequence,
    Parallel,
    Conditional,
    Loop,
    Delegate,
}

impl PatternType {
    fn as_str(&self) -> &'static str {
        match self {
            PatternType::Sequence => "sequence",
            PatternType::Parallel => "parallel",
            PatternType::Conditional => "conditional",
            PatternType::Loop => "loop",
            PatternType::Delegate => "delegate",
        }
    }
}

pub fn execute(file: PathBuf, pattern: Option<String>, template: Option<String>) -> Result<()> {
    // Check file doesn't exist (safety: no overwrite)
    if file.exists() {
        bail!(
            "File already exists: {}. Use a different path or remove the file first.",
            file.display()
        );
    }

    // Parse pattern type
    let pattern_type = match pattern.as_deref() {
        Some("sequence") => PatternType::Sequence,
        Some("parallel") => PatternType::Parallel,
        Some("conditional") => PatternType::Conditional,
        Some("loop") => PatternType::Loop,
        Some("delegate") => PatternType::Delegate,
        None => PatternType::default(),
        Some(p) => bail!(
            "Unknown pattern: {}. Valid: sequence, parallel, conditional, loop, delegate",
            p
        ),
    };

    // Parse template type
    let template_type = match template.as_deref() {
        Some("minimal") => TemplateType::Minimal,
        Some("full") => TemplateType::Full,
        None => TemplateType::default(),
        Some(t) => bail!("Unknown template: {}. Valid: minimal, full", t),
    };

    // Generate template content
    let content = generate_template(&pattern_type, &template_type);

    // Write file
    std::fs::write(&file, content)
        .with_context(|| format!("Failed to write {}", file.display()))?;

    println!("✅ Created: {}", file.display());
    println!("   Pattern: {}", pattern_type.as_str());
    println!(
        "   Template: {}",
        match template_type {
            TemplateType::Minimal => "minimal",
            TemplateType::Full => "full",
        }
    );

    Ok(())
}

fn generate_template(pattern: &PatternType, template: &TemplateType) -> String {
    match template {
        TemplateType::Minimal => generate_minimal_template(pattern),
        TemplateType::Full => generate_full_template(pattern),
    }
}

fn generate_minimal_template(pattern: &PatternType) -> String {
    let pattern_name = pattern.as_str();
    let pattern_yaml = match pattern {
        PatternType::Sequence => {
            "flow:\n  type: sequence\n  steps:\n    - worker-a"
        }
        PatternType::Parallel => {
            "flow:\n  type: parallel\n  branches:\n    - worker-a\n    - worker-b"
        }
        PatternType::Conditional => {
            "flow:\n  type: conditional\n  condition: '{{input.should_run}}'\n  then: worker-a\n  else: worker-b"
        }
        PatternType::Loop => {
            "flow:\n  type: loop\n  over: '{{input.items}}'\n  do: worker-a"
        }
        PatternType::Delegate => {
            "flow:\n  type: delegate\n  swarm: child.swarm.yaml"
        }
    };

    format!(
        "# SwarmFile - {pattern_name} pattern\n\
apiVersion: bzzz.dev/v1\n\
kind: swarm\n\
id: my-swarm\n\
workers:\n\
  - name: worker-a\n\
    spec: agents/worker-a.yaml\n\
{pattern_yaml}\n"
    )
}

fn generate_full_template(pattern: &PatternType) -> String {
    let pattern_name = pattern.as_str();
    let pattern_yaml = match pattern {
        PatternType::Sequence => {
            "flow:\n  type: sequence\n  steps:\n    - worker-a\n    # Add more steps here"
        }
        PatternType::Parallel => {
            "flow:\n  type: parallel\n  branches:\n    - worker-a\n    - worker-b\n  fail_fast: false  # Stop on first failure if true"
        }
        PatternType::Conditional => {
            "flow:\n  type: conditional\n  condition: '{{input.should_run}}'  # Expression evaluated at runtime\n  then: worker-a\n  else: worker-b  # Optional fallback"
        }
        PatternType::Loop => {
            "flow:\n  type: loop\n  over: '{{input.items}}'  # Array from input\n  do: worker-a\n  max_iterations: 100  # Safety limit (0 = unlimited)"
        }
        PatternType::Delegate => {
            "flow:\n  type: delegate\n  swarm: child.swarm.yaml\n  input_mapping:\n    query: '{{input.search}}'\n  output_mapping:\n    result: '{{steps.delegate.output.data}}'\n  failure_inherit: true  # Propagate failures to parent"
        }
    };

    // Use raw string concatenation instead of format!() to avoid escaping issues
    let header = "# SwarmFile - ".to_string()
        + pattern_name
        + " pattern (full template)\n\
#\n\
# This template includes all optional fields for reference.\n\
# Remove unused fields to keep your SwarmFile minimal.\n\
#\n\
# Usage:\n\
#   bzzz run -f this.swarm.yaml\n\
#   bzzz validate -f this.swarm.yaml\n\
#   bzzz inspect -f this.swarm.yaml\n\
\n\
apiVersion: bzzz.dev/v1\n\
kind: swarm\n\
id: my-swarm\n\
\n\
# Workers define the Agents participating in this swarm\n\
workers:\n\
  - name: worker-a\n\
    spec: agents/worker-a.yaml  # Local agent spec (mutually exclusive with a2a)\n\
    # a2a: https://agent.example.com  # Remote A2A agent\n\
    runtime: local  # Optional: override runtime (local, docker, http)\n\
    input:\n\
      key: '{{input.query}}'  # Parameter substitution\n\
\n\
# Optional: Swarm-level runtime default\n\
# runtime: docker\n\
\n\
# Optional: Failure behavior\n\
# on_failure: fail_fast  # Options: fail_fast, continue, ignore\n\
\n\
# Optional: Timeout in seconds\n\
# timeout: 60\n\
\n\
# Optional: Output behavior\n\
# output: last  # Options: all, last, aggregate\n\
\n\
# Optional: Interface definition for composition\n\
# interface:\n\
#   input:\n\
#     schema:\n\
#       type: object\n\
#       properties:\n\
#         query: { type: string }\n\
#       required: [query]\n\
#     required: true\n\
#   output:\n\
#     schema:\n\
#       type: object\n\
#       properties:\n\
#         result: { type: string }\n\
\n\
# Optional: Output exposure mapping\n\
# expose:\n\
#   - name: result\n\
#     from: steps.worker-a.output.value\n\
\n";

    header + pattern_yaml + "\n"
}