pmat 3.11.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
//! Maps MCP tool parameters to uniform contracts
//! This ensures MCP uses the exact same contracts as CLI and HTTP
#![cfg_attr(coverage_nightly, coverage(off))]

use super::{
    AnalyzeComplexityContract, AnalyzeDeadCodeContract, AnalyzeLintHotspotContract,
    AnalyzeSatdContract, AnalyzeTdgContract, BaseAnalysisContract, ContractValidation,
    OutputFormat, QualityGateContract, QualityProfile, RefactorAutoContract, SatdSeverity,
};
use anyhow::Result;
use serde_json::Value;
use std::path::PathBuf;

/// Convert MCP tool parameters to uniform contracts
pub fn map_mcp_tool(tool_name: &str, params: Value) -> Result<Box<dyn ContractValidation>> {
    match tool_name {
        "analyze_complexity" => map_complexity_contract(&params),
        "analyze_satd" => map_satd_contract(&params),
        "analyze_dead_code" => map_dead_code_contract(&params),
        "analyze_tdg" => map_tdg_contract(&params),
        "analyze_lint_hotspot" => map_lint_hotspot_contract(&params),
        "quality_gate" => map_quality_gate_contract(&params),
        "refactor_auto" => map_refactor_auto_contract(&params),
        _ => anyhow::bail!("Unknown MCP tool: {tool_name}"),
    }
}

fn map_complexity_contract(params: &Value) -> Result<Box<dyn ContractValidation>> {
    let contract = AnalyzeComplexityContract {
        base: parse_base_params(params)?,
        max_cyclomatic: params["max_cyclomatic"].as_u64().map(|n| n as u32),
        max_cognitive: params["max_cognitive"].as_u64().map(|n| n as u32),
        max_halstead: params["max_halstead"].as_f64(),
    };
    contract.validate()?;
    Ok(Box::new(contract))
}

fn map_satd_contract(params: &Value) -> Result<Box<dyn ContractValidation>> {
    let contract = AnalyzeSatdContract {
        base: parse_base_params(params)?,
        severity: parse_severity(&params["severity"]),
        critical_only: params["critical_only"].as_bool().unwrap_or(false),
        strict: params["strict"].as_bool().unwrap_or(false),
        fail_on_violation: params["fail_on_violation"].as_bool().unwrap_or(false),
    };
    contract.validate()?;
    Ok(Box::new(contract))
}

fn map_dead_code_contract(params: &Value) -> Result<Box<dyn ContractValidation>> {
    let contract = AnalyzeDeadCodeContract {
        base: parse_base_params(params)?,
        include_unreachable: params["include_unreachable"].as_bool().unwrap_or(false),
        min_dead_lines: params["min_dead_lines"].as_u64().unwrap_or(10) as usize,
        max_percentage: params["max_percentage"].as_f64().unwrap_or(15.0),
        fail_on_violation: params["fail_on_violation"].as_bool().unwrap_or(false),
    };
    contract.validate()?;
    Ok(Box::new(contract))
}

fn map_tdg_contract(params: &Value) -> Result<Box<dyn ContractValidation>> {
    let contract = AnalyzeTdgContract {
        base: parse_base_params(params)?,
        threshold: params["threshold"].as_f64().unwrap_or(1.5),
        include_components: params["include_components"].as_bool().unwrap_or(false),
        critical_only: params["critical_only"].as_bool().unwrap_or(false),
    };
    contract.validate()?;
    Ok(Box::new(contract))
}

fn map_lint_hotspot_contract(params: &Value) -> Result<Box<dyn ContractValidation>> {
    let contract = AnalyzeLintHotspotContract {
        base: parse_base_params(params)?,
        file: params["file"].as_str().map(PathBuf::from),
        max_density: params["max_density"].as_f64().unwrap_or(5.0),
        min_confidence: params["min_confidence"].as_f64().unwrap_or(0.8),
        enforce: params["enforce"].as_bool().unwrap_or(false),
        dry_run: params["dry_run"].as_bool().unwrap_or(false),
    };
    contract.validate()?;
    Ok(Box::new(contract))
}

fn map_quality_gate_contract(params: &Value) -> Result<Box<dyn ContractValidation>> {
    let contract = QualityGateContract {
        base: parse_base_params(params)?,
        profile: parse_quality_profile(&params["profile"]),
        file: params["file"].as_str().map(PathBuf::from),
        fail_on_violation: params["fail_on_violation"].as_bool().unwrap_or(false),
        verbose: params["verbose"].as_bool().unwrap_or(false),
    };
    contract.validate()?;
    Ok(Box::new(contract))
}

fn map_refactor_auto_contract(params: &Value) -> Result<Box<dyn ContractValidation>> {
    let file_path = params["file"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("Missing required parameter: file"))?;

    let contract = RefactorAutoContract {
        file: PathBuf::from(file_path),
        format: parse_output_format(&params["format"]),
        output: params["output"].as_str().map(PathBuf::from),
        target_complexity: params["target_complexity"].as_u64().unwrap_or(8) as u32,
        dry_run: params["dry_run"].as_bool().unwrap_or(false),
        timeout: params["timeout"].as_u64().unwrap_or(60),
    };
    contract.validate()?;
    Ok(Box::new(contract))
}

/// Parse base parameters that are common to all analysis commands
fn parse_base_params(params: &Value) -> Result<BaseAnalysisContract> {
    let path = params["path"]
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("Missing required parameter: path"))?;

    Ok(BaseAnalysisContract {
        path: PathBuf::from(path),
        format: parse_output_format(&params["format"]),
        output: params["output"].as_str().map(PathBuf::from),
        top_files: params["top_files"].as_u64().map(|n| n as usize),
        include_tests: params["include_tests"].as_bool().unwrap_or(false),
        timeout: params["timeout"].as_u64().unwrap_or(60),
    })
}

fn parse_output_format(value: &Value) -> OutputFormat {
    value
        .as_str()
        .and_then(|s| match s {
            "table" => Some(OutputFormat::Table),
            "json" => Some(OutputFormat::Json),
            "yaml" => Some(OutputFormat::Yaml),
            "markdown" => Some(OutputFormat::Markdown),
            "csv" => Some(OutputFormat::Csv),
            "summary" => Some(OutputFormat::Summary),
            _ => None,
        })
        .unwrap_or_default()
}

fn parse_severity(value: &Value) -> Option<SatdSeverity> {
    value.as_str().and_then(|s| match s {
        "low" => Some(SatdSeverity::Low),
        "medium" => Some(SatdSeverity::Medium),
        "high" => Some(SatdSeverity::High),
        "critical" => Some(SatdSeverity::Critical),
        _ => None,
    })
}

fn parse_quality_profile(value: &Value) -> QualityProfile {
    value
        .as_str()
        .and_then(|s| match s {
            "standard" => Some(QualityProfile::Standard),
            "strict" => Some(QualityProfile::Strict),
            "extreme" => Some(QualityProfile::Extreme),
            "toyota" => Some(QualityProfile::Toyota),
            _ => None,
        })
        .unwrap_or_default()
}