pmat 3.11.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
/// Format incremental coverage as LCOV
fn format_incremental_coverage_lcov(report: &IncrementalCoverageReport) -> Result<String> {
    let mut output = String::new();

    for file in &report.files {
        output.push_str("TN:\n");
        output.push_str(&format!("SF:{}\n", file.path.display()));

        // Generate fake line data based on coverage
        for line in 1..=file.lines_added {
            if line <= file.lines_covered {
                output.push_str(&format!("DA:{line},1\n"));
            } else {
                output.push_str(&format!("DA:{line},0\n"));
            }
        }

        output.push_str(&format!("LF:{}\n", file.lines_added));
        output.push_str(&format!("LH:{}\n", file.lines_covered));
        output.push_str("end_of_record\n");
    }

    Ok(output)
}

/// Format incremental coverage as SARIF
fn format_incremental_coverage_sarif(report: &IncrementalCoverageReport) -> Result<String> {
    use serde_json::json;

    let runs = vec![json!({
        "tool": {
            "driver": {
                "name": "pmat-incremental-coverage",
                "version": "2.13.3"
            }
        },
        "results": report.files.iter().filter(|f| f.coverage_delta < 0.0).map(|file| {
            json!({
                "ruleId": "coverage-decrease",
                "level": "warning",
                "message": {
                    "text": format!("Coverage decreased by {:.1}% in {}",
                             file.coverage_delta.abs(), file.path.display())
                },
                "locations": [{
                    "physicalLocation": {
                        "artifactLocation": {
                            "uri": file.path.to_string_lossy()
                        }
                    }
                }]
            })
        }).collect::<Vec<_>>()
    })];

    let sarif = json!({
        "version": "2.1.0",
        "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
        "runs": runs
    });

    Ok(serde_json::to_string_pretty(&sarif)?)
}

/// Format incremental coverage summary with top files
///
/// # Examples
///
/// ```ignore
/// use pmat::cli::analysis_utilities::format_incremental_coverage_summary;
/// use std::path::{Path, PathBuf};
///
/// // Create test data (would normally come from generate_stub_incremental_coverage)
/// let report = r#"{
///     "base_branch": "main",
///     "target_branch": "feature",
///     "coverage_threshold": 0.8,
///     "files": [
///         {
///             "path": "src/main.rs",
///             "base_coverage": 75.5,
///             "target_coverage": 82.3,
///             "coverage_delta": 6.8,
///             "lines_added": 45,
///             "lines_covered": 37,
///             "lines_uncovered": 8
///         }
///     ],
///     "summary": {
///         "total_files_changed": 1,
///         "files_improved": 1,
///         "files_degraded": 0,
///         "overall_delta": 6.8,
///         "meets_threshold": true
///     }
/// }"#;
///
/// // In real usage, this would be an IncrementalCoverageReport struct
/// // let output = format_incremental_coverage_summary(&report, 10).unwrap();
/// // assert!(output.contains("Top Files by Coverage Change"));
/// ```ignore
/// Formats incremental coverage analysis into a comprehensive summary.
///
/// This function creates a detailed markdown report showing coverage changes
/// between branches, broken down into focused sections for better readability.
/// Refactored from a monolithic implementation to improve maintainability.
///
/// # Arguments
///
/// * `report` - The incremental coverage report data
/// * `top_files` - Number of top files to display (0 = all files)
///
/// # Returns
///
/// A formatted string containing the coverage analysis report
///
/// # Examples
///
/// ```ignore
/// // This function formats incremental coverage reports
/// // See the examples/ directory for usage demonstrations
/// // Basic doctest to verify function is available
/// ```ignore
pub fn format_incremental_coverage_summary(
    report: &IncrementalCoverageReport,
    top_files: usize,
) -> Result<String> {
    let mut output = String::new();

    write_coverage_header(&mut output, report)?;
    write_coverage_summary(&mut output, &report.summary)?;
    write_coverage_file_details(&mut output, &report.files, top_files)?;

    Ok(output)
}

