pmat 3.15.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
#![cfg_attr(coverage_nightly, coverage(off))]
//! Output formatting functions for Big-O complexity analysis

use crate::cli::colors as c;
use crate::cli::BigOOutputFormat;
use crate::models::complexity_bound::BigOClass;
use crate::services::big_o_analyzer::{BigOAnalysisReport, BigOAnalyzer};
use anyhow::Result;
use std::path::PathBuf;
use tracing::info;

/// Format analysis output
pub(super) fn format_analysis_output(
    analyzer: &BigOAnalyzer,
    report: &BigOAnalysisReport,
    format: BigOOutputFormat,
) -> Result<String> {
    match format {
        BigOOutputFormat::Json => analyzer.format_as_json(report),
        BigOOutputFormat::Markdown => Ok(analyzer.format_as_markdown(report)),
        BigOOutputFormat::Summary => Ok(format_big_o_summary(report)),
        BigOOutputFormat::Detailed => Ok(format_big_o_detailed(report)),
    }
}

/// Write analysis output to file or stdout
pub(super) async fn write_analysis_output(content: &str, output: Option<PathBuf>) -> Result<()> {
    if let Some(output_path) = output {
        tokio::fs::write(&output_path, content).await?;
        info!("📄 Big-O analysis saved to: {}", output_path.display());
    } else {
        println!("{content}");
    }
    Ok(())
}

/// Format Big-O report as summary with top files
///
/// # Examples
///
/// ```no_run
/// use pmat::cli::handlers::big_o_handlers::format_big_o_summary;
/// use pmat::services::big_o_analyzer::{BigOAnalysisReport, FunctionComplexity};
/// use pmat::models::complexity_bound::{ComplexityBound, BigOClass};
/// use std::path::PathBuf;
///
/// let report = BigOAnalysisReport {
///     analyzed_functions: 100,
///     high_complexity_functions: vec![
///         FunctionComplexity {
///             function_name: "sort_data".to_string(),
///             file_path: PathBuf::from("src/utils.rs"),
///             line_number: 42,
///             time_complexity: ComplexityBound::quadratic().with_confidence(90),
///             space_complexity: ComplexityBound::linear().with_confidence(85),
///             confidence: 90,
///             notes: vec![],
///         },
///     ],
///     complexity_distribution: pmat::services::big_o_analyzer::ComplexityDistribution {
///         constant: 20,
///         logarithmic: 10,
///         linear: 50,
///         linearithmic: 5,
///         quadratic: 10,
///         cubic: 2,
///         exponential: 1,
///         unknown: 2,
///     },
///     pattern_matches: vec![],
///     recommendations: vec!["Consider optimizing quadratic algorithms".to_string()],
/// };
///
/// let output = format_big_o_summary(&report);
/// assert!(output.contains("Top Files by Complexity"));
/// assert!(output.contains("utils.rs"));
/// ```
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn format_big_o_summary(report: &BigOAnalysisReport) -> String {
    let mut output = String::with_capacity(1024);

    output.push_str(&format!(
        "{}\n",
        c::header("Big-O Complexity Analysis Summary")
    ));
    output.push('\n');

    output.push_str(&format!(
        "  {}: {}\n",
        c::label("Total Functions Analyzed"),
        c::number(&report.analyzed_functions.to_string()),
    ));
    let high_color = if report.high_complexity_functions.is_empty() {
        c::GREEN
    } else {
        c::YELLOW
    };
    output.push_str(&format!(
        "  {}: {}{}{}\n\n",
        c::label("High Complexity Functions"),
        high_color,
        report.high_complexity_functions.len(),
        c::RESET,
    ));

    output.push_str(&format!("{}\n", c::subheader("Complexity Distribution:")));
    let dist = &report.complexity_distribution;
    output.push_str(&format!(
        "  {}O(1){}       : {} functions\n",
        c::GREEN,
        c::RESET,
        c::number(&format!("{:>4}", dist.constant))
    ));
    output.push_str(&format!(
        "  {}O(log n){}   : {} functions\n",
        c::GREEN,
        c::RESET,
        c::number(&format!("{:>4}", dist.logarithmic))
    ));
    output.push_str(&format!(
        "  {}O(n){}       : {} functions\n",
        c::YELLOW,
        c::RESET,
        c::number(&format!("{:>4}", dist.linear))
    ));
    output.push_str(&format!(
        "  {}O(n log n){} : {} functions\n",
        c::YELLOW,
        c::RESET,
        c::number(&format!("{:>4}", dist.linearithmic))
    ));
    output.push_str(&format!(
        "  {}O(n²){}      : {} functions\n",
        c::RED,
        c::RESET,
        c::number(&format!("{:>4}", dist.quadratic))
    ));
    output.push_str(&format!(
        "  {}O(n³){}      : {} functions\n",
        c::RED,
        c::RESET,
        c::number(&format!("{:>4}", dist.cubic))
    ));
    output.push_str(&format!(
        "  {}O(2^n){}     : {} functions\n",
        c::BOLD_RED,
        c::RESET,
        c::number(&format!("{:>4}", dist.exponential))
    ));
    output.push_str(&format!(
        "  {}Unknown{}    : {} functions\n",
        c::DIM,
        c::RESET,
        c::number(&format!("{:>4}", dist.unknown))
    ));

    if !report.recommendations.is_empty() {
        output.push_str(&format!("\n{}\n", c::subheader("Recommendations:")));
        for rec in &report.recommendations {
            output.push_str(&format!("  {} {rec}\n", c::warn("")));
        }
    }

    // Show top files by complexity
    if !report.high_complexity_functions.is_empty() {
        output.push_str(&format!("\n{}\n", c::subheader("Top Files by Complexity:")));

        // Group functions by file
        use std::collections::HashMap;
        let mut file_scores: HashMap<&std::path::Path, f64> = HashMap::new();
        let mut file_function_counts: HashMap<&std::path::Path, usize> = HashMap::new();

        for func in &report.high_complexity_functions {
            let score = match func.time_complexity.class {
                BigOClass::Constant => 1.0,
                BigOClass::Logarithmic => 2.0,
                BigOClass::Linear => 3.0,
                BigOClass::Linearithmic => 4.0,
                BigOClass::Quadratic => 5.0,
                BigOClass::Cubic => 6.0,
                BigOClass::Exponential => 7.0,
                BigOClass::Factorial => 8.0,
                BigOClass::Unknown => 3.0,
            };
            *file_scores.entry(&func.file_path).or_insert(0.0) += score;
            *file_function_counts.entry(&func.file_path).or_insert(0) += 1;
        }

        // Sort files by total complexity score
        let mut sorted_files: Vec<_> = file_scores.into_iter().collect();
        sorted_files.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));

        // Display top 10 files
        for (i, (file_path, score)) in sorted_files.iter().take(10).enumerate() {
            let filename = file_path
                .file_name()
                .and_then(|n| n.to_str())
                .unwrap_or(file_path.to_str().unwrap_or("unknown"));
            let function_count = file_function_counts.get(file_path).unwrap_or(&0);
            let score_color = if *score > 20.0 {
                c::RED
            } else if *score > 10.0 {
                c::YELLOW
            } else {
                c::GREEN
            };
            output.push_str(&format!(
                "  {}. {} - score: {}{:.1}{}, {} functions\n",
                c::number(&(i + 1).to_string()),
                c::path(filename),
                score_color,
                score,
                c::RESET,
                c::number(&function_count.to_string()),
            ));
        }
    }

    output
}

