pmat 3.11.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
pub struct InteractiveScaffolder {
    /// Terminal for I/O.
    term: Term,
    /// Color theme for prompts.
    theme: ColorfulTheme,
}

impl InteractiveScaffolder {
    /// Create a new interactive scaffolder.
    #[must_use]
    pub fn new() -> Self {
        Self {
            term: Term::stdout(),
            theme: ColorfulTheme::default(),
        }
    }

    /// Run the interactive scaffolding process.
    pub fn run(&mut self) -> Result<AgentContext> {
        // Clear screen and show header
        self.term.clear_screen()?;
        self.show_header()?;

        // Prompt for basic information
        let name = self.prompt_name()?;
        let template = self.prompt_template()?;
        let features = self.prompt_features(&template)?;
        let quality = self.prompt_quality_level()?;

        // Build context
        let mut builder = AgentContextBuilder::new(&name, self.template_to_string(&template));
        for feature in &features {
            builder = builder.with_feature(feature.clone());
        }
        builder = builder.with_quality_level(quality);

        // Handle hybrid-specific configuration
        if matches!(template, AgentTemplate::HybridAnalyzer) {
            let core = self.prompt_deterministic_core()?;
            let wrapper = self.prompt_probabilistic_wrapper()?;
            builder = builder
                .with_deterministic_core(core)
                .with_probabilistic_wrapper(wrapper);
        }

        // Build and confirm
        let ctx = builder.build()?;
        self.confirm_and_display(&ctx)?;

        Ok(ctx)
    }

    /// Show the header.
    fn show_header(&self) -> Result<()> {
        println!("╔══════════════════════════════════════════╗");
        println!("║      PMAT Agent Scaffolder v0.1.0       ║");
        println!("║   Interactive Agent Creation Wizard      ║");
        println!("╚══════════════════════════════════════════╝");
        println!();
        Ok(())
    }

    /// Prompt for agent name.
    fn prompt_name(&self) -> Result<String> {
        loop {
            let name: String = Input::with_theme(&self.theme)
                .with_prompt("Agent name")
                .validate_with(|input: &String| -> Result<(), &str> {
                    if input.is_empty() {
                        Err("Name cannot be empty")
                    } else if !input.chars().all(|c| c.is_alphanumeric() || c == '_') {
                        Err("Name must be alphanumeric with underscores only")
                    } else if input.chars().next().is_some_and(char::is_numeric) {
                        Err("Name cannot start with a number")
                    } else {
                        Ok(())
                    }
                })
                .interact_text()?;

            if name.len() > 64 {
                eprintln!("Warning: Name is quite long. Consider a shorter name.");
                if !Confirm::with_theme(&self.theme)
                    .with_prompt("Continue with this name?")
                    .interact()?
                {
                    continue;
                }
            }

            return Ok(name);
        }
    }

    /// Prompt for template type.
    fn prompt_template(&self) -> Result<AgentTemplate> {
        let items = [
            ("MCP Tool Server", "Standard MCP server with async handlers"),
            (
                "State Machine Workflow",
                "Agent with state transitions and invariants",
            ),
            (
                "Deterministic Calculator",
                "Pure deterministic computation agent",
            ),
            ("Hybrid Analyzer", "Deterministic core with AI wrapper"),
            ("Custom Template", "Use a custom template from file"),
        ];

        let selection = Select::with_theme(&self.theme)
            .with_prompt("Select template type")
            .items(
                items
                    .iter()
                    .map(|(name, desc)| format!("{name}\n   {desc}"))
                    .collect::<Vec<_>>(),
            )
            .default(0)
            .interact()?;

        Ok(match selection {
            0 => AgentTemplate::MCPToolServer,
            1 => AgentTemplate::StateMachineWorkflow,
            2 => AgentTemplate::DeterministicCalculator,
            3 => AgentTemplate::HybridAnalyzer,
            4 => {
                let path: String = Input::with_theme(&self.theme)
                    .with_prompt("Custom template path")
                    .interact_text()?;
                AgentTemplate::CustomAgent(PathBuf::from(path))
            }
            _ => unreachable!(),
        })
    }

