/**
 * core/services/comparison-formatters.ts — Pure formatting functions for
 * comparison reports.
 *
 * No I/O — takes a ComparisonReport, returns a string.
 *
 * Extracted from packages/eval/src/lib/compare.ts during the
 * Ports & Adapters migration (Phase 4e).
 */
// ---------------------------------------------------------------------------
// Markdown format (for PR comments)
// ---------------------------------------------------------------------------
/**
 * Generate a markdown comparison section suitable for PR comments.
 */
export function formatComparisonMarkdown(report) {
    const lines = [];
    const overall = report.deltas.overall;
    const overallIcon = changeIcon(overall > report.noiseThreshold
        ? "improved"
        : overall < -report.noiseThreshold
            ? "regressed"
            : "unchanged");
    lines.push("### 📊 Score Comparison");
    lines.push("");
    lines.push(`**Overall: ${Math.round(report.baseline.overall.avgScore)} → ${Math.round(report.experiment.overall.avgScore)}** (${overallIcon} ${deltaStr(overall)})`);
    lines.push("");
    // Derive dimension columns from the first area's keys (all areas share the
    // same scoring profile, so the key set is uniform).
    const dimKeys = report.areas.length > 0
        ? Object.keys(report.areas[0].dimensions)
        : Object.keys(report.deltas.perDimension);
    // Per-area table — columns are dynamic
    const dimHeaders = dimKeys.map(kebabToTitleCase);
    const headerRow = ["Feature", "Baseline", "Current", "Delta", ...dimHeaders];
    const separatorRow = headerRow.map(() => "------");
    lines.push(`| ${headerRow.join(" | ")} |`);
    lines.push(`|${separatorRow.join("|")}|`);
    for (const a of report.areas) {
        const icon = changeIcon(a.change);
        const dimCells = dimKeys.map((k) => deltaStr(a.dimensions[k]?.delta ?? 0));
        lines.push(`| ${a.area} | ${a.baseline} | ${a.experiment} | ${a.delta !== undefined ? `${icon} ${deltaStr(a.delta)}` : "—"} | ${dimCells.join(" | ")} |`);
    }
    lines.push("");
    // Summary
    const parts = [];
    if (report.improved.length > 0) {
        parts.push(`📈 ${report.improved.length} improved`);
    }
    if (report.regressed.length > 0) {
        parts.push(`📉 ${report.regressed.length} regressed`);
    }
    if (report.unchanged.length > 0) {
        parts.push(`➡️ ${report.unchanged.length} unchanged`);
    }
    if (parts.length > 0) {
        lines.push(parts.join(" · "));
        lines.push("");
    }
    // Dimension averages in collapsible
    lines.push("<details>");
    lines.push("<summary>Dimension averages</summary>");
    lines.push("");
    const dim = report.deltas.perDimension;
    lines.push("| Dimension | Delta |");
    lines.push("|-----------|-------|");
    for (const k of Object.keys(dim)) {
        lines.push(`| ${kebabToTitleCase(k)} | ${deltaStr(dim[k])} |`);
    }
    lines.push(`| Doc Lift | ${deltaStr(report.deltas.docLift)} |`);
    if (report.deltas.cost !== undefined) {
        const costStr = report.deltas.cost > 0
            ? `+$${report.deltas.cost.toFixed(4)}`
            : `-$${Math.abs(report.deltas.cost).toFixed(4)}`;
        lines.push(`| Cost | ${costStr} |`);
    }
    lines.push("");
    lines.push("</details>");
    lines.push("");
    return lines.join("\n");
}
// ---------------------------------------------------------------------------
// Console table format
// ---------------------------------------------------------------------------
/**
 * Generate a human-readable console comparison table.
 */
