"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.addLayerTestCases = addLayerTestCases;
/* eslint-disable @typescript-eslint/no-use-before-define */
const uuid_1 = require("uuid");
const logger_1 = __importDefault(require("../../logger"));
const attackProviders_1 = require("../shared/attackProviders");
const util_1 = require("./util");
/**
 * Adds layer test cases by composing strategies in order.
 *
 * When an attack provider (hydra, crescendo, etc.) is encountered in the steps,
 * the remaining steps become per-turn transforms that are applied to each turn's
 * output before sending to the target.
 *
 * @example
 * ```yaml
 * # Regular layer composition (pre-eval transforms)
 * strategies:
 *   - id: layer
 *     config:
 *       steps: [jailbreak, base64]
 *
 * # Attack provider with per-turn transforms
 * strategies:
 *   - id: layer
 *     config:
 *       steps: [hydra, audio]  # audio applied to each Hydra turn
 *
 * # Mixed: pre-eval + attack provider + per-turn
 * strategies:
 *   - id: layer
 *     config:
 *       steps: [jailbreak, hydra, audio]
 *       # jailbreak applied to initial test cases
 *       # audio applied to each Hydra turn
 * ```
 */
async function addLayerTestCases(testCases, injectVar, config, strategies, loadStrategy) {
    // Compose strategies in-order. Config example:
    // { steps: [ 'base64', { id: 'rot13' } ] }
    const steps = Array.isArray(config?.steps)
        ? config.steps
        : [];
    if (steps.length === 0) {
        logger_1.default.warn('layer strategy: no steps provided; returning empty');
        return [];
    }
    let current = testCases;
    for (let i = 0; i < steps.length; i++) {
        const step = steps[i];
        const stepObj = typeof step === 'string' ? { id: step } : step;
        // ═══════════════════════════════════════════════════════════════════════
        // CHECK: Is this an attack provider (hydra, crescendo, etc.)?
        // If so, remaining steps become per-turn transforms
        // ═══════════════════════════════════════════════════════════════════════
        if ((0, attackProviders_1.isAttackProvider)(stepObj.id)) {
            logger_1.default.debug(`layer strategy: detected attack provider '${stepObj.id}' at step ${i}, remaining steps will be per-turn transforms`);
            // Collect remaining steps as per-turn layer configs
            const remainingSteps = steps.slice(i + 1);
            const perTurnLayers = remainingSteps.map((s) => typeof s === 'string' ? s : { id: s.id, config: s.config });
            // Get the full provider ID
            const providerId = (0, attackProviders_1.getAttackProviderFullId)(stepObj.id);
            const metricSuffix = getMetricSuffix(stepObj.id);
            const label = typeof config?.label === 'string' ? config.label : undefined;
            const strategyId = getStrategyId(stepObj.id, perTurnLayers, label);
            const scanId = (0, uuid_1.v4)();
            logger_1.default.debug(`layer strategy: configuring attack provider:  ${JSON.stringify({
                providerId,
                perTurnLayers: perTurnLayers.map((l) => (typeof l === 'string' ? l : l.id)),
                testCaseCount: current.length,
            })}`);
            // Transform current test cases to use the attack provider
            // with per-turn layers configured
            return current.map((testCase) => {
                const originalText = String(testCase.vars?.[injectVar] ?? '');
                return {
                    ...testCase,
                    provider: {
                        id: providerId,
                        config: {
                            injectVar,
                            scanId,
                            ...stepObj.config,
                            // Pass per-turn layers for runtime application
                            ...(perTurnLayers.length > 0 && { _perTurnLayers: perTurnLayers }),
                        },
                    },
                    assert: testCase.assert?.map((assertion) => ({
                        ...assertion,
                        metric: assertion.metric ? `${assertion.metric}/${metricSuffix}` : assertion.metric,
                    })),
                    metadata: {
                        ...testCase.metadata,
                        strategyId,
                        originalText,
                    },
                };
            });
        }
        // ═══════════════════════════════════════════════════════════════════════
        // REGULAR STRATEGY: Apply transform to test cases (existing behavior)
        // ═══════════════════════════════════════════════════════════════════════
        let stepAction;
        try {
            if (stepObj.id.startsWith('file://')) {
                const loaded = await loadStrategy(stepObj.id);
                stepAction = loaded.action;
            }
            else {
                // Try exact match first, then base id before ':'
                let builtin = strategies.find((s) => s.id === stepObj.id);
                if (!builtin && stepObj.id.includes(':')) {
                    const baseId = stepObj.id.split(':')[0];
                    builtin = strategies.find((s) => s.id === baseId);
                }
                stepAction = builtin?.action;
            }
        }
        catch (e) {
            logger_1.default.error(`layer strategy: error loading step ${stepObj.id}: ${e}`);
            stepAction = undefined;
        }
        if (!stepAction) {
            logger_1.default.warn(`layer strategy: step ${stepObj.id} not registered, skipping`);
            continue;
        }
        // Determine applicable test cases for this step using the same targeting rules
        const stepTargets = stepObj.config?.plugins ?? config?.plugins;
        const applicable = current.filter((t) => (0, util_1.pluginMatchesStrategyTargets)(t, stepObj.id, stepTargets));
        const next = await stepAction(applicable, injectVar, {
            ...(stepObj.config || {}),
            ...(config || {}),
        });
        // Feed output to next step. If a step yields nothing, subsequent steps operate on empty set.
        current = next;
    }
    return current;
}
/**
 * Gets the metric suffix for an attack provider.
 */
function getMetricSuffix(stepId) {
    const baseId = stepId.replace('trusys:redteam:', '').replace('jailbreak:', '');
    const suffixMap = {
        // Multi-turn conversational strategies
        hydra: 'Hydra',
        crescendo: 'Crescendo',
        goat: 'GOAT',
        custom: 'Custom',
        // Multi-attempt single-turn strategies
        iterative: 'Iterative',
        'iterative:meta': 'Meta',
        'iterative:tree': 'Tree',
    };
    return suffixMap[baseId] || baseId.charAt(0).toUpperCase() + baseId.slice(1);
}
/**
 * Gets the strategy ID for an attack provider with per-turn layers.
 * If a label is provided in the config, it's included for display.
 */
function getStrategyId(stepId, perTurnLayers, label) {
    const baseId = stepId.includes(':') ? stepId : `jailbreak:${stepId}`;
    const labelPrefix = label ? `layer/${label}:` : '';
    if (perTurnLayers.length === 0) {
        return `${labelPrefix}${baseId}`;
    }
    const layerIds = perTurnLayers.map((l) => (typeof l === 'string' ? l : l.id)).join('/');
    return `${labelPrefix}${baseId}/${layerIds}`;
}
//# sourceMappingURL=layer.js.map