/// Format Big-O report with detailed information
pub(super) fn format_big_o_detailed(report: &BigOAnalysisReport) -> String {
    let mut output = format_big_o_summary(report);

    if !report.high_complexity_functions.is_empty() {
        output.push_str(&format!("\n{}\n", c::header("High Complexity Functions:")));

        for func in &report.high_complexity_functions {
            output.push_str(&format!(
                "\n{} ({}:{}{}{})\n",
                c::label(&func.function_name),
                c::path(&func.file_path.display().to_string()),
                c::DIM,
                func.line_number,
                c::RESET,
            ));
            output.push_str(&format!(
                "  {}: {} ({})\n",
                c::label("Time Complexity"),
                func.time_complexity.notation(),
                c::pct(func.time_complexity.confidence as f64, 80.0, 50.0),
            ));
            output.push_str(&format!(
                "  {}: {} ({})\n",
                c::label("Space Complexity"),
                func.space_complexity.notation(),
                c::pct(func.space_complexity.confidence as f64, 80.0, 50.0),
            ));

            if !func.notes.is_empty() {
                output.push_str(&format!("  {}:\n", c::label("Notes")));
                for note in &func.notes {
                    output.push_str(&format!("    {}─{} {note}\n", c::DIM, c::RESET));
                }
            }
        }
    }

    if !report.pattern_matches.is_empty() {
        output.push_str(&format!("\n{}\n", c::header("Pattern Matches:")));

        for pattern in &report.pattern_matches {
            output.push_str(&format!(
                "  {} : {} occurrences\n",
                c::label(&pattern.pattern_name),
                c::number(&pattern.occurrences.to_string()),
            ));
        }
    }

    output
}