/**
 * Evaluation Report Generator
 *
 * Generates markdown reports from evaluation results
 * for publishing precision/recall metrics.
 *
 * @module eval/report
 */
import { formatPercent, getGrade, meetsTargets, } from "./metrics.js";
/**
 * Generate a complete evaluation report
 */
export function generateEvaluationReport(metrics, fixtureResults, config, stability, commitSha) {
    return {
        version: "1.0.0",
        timestamp: new Date().toISOString(),
        commitSha,
        metrics,
        stability,
        fixtureResults,
        config,
    };
}
/**
 * Format evaluation report as markdown
 */
export function formatReportAsMarkdown(report) {
    const sections = [];
    // Header
    sections.push(generateHeader(report));
    // Summary badge
    sections.push(generateSummaryBadge(report.metrics));
    // Overall metrics
    sections.push(generateOverallMetrics(report.metrics));
    // Target comparison
    sections.push(generateTargetComparison(report.metrics));
    // Category breakdown
    sections.push(generateCategoryBreakdown(report.metrics));
    // Scanner performance
    sections.push(generateScannerPerformance(report.metrics));
    // Stability metrics if available
    if (report.stability) {
        sections.push(generateStabilitySection(report.stability));
    }
    // Fixture results summary
    sections.push(generateFixtureSummary(report.fixtureResults));
    // False positives/negatives details
    sections.push(generateErrorAnalysis(report.fixtureResults));
    // Footer
    sections.push(generateFooter(report));
    return sections.join("\n\n");
}
/**
 * Generate report header
 */
function generateHeader(report) {
    const lines = [
        "# Vaspera Hardening Evaluation Report",
        "",
        `**Generated:** ${new Date(report.timestamp).toLocaleString()}`,
    ];
    if (report.commitSha) {
        lines.push(`**Commit:** \`${report.commitSha.slice(0, 7)}\``);
    }
    lines.push(`**Version:** ${report.version}`);
    return lines.join("\n");
}
/**
 * Generate summary badge based on metrics
 */
function generateSummaryBadge(metrics) {
    const { meetsAll, results } = meetsTargets(metrics);
    const grade = getGrade(metrics.f1Score);
    const status = meetsAll ? "PASSING" : "NEEDS IMPROVEMENT";
    const icon = meetsAll ? "✅" : "⚠️";
    return `## ${icon} Overall: ${status} (Grade: ${grade})`;
}
/**
 * Generate overall metrics table
 */
function generateOverallMetrics(metrics) {
    const lines = [
        "## Overall Metrics",
        "",
        "| Metric | Value | Grade |",
        "|--------|-------|-------|",
        `| **Precision** | ${formatPercent(metrics.precision)} | ${getGrade(metrics.precision)} |`,
        `| **Recall** | ${formatPercent(metrics.recall)} | ${getGrade(metrics.recall)} |`,
        `| **F1 Score** | ${formatPercent(metrics.f1Score)} | ${getGrade(metrics.f1Score)} |`,
        "",
        "### Counts",
        "",
        "| Metric | Count |",
        "|--------|-------|",
        `| Expected Findings | ${metrics.totalExpected} |`,
        `| Actual Findings | ${metrics.totalActual} |`,
        `| True Positives | ${metrics.totalTruePositives} |`,
        `| False Positives | ${metrics.totalFalsePositives} |`,
        `| False Negatives | ${metrics.totalFalseNegatives} |`,
        `| Fixtures Evaluated | ${metrics.totalFixtures} |`,
        `| Successful | ${metrics.successfulFixtures} |`,
        `| Failed | ${metrics.failedFixtures} |`,
    ];
    return lines.join("\n");
}
/**
 * Generate target comparison table
 */
function generateTargetComparison(metrics) {
    const { results } = meetsTargets(metrics);
    const lines = [
        "## Target Comparison",
        "",
        "| Metric | Target | Actual | Status |",
        "|--------|--------|--------|--------|",
    ];
    for (const [metric, data] of Object.entries(results)) {
        const icon = data.met ? "✅" : "❌";
        lines.push(`| ${capitalize(metric)} | ${formatPercent(data.target)} | ${formatPercent(data.actual)} | ${icon} |`);
    }
    return lines.join("\n");
}
/**
 * Generate category breakdown table
 */
