/*
 * Copyright OpenSearch Contributors
 * SPDX-License-Identifier: Apache-2.0
 */
import { recordVerdict } from '../matchers/session.js';
const evaluatorRegistry = new Map();
/**
 * Register a custom evaluator under `id`.
 *
 * Registering the **same function** under an id again is a no-op (a watched
 * file re-loading the identical module is fine). Registering a *different*
 * function under an id that's already taken throws: the registry is
 * process-global (shared like a helper library), so two `.eval.{js,ts}` files
 * defining `defineEvaluator('len-check', …)` with different bodies would
 * otherwise silently shadow each other based on load order. Fail loudly so the
 * collision is visible at authoring time. Use distinct ids (or a shared helper
 * module) when you need different implementations.
 */
export function defineEvaluator(id, fn) {
    if (!id || typeof id !== 'string') {
        throw new Error('defineEvaluator(id, fn): id must be a non-empty string');
    }
    if (typeof fn !== 'function') {
        throw new Error(`defineEvaluator('${id}', fn): fn must be a function`);
    }
    const existing = evaluatorRegistry.get(id);
    if (existing && existing !== fn) {
        throw new Error(`defineEvaluator('${id}', ...): an evaluator with id '${id}' is already ` +
            `registered with a different function. Evaluator ids are global — use a ` +
            `unique id per evaluator (or share one function) to avoid silent shadowing.`);
    }
    evaluatorRegistry.set(id, fn);
}
/** Look up a registered evaluator by id. @internal */
export function getEvaluator(id) {
    return evaluatorRegistry.get(id);
}
/** Clear all registered evaluators (test isolation / per-load reset). @internal */
export function clearEvaluators() {
    evaluatorRegistry.clear();
}
/** Build a {@link Verdict} from a custom-evaluator result. */
function evaluatorVerdict(r, role, errored, errorMessage) {
    const verdict = {
        passFailStatus: r.pass ? 'passed' : 'failed',
        accuracy: typeof r.score === 'number' ? r.score * 100 : (r.pass ? 100 : 0),
        score: typeof r.score === 'number' ? r.score : (r.pass ? 1 : 0),
        reasoning: r.reasoning ?? '',
        pass: r.pass,
        role,
        skipped: false,
        errored,
        errorMessage,
        orThrow() {
            if (this.pass || this.skipped)
                return this;
            const label = this.errored ? 'errored' : 'FAILED';
            throw new Error(`Evaluator: ${label}\n${this.errorMessage ?? this.reasoning}`);
        },
    };
    return verdict;
}
async function runEvaluator(result, evaluatorId, criteria, role) {
    const fn = getEvaluator(evaluatorId);
    const description = `evaluate: ${evaluatorId}${criteria ? ` — ${criteria}` : ''}`;
    if (!fn) {
        // Unknown evaluator id — this is an authoring error the run can't
        // recover from. Record it as errored (excluded from pass-rate).
        const msg = `No evaluator registered with id '${evaluatorId}'. Did you call defineEvaluator('${evaluatorId}', ...)?`;
        recordVerdict({
            description, pass: false, method: 'evaluator', role, errored: true,
            durationMs: 0, errorMessage: msg, reasoning: '',
        });
        return evaluatorVerdict({ pass: false }, role, true, msg);
    }
    const startedAt = Date.now();
    let out;
    try {
        out = await fn({ result, criteria, traces: result.traces });
    }
    catch (err) {
        const msg = err?.message || String(err);
        recordVerdict({
            description, pass: false, method: 'evaluator', role, errored: true,
            durationMs: Date.now() - startedAt, errorMessage: `Evaluator threw: ${msg}`, reasoning: '',
        });
        return evaluatorVerdict({ pass: false }, role, true, `Evaluator threw: ${msg}`);
    }
    const verdict = evaluatorVerdict(out, role, false);
    recordVerdict({
        description,
        pass: verdict.pass,
        method: 'evaluator',
        role,
        durationMs: Date.now() - startedAt,
        score: verdict.score,
        reasoning: verdict.reasoning,
        errorMessage: verdict.pass ? undefined : (verdict.reasoning || 'evaluator failed'),
    });
    return verdict;
}
export const evaluate = Object.assign((result, evaluatorId, criteria) => runEvaluator(result, evaluatorId, criteria, 'gate'), {
    observe: (result, evaluatorId, criteria) => runEvaluator(result, evaluatorId, criteria, 'observe'),
});
//# sourceMappingURL=evaluators.js.map