/**
 * pipeline/thresholds.ts
 *
 * Threshold evaluation engine — compares a ScoreSummary against configurable
 * quality thresholds and produces a set of typed violations.
 *
 * Pure functions only: no I/O, no side effects. The caller (pipeline.ts,
 * publish-report-step.ts) is responsible for loading the threshold config
 * and score summary.
 *
 * @see docs/archive/exec-plans/scenario-matrix-implementation/phase-5-readiness-thresholds.md
 */
// ---------------------------------------------------------------------------
// Severity priority for sorting (higher = more severe)
// ---------------------------------------------------------------------------
const SEVERITY_RANK = {
    critical: 3,
    info: 1,
    warning: 2,
};
/**
 * Evaluate regression thresholds against a comparison report.
 *
 * Returns violations for regressions that exceed configured thresholds.
 */
export function evaluateRegressionThresholds(comparison, config) {
    const regression = config.regression;
    if (!regression)
        return [];
    const violations = [];
    // Overall composite regression
    if (comparison.deltas.overall < regression.composite) {
        violations.push({
            actual: comparison.deltas.overall,
            area: "overall",
            description: `Overall score dropped by ${fmt(Math.abs(comparison.deltas.overall))} (threshold: ${regression.composite})`,
            metric: "regression-composite",
            severity: classifyRegressionSeverity(comparison.deltas.overall, config),
            threshold: regression.composite,
        });
    }
    // Per-area regressions
    for (const areaDelta of comparison.areas) {
        if (areaDelta.delta < regression["per-area"]) {
            violations.push({
                actual: areaDelta.delta,
                area: areaDelta.area,
                description: `${areaDelta.area} dropped by ${fmt(Math.abs(areaDelta.delta))} (threshold: ${regression["per-area"]})`,
                metric: "regression-area",
                severity: classifyRegressionSeverity(areaDelta.delta, config),
                threshold: regression["per-area"],
            });
        }
        // Per-dimension regressions
        const dims = areaDelta.dimensions;
        for (const [dimKey, dimData] of Object.entries(dims)) {
            if (dimData.delta < regression["per-dimension"]) {
                violations.push({
                    actual: dimData.delta,
                    area: areaDelta.area,
                    description: `${areaDelta.area} ${dimKey} dropped by ${fmt(Math.abs(dimData.delta))} (threshold: ${regression["per-dimension"]})`,
                    metric: `regression-${dimKey}`,
                    severity: "warning",
                    threshold: regression["per-dimension"],
                });
            }
        }
    }
    // Sort by severity then magnitude
    violations.sort((a, b) => {
        const sevDiff = SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity];
        if (sevDiff !== 0)
            return sevDiff;
        return a.actual - b.actual;
    });
    return violations;
}
// ---------------------------------------------------------------------------
// Per-area evaluation
// ---------------------------------------------------------------------------
/**
 * Evaluate a score summary against threshold configuration.
 *
 * Returns all violations sorted by severity (critical first), then by
 * the magnitude of the threshold breach (largest gap first).
 */
export function evaluateThresholds(scores, config) {
    const violations = [];
    // Evaluate overall composite score against defaults
    const overallScore = scores.overall.avgScore;
    if (overallScore < config.defaults.composite) {
        violations.push({
            actual: overallScore,
            area: "overall",
            description: `Overall composite score ${fmt(overallScore)} is below threshold ${config.defaults.composite}`,
            metric: "composite",
            severity: classifySeverity(overallScore, config),
            threshold: config.defaults.composite,
        });
    }
    // Evaluate per-area scores
    for (const areaScore of scores.scores) {
        const areaOverrides = config.areas?.[areaScore.feature];
        const merged = mergeDefaults(config.defaults, areaOverrides);
        evaluateArea(areaScore, merged, config, violations);
    }
    // Sort: critical first, then by delta magnitude (largest breach first)
    violations.sort((a, b) => {
        const sevDiff = SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity];
        if (sevDiff !== 0)
            return sevDiff;
        // Larger breach = more negative (actual - threshold)
        return a.actual - a.threshold - (b.actual - b.threshold);
    });
    const maxSeverity = violations.length > 0 ? violations[0].severity : "none";
    return {
        maxSeverity,
        pass: violations.length === 0,
        violations,
    };
}
/**
 * Classify severity for a regression delta using the severity config.
 */