function generateCategoryBreakdown(metrics) {
    const lines = [
        "## Performance by Category",
        "",
        "| Category | Expected | TP | FP | FN | Precision | Recall | F1 |",
        "|----------|----------|----|----|----|-----------|---------|----|",
    ];
    const categories = Object.entries(metrics.byCategory);
    // Sort by expected count descending
    categories.sort((a, b) => b[1].expected - a[1].expected);
    for (const [category, data] of categories) {
        lines.push(`| ${formatCategory(category)} | ${data.expected} | ${data.truePositives} | ${data.falsePositives} | ${data.falseNegatives} | ${formatPercent(data.precision)} | ${formatPercent(data.recall)} | ${formatPercent(data.f1Score)} |`);
    }
    return lines.join("\n");
}
/**
 * Generate scanner performance table
 */
function generateScannerPerformance(metrics) {
    const lines = [
        "## Performance by Scanner",
        "",
        "| Scanner | Findings | True Positives | False Positives | Precision |",
        "|---------|----------|----------------|-----------------|-----------|",
    ];
    const scanners = Object.entries(metrics.byScanner);
    // Sort by findings count descending
    scanners.sort((a, b) => b[1].findings - a[1].findings);
    for (const [scanner, data] of scanners) {
        lines.push(`| ${scanner} | ${data.findings} | ${data.truePositives} | ${data.falsePositives} | ${formatPercent(data.precision)} |`);
    }
    return lines.join("\n");
}
/**
 * Generate stability section
 */
function generateStabilitySection(stability) {
    const lines = [
        "## Stability Analysis",
        "",
        `**Runs:** ${stability.runs}`,
        `**Stability:** ${stability.stabilityPercent.toFixed(1)}%`,
        "",
        "| Metric | Count |",
        "|--------|-------|",
        `| Consistent Findings | ${stability.consistentFindings} |`,
        `| Inconsistent Findings | ${stability.inconsistentFindings} |`,
    ];
    // Add per-fixture stability if not too many
    const fixtureStabilities = Object.entries(stability.byFixture);
    if (fixtureStabilities.length > 0 && fixtureStabilities.length <= 20) {
        lines.push("", "### Per-Fixture Stability", "", "| Fixture | Stability |", "|---------|-----------|");
        fixtureStabilities.sort((a, b) => a[1] - b[1]); // Least stable first
        for (const [fixture, stab] of fixtureStabilities) {
            const icon = stab >= 0.95 ? "✅" : stab >= 0.8 ? "⚠️" : "❌";
            lines.push(`| ${fixture} | ${icon} ${(stab * 100).toFixed(1)}% |`);
        }
    }
    return lines.join("\n");
}
/**
 * Generate fixture summary
 */
function generateFixtureSummary(results) {
    const lines = [
        "## Fixture Summary",
        "",
        "<details>",
        "<summary>Click to expand fixture results</summary>",
        "",
        "| Fixture | Status | TP | FP | FN | Duration |",
        "|---------|--------|----|----|----|---------:|",
    ];
    for (const result of results) {
        const status = result.success ? "✅" : "❌";
        const duration = `${(result.duration / 1000).toFixed(2)}s`;
        lines.push(`| ${result.fixtureId} | ${status} | ${result.truePositives.length} | ${result.falsePositives.length} | ${result.falseNegatives.length} | ${duration} |`);
    }
    lines.push("", "</details>");
    return lines.join("\n");
}
/**
 * Generate error analysis section
 */