/// Write the header section of the coverage report
fn write_coverage_header(output: &mut String, report: &IncrementalCoverageReport) -> Result<()> {
    use std::fmt::Write;

    writeln!(output, "# Incremental Coverage Analysis\n")?;
    writeln!(output, "**Base Branch**: {}", report.base_branch)?;
    writeln!(output, "**Target Branch**: {}", report.target_branch)?;
    writeln!(
        output,
        "**Coverage Threshold**: {:.1}%",
        report.coverage_threshold * 100.0
    )?;
    writeln!(
        output,
        "**Overall Delta**: {:+.1}%",
        report.summary.overall_delta
    )?;
    writeln!(
        output,
        "**Meets Threshold**: {}\n",
        if report.summary.meets_threshold {
            "✅ Yes"
        } else {
            "❌ No"
        }
    )?;

    Ok(())
}

/// Write the summary section of the coverage report
fn write_coverage_summary(output: &mut String, summary: &CoverageSummary) -> Result<()> {
    use std::fmt::Write;

    writeln!(output, "## Summary\n")?;
    writeln!(output, "- Files Changed: {}", summary.total_files_changed)?;
    writeln!(output, "- Files Improved: {} 📈", summary.files_improved)?;
    writeln!(output, "- Files Degraded: {} 📉\n", summary.files_degraded)?;

    Ok(())
}

/// Write the detailed file changes section of the coverage report
fn write_coverage_file_details(
    output: &mut String,
    files: &[FileCoverageMetrics],
    top_files: usize,
) -> Result<()> {
    use std::fmt::Write;

    writeln!(output, "## Top Files by Coverage Change\n")?;

    let mut sorted_files = files.to_vec();
    sorted_files.sort_unstable_by(|a, b| {
        b.coverage_delta
            .abs()
            .partial_cmp(&a.coverage_delta.abs())
            .unwrap_or(std::cmp::Ordering::Equal)
    });

    let files_to_show = calculate_files_to_show(&sorted_files, top_files);
    write_file_entries(output, &sorted_files, files_to_show)?;

    Ok(())
}

/// Calculate the number of files to display based on parameters
pub fn calculate_files_to_show(files: &[FileCoverageMetrics], top_files: usize) -> usize {
    if top_files == 0 {
        files.len()
    } else {
        top_files.min(files.len())
    }
}

/// Write individual file coverage entries
fn write_file_entries(
    output: &mut String,
    files: &[FileCoverageMetrics],
    files_to_show: usize,
) -> Result<()> {
    use std::fmt::Write;

    for (i, file) in files.iter().take(files_to_show).enumerate() {
        let filename = extract_filename(&file.path);
        let emoji = get_coverage_emoji(file.coverage_delta);

        writeln!(
            output,
            "{}. `{}` - {:.1}% → {:.1}% ({:+.1}%) {}",
            i + 1,
            filename,
            file.base_coverage,
            file.target_coverage,
            file.coverage_delta,
            emoji
        )?;
    }

    Ok(())
}

/// Extract filename from path for display
pub fn extract_filename(path: &std::path::Path) -> &str {
    path.file_name()
        .and_then(|n| n.to_str())
        .unwrap_or("unknown")
}

/// Get appropriate emoji for coverage delta
pub fn get_coverage_emoji(delta: f64) -> &'static str {
    if delta > 0.0 {
        "📈"
    } else {
        "📉"
    }
}

fn format_incremental_coverage_detailed(
    report: &IncrementalCoverageReport,
    top_files: usize,
) -> Result<String> {
    format_incremental_coverage_summary(report, top_files) // For stub, reuse summary
}

fn format_incremental_coverage_markdown(
    report: &IncrementalCoverageReport,
    top_files: usize,
) -> Result<String> {
    format_incremental_coverage_summary(report, top_files) // For stub, reuse summary
}

fn format_incremental_coverage_delta(
    report: &IncrementalCoverageReport,
    _top_files: usize,
) -> Result<String> {
    use std::fmt::Write;
    let mut output = String::new();

    writeln!(&mut output, "Coverage Delta Report\n")?;
    for file in &report.files {
        let filename = file.path.display();
        writeln!(&mut output, "{}: {:+.1}%", filename, file.coverage_delta)?;
    }

    Ok(output)
}