import { judgeMetric } from "../judge.js";
export async function executeMetrics(input) {
    const metrics = input.evalDef.metrics ?? [];
    const judge = input.judge ?? judgeMetric;
    const results = [];
    for (const metric of metrics) {
        if (!metric.enabled) {
            results.push(createUnavailableResult(metric, "disabled", "Metric is disabled."));
            continue;
        }
        if (metric.evaluator.kind === "judge" && input.judgeEnabled === false) {
            results.push(createUnavailableResult(metric, "disabled", "Judge-backed metrics are disabled."));
            continue;
        }
        if (metric.evaluator.kind === "judge" && input.judgeSkipReason !== undefined) {
            results.push(createUnavailableResult(metric, "skipped", input.judgeSkipReason));
            continue;
        }
        try {
            const score = metric.evaluator.kind === "judge"
                ? await judge({
                    evalDef: input.evalDef,
                    metric,
                    cloneDir: input.cloneDir,
                    traceJsonPath: input.traceJsonPath,
                    trace: input.trace,
                    oracleOutcome: input.oracleOutcome,
                    agentUnderTest: input.agentUnderTest
                })
                : scoreDeterministicMetric(metric, input.trace, input.oracleOutcome, input.evalDef.budget.maxIterations);
            results.push(createExecutedResult(metric, score));
        }
        catch (error) {
            results.push(createUnavailableResult(metric, "failed", formatUnknownError(error)));
        }
    }
    return results;
}
function scoreDeterministicMetric(metric, trace, oracleOutcome, maxIterations) {
    switch (metric.id) {
        case "task_completion": {
            const score = oracleOutcome.total === 0 ? 0 : oracleOutcome.passed / oracleOutcome.total;
            return {
                score,
                reason: `${oracleOutcome.passed}/${oracleOutcome.total} deterministic oracle tests passed.`
            };
        }
        case "tool_correctness": {
            const completed = terminalToolEvents(trace);
            const successful = completed.filter((event) => event.outcome === "completed").length;
            const score = completed.length === 0 ? 1 : successful / completed.length;
            return {
                score,
                reason: completed.length === 0
                    ? "No completed tool calls required correctness scoring."
                    : `${successful}/${completed.length} completed tool calls succeeded.`,
                traceReferences: completed.map((event) => event.sequence)
            };
        }
        case "step_efficiency": {
            const steps = terminalToolEvents(trace);
            const maxSteps = readPositiveNumber(metric.evaluator.config, "max_steps") ?? maxIterations;
            const score = steps.length === 0 ? 1 : Math.min(1, maxSteps / steps.length);
            return {
                score,
                reason: `${steps.length} completed tool steps observed against a ${maxSteps}-step budget.`,
                traceReferences: steps.map((event) => event.sequence)
            };
        }
        case "plan_adherence":
            throw new Error("plan_adherence requires a judge evaluator.");
    }
}
function terminalToolEvents(trace) {
    return trace.events.filter((event) => event.type === "tool" && event.phase === "complete");
}
function createExecutedResult(metric, evaluated) {
    const score = clampScore(evaluated.score);
    return {
        id: metric.id,
        enabled: metric.enabled,
        required: metric.required,
        weight: metric.weight,
        score,
        threshold: metric.threshold,
        passed: score >= metric.threshold,
        status: "executed",
        reason: evaluated.reason,
        ...(evaluated.traceReferences === undefined
            ? {}
            : { traceReferences: evaluated.traceReferences })
    };
}
function createUnavailableResult(metric, status, reason) {
    return {
        id: metric.id,
        enabled: metric.enabled,
        required: metric.required,
        weight: metric.weight,
        score: 0,
        threshold: metric.threshold,
        passed: false,
        status,
        reason
    };
}
function clampScore(value) {
    if (!Number.isFinite(value)) {
        return 0;
    }
    return Math.min(1, Math.max(0, value));
}
function readPositiveNumber(config, key) {
    if (typeof config !== "object" || config === null || Array.isArray(config)) {
        return undefined;
    }
    const value = config[key];
    return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
}
function formatUnknownError(error) {
    return error instanceof Error ? error.message : String(error);
}