function generateErrorAnalysis(results) {
    const allFalsePositives = [];
    const allFalseNegatives = [];
    for (const result of results) {
        for (const fp of result.falsePositives) {
            allFalsePositives.push({
                fixture: result.fixtureId,
                finding: `${fp.file}:${fp.line} - ${fp.ruleId}`,
            });
        }
        for (const fn of result.falseNegatives) {
            allFalseNegatives.push({
                fixture: result.fixtureId,
                finding: `${fn.file}:${fn.line} - ${fn.description}`,
            });
        }
    }
    const lines = ["## Error Analysis"];
    // False Positives
    lines.push("", `### False Positives (${allFalsePositives.length})`, "");
    if (allFalsePositives.length === 0) {
        lines.push("_No false positives detected._");
    }
    else {
        lines.push("<details>", "<summary>Click to expand false positives</summary>", "", "| Fixture | Finding |", "|---------|---------|");
        for (const { fixture, finding } of allFalsePositives.slice(0, 50)) {
            lines.push(`| ${fixture} | \`${finding}\` |`);
        }
        if (allFalsePositives.length > 50) {
            lines.push(`| ... | _${allFalsePositives.length - 50} more_ |`);
        }
        lines.push("", "</details>");
    }
    // False Negatives
    lines.push("", `### False Negatives (${allFalseNegatives.length})`, "");
    if (allFalseNegatives.length === 0) {
        lines.push("_No false negatives detected._");
    }
    else {
        lines.push("<details>", "<summary>Click to expand false negatives</summary>", "", "| Fixture | Expected |", "|---------|----------|");
        for (const { fixture, finding } of allFalseNegatives.slice(0, 50)) {
            lines.push(`| ${fixture} | \`${finding}\` |`);
        }
        if (allFalseNegatives.length > 50) {
            lines.push(`| ... | _${allFalseNegatives.length - 50} more_ |`);
        }
        lines.push("", "</details>");
    }
    return lines.join("\n");
}
/**
 * Generate footer
 */
function generateFooter(report) {
    const duration = (report.metrics.totalDuration / 1000).toFixed(1);
    return `---

_Report generated by [Vaspera Hardening](https://github.com/RCOLKITT/hardening-mcp) in ${duration}s_

**Scanners:** ${report.config.scanners.join(", ")}`;
}
/**
 * Format category name for display
 */
function formatCategory(category) {
    return category
        .split("-")
        .map(capitalize)
        .join(" ");
}
/**
 * Capitalize first letter
 */
function capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
 * Generate a compact summary for CI output
 */
export function generateCompactSummary(metrics) {
    const { meetsAll } = meetsTargets(metrics);
    const status = meetsAll ? "PASS" : "FAIL";
    return [
        `Vaspera Eval: ${status}`,
        `Precision: ${formatPercent(metrics.precision)} (${getGrade(metrics.precision)})`,
        `Recall: ${formatPercent(metrics.recall)} (${getGrade(metrics.recall)})`,
        `F1: ${formatPercent(metrics.f1Score)} (${getGrade(metrics.f1Score)})`,
        `TP: ${metrics.totalTruePositives}, FP: ${metrics.totalFalsePositives}, FN: ${metrics.totalFalseNegatives}`,
    ].join(" | ");
}
/**
 * Generate JSON report for programmatic consumption
 */
export function generateJsonReport(report) {
    return JSON.stringify(report, null, 2);
}
/**
 * Generate badges for README
 */
export function generateReadmeBadges(metrics) {
    const precisionColor = getColorForScore(metrics.precision);
    const recallColor = getColorForScore(metrics.recall);
    const f1Color = getColorForScore(metrics.f1Score);
    return [
        `![Precision](https://img.shields.io/badge/Precision-${encodeURIComponent(formatPercent(metrics.precision))}-${precisionColor})`,
        `![Recall](https://img.shields.io/badge/Recall-${encodeURIComponent(formatPercent(metrics.recall))}-${recallColor})`,
        `![F1 Score](https://img.shields.io/badge/F1_Score-${encodeURIComponent(formatPercent(metrics.f1Score))}-${f1Color})`,
    ].join(" ");
}
/**
 * Get badge color based on score
 */
function getColorForScore(score) {
    if (score >= 0.9)
        return "brightgreen";
    if (score >= 0.8)
        return "green";
    if (score >= 0.7)
        return "yellow";
    if (score >= 0.6)
        return "orange";
    return "red";
}
//# sourceMappingURL=report.js.map