/**
 * pipeline/targeted-loo.ts
 *
 * Targeted leave-one-out (LOO) attribution for ambiguous cases.
 *
 * Phase 4d of the Scenario Matrix implementation.
 *
 * When correlation-based attribution (Phase 2c) identifies ambiguous tasks
 * (2+ changed docs map to the same task), targeted LOO resolves the ambiguity
 * by running per-document mini-evaluations to measure each document's
 * marginal contribution.
 *
 * This module handles:
 * - Cost estimation before execution (to enable user confirmation)
 * - Result analysis after LOO runs complete
 * - Integration with the existing AttributionReport
 *
 * The actual evaluation execution is handled by the pipeline orchestrator —
 * this module is pure computation on inputs and outputs.
 *
 * @see docs/archive/exec-plans/scenario-matrix-implementation/phase-4-content-release-integration.md
 * @see docs/design-docs/scenario-matrix/per-document-attribution.md
 */
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/** Default estimated cost per test (provider + grader combined) */
const DEFAULT_COST_PER_TEST = 0.08;
/** Sum tolerance — marginal contributions should sum to ~total delta (±15%) */
const SUM_TOLERANCE = 0.15;
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Analyze LOO evaluation results to compute marginal contributions.
 *
 * Given the full-release score and per-document revert scores,
 * calculates each document's marginal contribution as:
 *   marginal(doc) = fullReleaseScore - revertedScore(doc)
 *
 * @param taskId - The task being analyzed
 * @param fullReleaseDelta - The total task delta from the full release
 * @param revertResults - Per-document scores when that document is reverted
 * @param noiseThreshold - Threshold for marking contributions as noise
 * @param additionalCost - Actual cost incurred for the LOO evaluations
 * @returns LOO result with per-document marginal contributions
 */
export function analyzeLOOResults(taskId, fullReleaseDelta, revertResults, noiseThreshold, additionalCost) {
    const contributions = revertResults.map(({ revertedDelta, slug }) => {
        // Marginal contribution = full delta - delta when this doc is reverted
        const marginalContribution = fullReleaseDelta - revertedDelta;
        return {
            marginalContribution: Math.round(marginalContribution * 10) / 10,
            slug,
            withinNoiseFloor: Math.abs(marginalContribution) <= noiseThreshold,
        };
    });
    // Check if contributions sum to approximately the total delta
    const contributionSum = contributions.reduce((sum, c) => sum + c.marginalContribution, 0);
    const sumMatchesTotal = fullReleaseDelta === 0 ||
        Math.abs(contributionSum - fullReleaseDelta) / Math.abs(fullReleaseDelta) <=
            SUM_TOLERANCE;
    return {
        additionalCost,
        contributions: contributions.sort((a, b) => Math.abs(b.marginalContribution) - Math.abs(a.marginalContribution)),
        sumMatchesTotal,
        taskId,
    };
}
/**
 * Enrich an attribution report with LOO results for ambiguous tasks.
 *
 * Replaces the "ambiguous" classification with resolved per-document
 * contributions for tasks that have LOO data.
 *
 * @param attribution - Original attribution report
 * @param looResults - LOO results for ambiguous tasks
 * @returns New attribution report with LOO data integrated
 */
export function enrichAttributionWithLOO(attribution, looResults) {
    const looByTask = new Map();
    for (const result of looResults) {
        looByTask.set(result.taskId, result);
    }
    return {
        ...attribution,
        looResults,
    };
}
/**
 * Estimate the cost of running targeted LOO for ambiguous tasks.
 *
 * @param ambiguousTasks - Tasks identified as ambiguous
 * @param testsPerTask - Number of tests per task (from task YAML)
 * @param costPerTest - Estimated cost per test (default: $0.08)
 * @returns Cost estimate with per-task breakdown
 */