export function formatComparisonTable(report) {
    const lines = [];
    lines.push("=".repeat(80));
    lines.push("                    COMPARISON REPORT");
    lines.push("=".repeat(80));
    lines.push("");
    // Overall summary
    const overall = report.deltas.overall;
    const overallIcon = changeIcon(overall > report.noiseThreshold
        ? "improved"
        : overall < -report.noiseThreshold
            ? "regressed"
            : "unchanged");
    lines.push(`  Overall: ${Math.round(report.baseline.overall.avgScore)} → ${Math.round(report.experiment.overall.avgScore)} (${overallIcon} ${deltaStr(overall)})`);
    lines.push("");
    // Per-dimension averages — derived dynamically from the report
    const dim = report.deltas.perDimension;
    const dimKeys = report.areas.length > 0
        ? Object.keys(report.areas[0].dimensions)
        : Object.keys(dim);
    lines.push("  Dimension averages:");
    // Pad labels to the longest dimension label for alignment
    const dimLabels = dimKeys.map(kebabToTitleCase);
    // +1 for the colon appended to each label
    const maxLabelLen = Math.max(...dimLabels.map((l) => l.length + 1), "Doc Lift:".length);
    for (let i = 0; i < dimKeys.length; i++) {
        lines.push(`    ${(dimLabels[i] + ":").padEnd(maxLabelLen)} ${deltaStr(dim[dimKeys[i]] ?? 0)}`);
    }
    lines.push(`    ${"Doc Lift:".padEnd(maxLabelLen)} ${deltaStr(report.deltas.docLift)}`);
    if (report.deltas.cost !== undefined) {
        lines.push(`    ${"Cost:".padEnd(maxLabelLen)} ${report.deltas.cost > 0 ? "+" : ""}$${report.deltas.cost.toFixed(4)}`);
    }
    lines.push("");
    // Per-area table — columns are dynamic
    lines.push("-".repeat(80));
    lines.push("PER-AREA BREAKDOWN");
    lines.push("-".repeat(80));
    lines.push("");
    const dimHeaders = dimKeys.map(kebabToTitleCase);
    const colWidths = dimHeaders.map((h) => Math.max(h.length, 4));
    const hCols = [
        "Feature Area".padEnd(19),
        "Baseline".padStart(8),
        "Experiment".padStart(10),
        "Delta".padStart(5),
        ...dimHeaders.map((h, i) => h.padStart(colWidths[i])),
    ];
    const sepCols = [
        "-".repeat(21),
        "-".repeat(10),
        "-".repeat(12),
        "-".repeat(7),
        ...colWidths.map((w) => "-".repeat(w + 2)),
    ];
    lines.push(`| ${hCols.join(" | ")} |`);
    lines.push(`|${sepCols.join("|")}|`);
    for (const a of report.areas) {
        const icon = changeIcon(a.change);
        const dimCells = dimKeys.map((k, i) => deltaStr(a.dimensions[k]?.delta ?? 0).padStart(colWidths[i]));
        lines.push(`| ${icon} ${a.area.padEnd(17)} | ${String(a.baseline).padStart(8)} | ${String(a.experiment).padStart(10)} | ${(a.delta !== undefined ? deltaStr(a.delta) : "—").padStart(5)} | ${dimCells.join(" | ")} |`);
    }
    lines.push("");
    // Classification summary
    if (report.improved.length > 0) {
        lines.push(`  📈 Improved:   ${report.improved.join(", ")}`);
    }
    if (report.regressed.length > 0) {
        lines.push(`  📉 Regressed:  ${report.regressed.join(", ")}`);
    }
    if (report.unchanged.length > 0) {
        lines.push(`  ➡️  Unchanged:  ${report.unchanged.join(", ")}`);
    }
    lines.push("");
    // Mismatched areas
    if (report.mismatched.onlyInBaseline.length > 0 ||
        report.mismatched.onlyInExperiment.length > 0) {
        lines.push("  ⚠️  Area mismatches:");
        if (report.mismatched.onlyInBaseline.length > 0) {
            lines.push(`    Only in baseline:   ${report.mismatched.onlyInBaseline.join(", ")}`);
        }
        if (report.mismatched.onlyInExperiment.length > 0) {
            lines.push(`    Only in experiment: ${report.mismatched.onlyInExperiment.join(", ")}`);
        }
        lines.push("");
    }
    const isEmpirical = "noiseThresholdEmpirical" in report &&
        report.noiseThresholdEmpirical === true;
    const thresholdSource = isEmpirical
        ? "empirical, from grader consistency data"
        : "default";
    lines.push(`  Noise threshold: ±${report.noiseThreshold}${Number.isInteger(report.noiseThreshold) ? "" : ` (${report.noiseThreshold.toFixed(1)})`} (${thresholdSource})`);
    lines.push("");
    // Ceiling decomposition deltas (when areas have ceiling data)
    const hasCeilingData = report.areas.some((a) => a.ceilingDelta !== undefined);
    if (hasCeilingData) {
        lines.push("-".repeat(80));
        lines.push("CEILING DECOMPOSITION DELTAS");
        lines.push("-".repeat(80));
        lines.push("");
        const cH = "| Feature Area        | Ceiling Δ | Floor Δ | Doc Lift Δ |";
        const cSep = "|---------------------|-----------|---------|------------|";
        lines.push(cH);
        lines.push(cSep);
        for (const a of report.areas) {
            lines.push(`| ${a.area.padEnd(19)} | ` +
                `${deltaStr(a.ceilingDelta).padStart(9)} | ` +
                `${deltaStr(a.floorDelta).padStart(7)} | ` +
                `${deltaStr(a.docLiftDelta).padStart(10)} |`);
        }
        lines.push("");
    }
    return lines.join("\n");
}
// ---------------------------------------------------------------------------
// Helpers (private)
// ---------------------------------------------------------------------------
function changeIcon(change) {
    switch (change) {
        case "improved":
            return "📈";
        case "regressed":
            return "📉";
        default:
            return "➡️";
    }
}
function deltaStr(d) {
    if (d > 0)
        return `+${Math.round(d)}`;
    if (d < 0)
        return `${Math.round(d)}`;
    return "0";
}
/** Convert kebab-case dimension name to title case (e.g. 'task-completion' → 'Task Completion') */
function kebabToTitleCase(name) {
    return name
        .split("-")
        .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
        .join(" ");
}
