/**
 * pipeline/repo-threshold-evaluator.ts
 *
 * Evaluates per-task scores against thresholds configured in repo task
 * definitions (`.ailf/tasks/*.yaml` → `execution.threshold`).
 *
 * This is distinct from the readiness-gate threshold system in
 * `config/thresholds`. Repo thresholds are per-task, defined by
 * the product team, and drive PR check pass/fail status. Framework
 * thresholds are per-area, defined by the AILF team, and drive
 * readiness reports.
 *
 * @see docs/archive/exec-plans/tasks-as-content/phase-6-pr-quality-gates.md
 * @see packages/eval/src/adapters/task-sources/repo-schemas.ts
 */
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/** Framework default threshold when neither task nor config specifies one */
const FRAMEWORK_DEFAULT_THRESHOLD = 0;
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Evaluate task scores against repo-configured thresholds.
 *
 * For each task with a threshold configured in its `execution` block,
 * compare the actual score against the threshold and determine pass/fail.
 *
 * Tasks without explicit thresholds use the `configDefaultThreshold`
 * (from `.ailf/config.yaml`) or the framework default (0 = no gate).
 *
 * @param scoreSummary - The evaluation score summary
 * @param thresholds - Per-task threshold configs (from repo task definitions)
 * @param configDefaultThreshold - Default threshold from .ailf/config.yaml
 */
export function evaluateRepoThresholds(scoreSummary, thresholds, configDefaultThreshold = FRAMEWORK_DEFAULT_THRESHOLD) {
    const thresholdMap = new Map(thresholds.map((t) => [t.taskId, t]));
    // Build a task-to-area mapping from the score summary
    const taskScoreMap = buildTaskScoreMap(scoreSummary);
    const results = [];
    // Evaluate each task that has scores
    for (const [taskId, taskScore] of taskScoreMap) {
        const config = thresholdMap.get(taskId);
        const threshold = config?.score ?? configDefaultThreshold;
        const blocking = config?.blocking ?? false;
        const actualScore = Math.round(taskScore.score);
        const passed = actualScore >= threshold;
        // Evaluate dimension thresholds
        let dimensionResults;
        if (config?.dimensions) {
            dimensionResults = evaluateDimensions(taskScore, config.dimensions);
        }
        const dimensionsFailed = dimensionResults?.some((d) => !d.passed) ?? false;
        const overallPassed = passed && !dimensionsFailed;
        results.push({
            taskId,
            area: taskScore.area,
            actualScore,
            threshold,
            passed: overallPassed,
            blocking,
            dimensionResults,
            status: overallPassed ? "passed" : blocking ? "failed" : "warning",
        });
    }
    // Also add entries for threshold-configured tasks that weren't evaluated
    for (const config of thresholds) {
        if (!taskScoreMap.has(config.taskId)) {
            results.push({
                taskId: config.taskId,
                area: "unknown",
                actualScore: 0,
                threshold: config.score ?? configDefaultThreshold,
                passed: false,
                blocking: config.blocking,
                status: config.blocking ? "failed" : "warning",
            });
        }
    }
    // Sort: failed first, then warnings, then passed
    results.sort((a, b) => {
        const order = { failed: 0, warning: 1, passed: 2 };
        return order[a.status] - order[b.status];
    });
    const passed = results.filter((r) => r.status === "passed").length;
    const warnings = results.filter((r) => r.status === "warning").length;
    const failed = results.filter((r) => r.status === "failed").length;
    // Check passes if no blocking failures
    const checkPassed = failed === 0;
    // Overall score
    const evaluatedResults = results.filter((r) => r.actualScore > 0);
    const overallScore = evaluatedResults.length > 0
        ? Math.round(evaluatedResults.reduce((sum, r) => sum + r.actualScore, 0) /
            evaluatedResults.length)
        : 0;
    return {
        checkPassed,
        results,
        summary: {
            total: results.length,
            passed,
            warnings,
            failed,
        },
        overallScore,
        defaultThreshold: configDefaultThreshold,
    };
}
/**
 * Build a map of task ID → score from the ScoreSummary.
 *
 * ScoreSummary groups scores by feature area, not by task. We derive
 * per-task scores from the area scores. When multiple tasks share an
 * area, they share the area's composite score (this is a simplification;
 * per-task scoring requires individual test results not available in
 * ScoreSummary).
 */
function buildTaskScoreMap(summary) {
    const map = new Map();
    for (const areaScore of summary.scores) {
        // Use the feature area name as a proxy task ID if we don't have
        // per-task granularity. In practice, repo tasks map 1:1 to areas
        // in most cases (each .ailf/tasks/*.yaml file is one area).
        map.set(areaScore.feature, {
            area: areaScore.feature,
            score: areaScore.totalScore,
            taskCompletion: areaScore.taskCompletion,
            codeCorrectness: areaScore.codeCorrectness,
            docCoverage: areaScore.docCoverage,
        });
    }
    return map;
}
function evaluateDimensions(score, thresholds) {
    const results = [];
    if (thresholds.taskCompletion !== undefined) {
        results.push({
            dimension: "task-completion",
            actual: Math.round(score.taskCompletion),
            threshold: thresholds.taskCompletion,
            passed: Math.round(score.taskCompletion) >= thresholds.taskCompletion,
        });
    }
    if (thresholds.codeCorrectness !== undefined) {
        results.push({
            dimension: "code-correctness",
            actual: Math.round(score.codeCorrectness),
            threshold: thresholds.codeCorrectness,
            passed: Math.round(score.codeCorrectness) >= thresholds.codeCorrectness,
        });
    }
    if (thresholds.docCoverage !== undefined) {
        results.push({
            dimension: "doc-coverage",
            actual: Math.round(score.docCoverage),
            threshold: thresholds.docCoverage,
            passed: Math.round(score.docCoverage) >= thresholds.docCoverage,
        });
    }
    return results;
}