    /// Prompt for features to include.
    fn prompt_features(&self, template: &AgentTemplate) -> Result<HashSet<AgentFeature>> {
        let available_features = self.get_available_features(template);

        if available_features.is_empty() {
            return Ok(HashSet::new());
        }

        let feature_names: Vec<String> = available_features
            .iter()
            .map(|f| self.feature_to_string(f))
            .collect();

        let selections = MultiSelect::with_theme(&self.theme)
            .with_prompt("Select features to include (Space to select, Enter to confirm)")
            .items(&feature_names)
            .interact()?;

        let mut features = HashSet::new();
        for idx in selections {
            features.insert(available_features[idx].clone());
        }

        Ok(features)
    }

    /// Get available features for a template.
    fn get_available_features(&self, template: &AgentTemplate) -> Vec<AgentFeature> {
        match template {
            AgentTemplate::MCPToolServer => vec![
                AgentFeature::ToolComposition,
                AgentFeature::AsyncHandlers,
                AgentFeature::ResourceSubscriptions,
                AgentFeature::Monitoring {
                    backend: super::features::MonitoringBackend::Prometheus,
                },
                AgentFeature::Tracing {
                    exporter: super::features::TraceExporter::OTLP,
                },
                AgentFeature::HealthChecks,
            ],
            AgentTemplate::StateMachineWorkflow => vec![
                AgentFeature::StateMachine {
                    states: vec![
                        "Initial".to_string(),
                        "Processing".to_string(),
                        "Complete".to_string(),
                    ],
                },
                AgentFeature::QualityGates {
                    level: QualityLevel::Extreme,
                },
            ],
            AgentTemplate::HybridAnalyzer => vec![
                AgentFeature::ComplexityAnalysis,
                AgentFeature::SATDDetection,
                AgentFeature::DeadCodeElimination,
            ],
            _ => vec![],
        }
    }

    /// Convert feature to display string.
    fn feature_to_string(&self, feature: &AgentFeature) -> String {
        match feature {
            AgentFeature::StateMachine { .. } => "State Machine with transitions".to_string(),
            AgentFeature::QualityGates { .. } => "Quality Gates enforcement".to_string(),
            AgentFeature::ToolComposition => "Tool Composition support".to_string(),
            AgentFeature::AsyncHandlers => "Async request handlers".to_string(),
            AgentFeature::ResourceSubscriptions => "Resource subscriptions".to_string(),
            AgentFeature::ComplexityAnalysis => "Complexity analysis".to_string(),
            AgentFeature::SATDDetection => "SATD detection".to_string(),
            AgentFeature::DeadCodeElimination => "Dead code elimination".to_string(),
            AgentFeature::Monitoring { .. } => "Monitoring integration".to_string(),
            AgentFeature::Tracing { .. } => "Distributed tracing".to_string(),
            AgentFeature::HealthChecks => "Health check endpoints".to_string(),
        }
    }

    /// Prompt for quality level.
    fn prompt_quality_level(&self) -> Result<QualityLevel> {
        let items = [
            ("Standard", "Basic quality checks, suitable for prototypes"),
            ("Strict", "Zero warnings, high test coverage"),
            (
                "Extreme (Toyota Way)",
                "Zero SATD, max complexity 10, full verification",
            ),
        ];

        let selection = Select::with_theme(&self.theme)
            .with_prompt("Quality level")
            .items(
                items
                    .iter()
                    .map(|(name, desc)| format!("{name}\n   {desc}"))
                    .collect::<Vec<_>>(),
            )
            .default(1)
            .interact()?;

        Ok(match selection {
            0 => QualityLevel::Standard,
            1 => QualityLevel::Strict,
            2 => QualityLevel::Extreme,
            _ => unreachable!(),
        })
    }

    /// Prompt for deterministic core specification.
    fn prompt_deterministic_core(&self) -> Result<CoreSpec> {
        println!("\n=== Deterministic Core Configuration ===");

        let verification_items = ["Property-based tests", "Formal proof", "Model checking"];

        let verification_idx = Select::with_theme(&self.theme)
            .with_prompt("Verification method")
            .items(verification_items)
            .default(0)
            .interact()?;

        let verification_method = match verification_idx {
            0 => VerificationMethod::PropertyTests,
            1 => VerificationMethod::FormalProof,
            2 => VerificationMethod::ModelChecking,
            _ => unreachable!(),
        };

        let max_complexity = Input::with_theme(&self.theme)
            .with_prompt("Maximum cyclomatic complexity")
            .default(10)
            .validate_with(|input: &u32| -> Result<(), &str> {
                if *input == 0 {
                    Err("Complexity must be at least 1")
                } else if *input > 50 {
                    Err("Complexity should not exceed 50")
                } else {
                    Ok(())
                }
            })
            .interact()?;

        Ok(CoreSpec {
            verification_method,
            max_complexity,
            invariants: Vec::new(),
        })
    }

