/**
 * Assertion type mapper — maps AILF assertion types to Promptfoo assertion types.
 *
 * AILF assertions have two flavors:
 * 1. Templated assertions (`type: "llm-rubric"` with `template` + `criteria`)
 *    → resolved into Promptfoo's `llm-rubric` with a fully assembled rubric prompt
 * 2. Value assertions (any other `type` with a `value`)
 *    → passed through to Promptfoo mostly as-is
 *
 * This module handles the mapping for both, validates mode compatibility
 * (e.g., `tool-called` is only valid for agent-harness/mcp-server modes),
 * and normalizes weight fields.
 *
 * @see docs/design-docs/architecture-overhaul/scoring-rubrics-assertions.md
 * @see docs/archive/exec-plans/architecture-overhaul/phase-2-config-compiler.md
 */
// ---------------------------------------------------------------------------
// Known assertion types and their mode compatibility
// ---------------------------------------------------------------------------
/** Assertion types supported by AILF and their Promptfoo equivalents */
const ASSERTION_TYPE_MAP = {
    // Deterministic
    contains: "contains",
    "contains-all": "contains-all",
    "contains-any": "contains-any",
    "contains-json": "contains-json",
    cost: "cost",
    equals: "equals",
    "is-json": "is-json",
    "is-valid-openai-function-call": "is-valid-openai-function-call",
    "is-valid-openai-tools-call": "is-valid-openai-tools-call",
    latency: "latency",
    regex: "regex",
    // Programmatic
    javascript: "javascript",
    python: "python",
    // LLM-graded
    "g-eval": "g-eval",
    "llm-rubric": "llm-rubric",
    "model-graded-closedqa": "model-graded-closedqa",
    "model-graded-factuality": "model-graded-factuality",
    similar: "similar",
    // Tool-use (agent/MCP modes)
    "skill-used": "javascript", // Custom JS assertion checking tool calls
    "tool-call-f1": "javascript", // Custom JS assertion computing F1
    "tool-called": "javascript", // Custom JS assertion checking specific tool
};
/** Assertion types restricted to specific modes.
 * "literacy" is included because the agentic literacy variant uses tool calls. */
const MODE_RESTRICTED_TYPES = {
    "skill-used": ["agent-harness", "mcp-server", "literacy"],
    "tool-call-f1": ["agent-harness", "mcp-server", "literacy"],
    "tool-called": ["agent-harness", "mcp-server", "literacy"],
};
/** Negatable assertion types (Promptfoo supports `not-` prefix) */
const NEGATABLE_TYPES = new Set([
    "contains",
    "contains-all",
    "contains-any",
    "equals",
    "is-json",
    "regex",
]);
/**
 * Map an array of AILF assertions to Promptfoo assertions.
 *
 * @param assertions - AILF assertion definitions
 * @param options - Mapper options
 * @returns Mapped Promptfoo assertions and any warnings
 */
export function mapAssertions(assertions, options) {
    const mapped = [];
    const warnings = [];
    for (const assertion of assertions) {
        const result = mapSingleAssertion(assertion, options);
        if (result.warning) {
            warnings.push(result.warning);
        }
        if (result.assertion) {
            mapped.push(result.assertion);
        }
    }
    return { mapped, warnings };
}
/**
 * Check if an assertion type is valid.
 */
export function isValidAssertionType(type) {
    const baseType = type.startsWith("not-") ? type.slice(4) : type;
    return baseType in ASSERTION_TYPE_MAP;
}
/**
 * Check if an assertion type is compatible with a given mode.
 */
export function isAssertionCompatibleWithMode(type, mode) {
    const baseType = type.startsWith("not-") ? type.slice(4) : type;
    const restrictions = MODE_RESTRICTED_TYPES[baseType];
    if (!restrictions)
        return true; // No restrictions
    return restrictions.includes(mode);
}
// ---------------------------------------------------------------------------
// Internal mapping
// ---------------------------------------------------------------------------
function mapSingleAssertion(assertion, options) {
    const { type } = assertion;
    // Check mode compatibility
    if (options?.mode) {
        if (!isAssertionCompatibleWithMode(type, options.mode)) {
            return {
                assertion: null,
                warning: `Assertion type "${type}" is not compatible with mode "${options.mode}" — skipped`,
            };
        }
    }
    // Handle negation
    const isNegated = type.startsWith("not-");
    const baseType = isNegated ? type.slice(4) : type;
    if (isNegated && !NEGATABLE_TYPES.has(baseType)) {
        return {
            assertion: null,
            warning: `Assertion type "${type}" does not support negation — skipped`,
        };
    }
    // Templated assertions (llm-rubric with template + criteria)
    if (isTemplated(assertion)) {
        return {
            assertion: mapTemplatedAssertion(assertion, options),
            warning: null,
        };
    }
    // Value assertions — map type and pass through
    const promptfooType = ASSERTION_TYPE_MAP[baseType];
    if (!promptfooType) {
        // Unknown type — pass through as-is (Promptfoo may support it natively)
        return {
            assertion: mapValueAssertion(assertion, type),
            warning: `Unknown assertion type "${type}" — passed through to Promptfoo as-is`,
        };
    }
    const mappedType = isNegated ? `not-${promptfooType}` : promptfooType;
    return {
        assertion: mapValueAssertion(assertion, mappedType),
        warning: null,
    };
}
function isTemplated(assertion) {
    return (assertion.type === "llm-rubric" &&
        "template" in assertion &&
        "criteria" in assertion);
}
function mapTemplatedAssertion(assertion, options) {
    const result = {
        type: "llm-rubric",
        // The rubric prompt will be fully assembled by the PromptfooCompiler
        // using rubric templates. Here we pass the template ref + criteria as
        // metadata so the compiler can resolve it. Criteria are either legacy
        // bare strings or canonical `{ id, text }` objects — render the text of
        // each (interpolating an object directly would emit "[object Object]").
        value: `[template:${assertion.template}] ${assertion.criteria
            .map((c) => (typeof c === "string" ? c : c.text))
            .join("; ")}`,
    };
    if (assertion.weight !== undefined) {
        result.weight = assertion.weight;
    }
    if (options?.graderProvider) {
        result.provider = options.graderProvider;
    }
    return result;
}
function mapValueAssertion(assertion, mappedType) {
    const { type: _type, ...rest } = assertion;
    const result = {
        type: mappedType,
        ...rest,
    };
    return result;
}