export function estimateLOOCost(ambiguousTasks, testsPerTask, costPerTest = DEFAULT_COST_PER_TEST) {
    const perTask = [];
    for (const task of ambiguousTasks) {
        const numDocuments = task.attributedDocs.length;
        const numTests = testsPerTask[task.taskId] ?? 6; // Default estimate
        const estimatedCost = numDocuments * numTests * costPerTest;
        perTask.push({
            estimatedCost: Math.round(estimatedCost * 100) / 100,
            numDocuments,
            numTests,
            taskId: task.taskId,
        });
    }
    const totalEstimatedCost = perTask.reduce((sum, t) => sum + t.estimatedCost, 0);
    return {
        perTask,
        totalEstimatedCost: Math.round(totalEstimatedCost * 100) / 100,
    };
}
/**
 * Identify ambiguous tasks that would benefit from targeted LOO.
 *
 * Filters the attribution report to find tasks where 2+ changed documents
 * are in the canonical set and the delta is outside the noise floor.
 *
 * @param attribution - Attribution report from Phase 2c
 * @returns Ambiguous task attributions suitable for LOO
 */
export function findAmbiguousTasks(attribution) {
    return attribution.attributions.filter((a) => a.classification === "ambiguous" && !a.withinNoiseFloor);
}
// ---------------------------------------------------------------------------
// Formatting
// ---------------------------------------------------------------------------
/**
 * Format a LOO cost estimate for console output (for user confirmation).
 */
export function formatLOOCostEstimate(estimate) {
    const lines = [];
    lines.push("💰 TARGETED LOO COST ESTIMATE");
    lines.push("");
    lines.push(`  Targeted LOO for ${estimate.perTask.length} ambiguous task(s):`);
    lines.push("");
    for (const task of estimate.perTask) {
        lines.push(`    ${task.taskId}: ${task.numDocuments} documents × ~${task.numTests} tests × $${DEFAULT_COST_PER_TEST}/test = ~$${task.estimatedCost.toFixed(2)}`);
    }
    lines.push("");
    lines.push(`  Total additional cost: ~$${estimate.totalEstimatedCost.toFixed(2)}`);
    lines.push("");
    return lines.join("\n");
}
/**
 * Format LOO results for console output.
 */
export function formatLOOResultsConsole(results) {
    const lines = [];
    lines.push("🔬 TARGETED LOO RESULTS");
    lines.push("");
    for (const result of results) {
        lines.push(`  ${result.taskId}:`);
        for (const c of result.contributions) {
            const sign = c.marginalContribution >= 0 ? "+" : "";
            const noise = c.withinNoiseFloor ? " (within noise)" : "";
            lines.push(`    ${c.slug}: ${sign}${c.marginalContribution.toFixed(1)}${noise}`);
        }
        if (!result.sumMatchesTotal) {
            lines.push("    ⚠️  Contributions don't sum to total delta (interaction effects likely)");
        }
        lines.push(`    Additional cost: $${result.additionalCost.toFixed(2)}`);
        lines.push("");
    }
    return lines.join("\n");
}
/**
 * Format LOO results as markdown.
 */
export function formatLOOResultsMarkdown(results) {
    const lines = [];
    lines.push("#### 🔬 Targeted LOO Attribution");
    lines.push("");
    if (results.length === 0) {
        lines.push("No ambiguous tasks required LOO resolution.");
        lines.push("");
        return lines.join("\n");
    }
    for (const result of results) {
        lines.push(`**${result.taskId}:**`);
        lines.push("");
        for (const c of result.contributions) {
            const sign = c.marginalContribution >= 0 ? "+" : "";
            const pct = result.contributions.length > 0
                ? Math.round((Math.abs(c.marginalContribution) /
                    result.contributions.reduce((s, cc) => s + Math.abs(cc.marginalContribution), 0)) *
                    100)
                : 0;
            const noise = c.withinNoiseFloor ? " ⚠️" : "";
            lines.push(`- \`${c.slug}\`: ${sign}${c.marginalContribution.toFixed(1)} (${pct}%)${noise}`);
        }
        if (!result.sumMatchesTotal) {
            lines.push("> ⚠️ Marginal contributions don't sum to total delta due to interaction effects.");
        }
        lines.push(`- Additional cost: $${result.additionalCost.toFixed(2)}`);
        lines.push("");
    }
    return lines.join("\n");
}