    /// Prompt for probabilistic wrapper specification.
    fn prompt_probabilistic_wrapper(&self) -> Result<WrapperSpec> {
        println!("\n=== Probabilistic Wrapper Configuration ===");

        let model_items = ["GPT-4", "Claude", "Local model"];

        let model_idx = Select::with_theme(&self.theme)
            .with_prompt("AI model type")
            .items(model_items)
            .default(0)
            .interact()?;

        let model_type = match model_idx {
            0 => ModelType::GPT4,
            1 => ModelType::Claude,
            2 => {
                let path = Input::with_theme(&self.theme)
                    .with_prompt("Local model path")
                    .interact_text()?;
                ModelType::Local(path)
            }
            _ => unreachable!(),
        };

        let fallback_items = ["Deterministic fallback", "Default value", "Return error"];

        let fallback_idx = Select::with_theme(&self.theme)
            .with_prompt("Fallback strategy")
            .items(fallback_items)
            .default(0)
            .interact()?;

        let fallback_strategy = match fallback_idx {
            0 => FallbackStrategy::Deterministic,
            1 => FallbackStrategy::DefaultValue,
            2 => FallbackStrategy::Error,
            _ => unreachable!(),
        };

        let confidence_threshold = Input::with_theme(&self.theme)
            .with_prompt("Confidence threshold (0.0-1.0)")
            .default(0.95)
            .validate_with(|input: &f64| -> Result<(), &str> {
                if *input < 0.0 || *input > 1.0 {
                    Err("Threshold must be between 0.0 and 1.0")
                } else {
                    Ok(())
                }
            })
            .interact()?;

        Ok(WrapperSpec {
            model_type,
            fallback_strategy,
            confidence_threshold,
        })
    }

    /// Convert template to string representation.
    fn template_to_string(&self, template: &AgentTemplate) -> String {
        match template {
            AgentTemplate::MCPToolServer => "mcp-server".to_string(),
            AgentTemplate::StateMachineWorkflow => "state-machine".to_string(),
            AgentTemplate::DeterministicCalculator => "calculator".to_string(),
            AgentTemplate::HybridAnalyzer => "hybrid".to_string(),
            AgentTemplate::CustomAgent(path) => format!("custom:{}", path.display()),
        }
    }

    /// Confirm and display the configuration.
    fn confirm_and_display(&self, ctx: &AgentContext) -> Result<()> {
        println!("\n╔══════════════════════════════════════════╗");
        println!("║         Agent Configuration Summary      ║");
        println!("╚══════════════════════════════════════════╝");
        println!();
        println!("  Name:     {}", ctx.name);
        println!("  Template: {:?}", ctx.template_type);
        println!("  Quality:  {:?}", ctx.quality_level);
        println!("  Features: {} enabled", ctx.features.len());

        if let Some(core) = &ctx.deterministic_core {
            println!("\n  Deterministic Core:");
            println!("    Verification: {:?}", core.verification_method);
            println!("    Max Complexity: {}", core.max_complexity);
        }

        if let Some(wrapper) = &ctx.probabilistic_wrapper {
            println!("\n  Probabilistic Wrapper:");
            println!("    Model: {:?}", wrapper.model_type);
            println!("    Fallback: {:?}", wrapper.fallback_strategy);
            println!("    Confidence: {:.2}", wrapper.confidence_threshold);
        }

        println!();
        let confirm = Confirm::with_theme(&self.theme)
            .with_prompt("Generate agent with these settings?")
            .default(true)
            .interact()?;

        if !confirm {
            return Err(ScaffoldError::UserCancelled.into());
        }

        Ok(())
    }
}

impl Default for InteractiveScaffolder {
    fn default() -> Self {
        Self::new()
    }
}