function classifyRegressionSeverity(delta, config) {
    const sev = config.severity;
    if (!sev)
        return "warning";
    if (sev.warning?.["regression-exceeds"] !== undefined &&
        delta < sev.warning["regression-exceeds"]) {
        return "warning";
    }
    return "info";
}
// ---------------------------------------------------------------------------
// Severity classification
// ---------------------------------------------------------------------------
/**
 * Classify the severity of a score-based violation using the severity config.
 * Checks from most severe to least: critical → warning → info.
 */
function classifySeverity(score, config) {
    const sev = config.severity;
    if (!sev)
        return "warning";
    if (sev.critical?.["composite-below"] !== undefined &&
        score < sev.critical["composite-below"]) {
        return "critical";
    }
    if (sev.warning?.["composite-below"] !== undefined &&
        score < sev.warning["composite-below"]) {
        return "warning";
    }
    if (sev.info?.["composite-below"] !== undefined &&
        score < sev.info["composite-below"]) {
        return "info";
    }
    return "warning";
}
function evaluateArea(score, thresholds, config, violations) {
    const area = score.feature;
    // Composite score
    if (score.totalScore < thresholds.composite) {
        violations.push({
            actual: score.totalScore,
            area,
            description: `${area} composite score ${fmt(score.totalScore)} is below threshold ${thresholds.composite}`,
            metric: "composite",
            severity: classifySeverity(score.totalScore, config),
            threshold: thresholds.composite,
        });
    }
    // Per-dimension thresholds
    const dims = thresholds.dimensions;
    if (dims) {
        const dimMap = [
            ["task-completion", score.taskCompletion, dims["task-completion"]],
            ["code-correctness", score.codeCorrectness, dims["code-correctness"]],
            ["doc-coverage", score.docCoverage, dims["doc-coverage"]],
        ];
        for (const [dimKey, actual, threshold] of dimMap) {
            if (threshold !== undefined && actual < threshold) {
                violations.push({
                    actual,
                    area,
                    description: `${area} ${dimKey} score ${fmt(actual)} is below threshold ${threshold}`,
                    metric: dimKey,
                    severity: classifySeverity(actual, config),
                    threshold,
                });
            }
        }
    }
    // Doc Lift threshold (docs must not hurt)
    const docLiftThreshold = thresholds["doc-lift"];
    if (docLiftThreshold !== undefined && score.docLift < docLiftThreshold) {
        const severity = score.negativeDocLift && config.severity?.critical?.["negative-doc-lift"]
            ? "critical"
            : "warning";
        violations.push({
            actual: score.docLift,
            area,
            description: `${area} Doc Lift ${fmt(score.docLift)} is below threshold ${docLiftThreshold}${score.negativeDocLift ? " — docs are hurting performance" : ""}`,
            metric: "doc-lift",
            severity,
            threshold: docLiftThreshold,
        });
    }
    // Ceiling threshold (doc quality floor)
    const ceilingThreshold = thresholds.ceiling;
    if (ceilingThreshold !== undefined && score.ceilingScore < ceilingThreshold) {
        violations.push({
            actual: score.ceilingScore,
            area,
            description: `${area} ceiling score ${fmt(score.ceilingScore)} is below threshold ${ceilingThreshold} — documentation quality is insufficient`,
            metric: "ceiling",
            severity: "warning",
            threshold: ceilingThreshold,
        });
    }
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/** Format a score for display (round to nearest integer) */
function fmt(n) {
    return String(Math.round(n));
}
/**
 * Merge per-area overrides with defaults. Area overrides take precedence.
 */
function mergeDefaults(defaults, overrides) {
    if (!overrides) {
        return { ...defaults };
    }
    return {
        ceiling: overrides.ceiling ?? defaults.ceiling,
        composite: overrides.composite ?? defaults.composite,
        dimensions: {
            ...defaults.dimensions,
            ...overrides.dimensions,
        },
        "doc-lift": overrides["doc-lift"] ?? defaults["doc-lift"],
    };
}
