/**
 * Mock scenario support for integration testing.
 *
 * Provides a queue-based mechanism to control mock provider responses
 * per agent or by call order. Scenarios can be loaded from JSON files
 * (via TAKT_MOCK_SCENARIO env var) or set programmatically in tests.
 */
import { readFileSync, existsSync } from 'node:fs';
import { STATUS_VALUES } from '../../core/models/status.js';
import { AGENT_FAILURE_CATEGORIES } from '../../shared/types/agent-failure.js';
const AGENT_FAILURE_CATEGORY_VALUES = new Set(Object.values(AGENT_FAILURE_CATEGORIES));
/**
 * Queue that dispenses scenario entries.
 *
 * Matching rules:
 * 1. If an entry has `agent` set, it only matches calls for that agent name.
 * 2. Entries without `agent` match any call (consumed in order).
 * 3. First matching entry is removed from the queue and returned.
 * 4. Returns undefined when no matching entry remains.
 */
export class ScenarioQueue {
    entries;
    constructor(entries) {
        // Defensive copy
        this.entries = [...entries];
    }
    /**
     * Consume the next matching entry for the given agent.
     */
    consume(personaName) {
        // Try persona-specific match first
        const personaIndex = this.entries.findIndex((e) => e.persona !== undefined && e.persona === personaName);
        if (personaIndex >= 0) {
            return this.entries.splice(personaIndex, 1)[0];
        }
        // Fall back to first unspecified entry
        const anyIndex = this.entries.findIndex((e) => e.persona === undefined);
        if (anyIndex >= 0) {
            return this.entries.splice(anyIndex, 1)[0];
        }
        return undefined;
    }
    /** Number of remaining entries */
    get remaining() {
        return this.entries.length;
    }
}
// --- Global singleton (module-level state) ---
let globalQueue = null;
/**
 * Set mock scenario programmatically (for tests).
 * Pass null to clear.
 */
export function setMockScenario(entries) {
    globalQueue = entries ? new ScenarioQueue(entries) : null;
}
/**
 * Get the current global scenario queue.
 * Lazily loads from TAKT_MOCK_SCENARIO env var on first access.
 */
export function getScenarioQueue() {
    if (globalQueue)
        return globalQueue;
    const envPath = process.env.TAKT_MOCK_SCENARIO;
    if (envPath) {
        const entries = loadScenarioFile(envPath);
        globalQueue = new ScenarioQueue(entries);
        return globalQueue;
    }
    return null;
}
/**
 * Reset global scenario state (for test cleanup).
 */
export function resetScenario() {
    globalQueue = null;
}
/**
 * Load and validate a scenario JSON file.
 *
 * @param filePath Absolute or relative path to scenario JSON
 * @throws Error if file not found or JSON invalid
 */
export function loadScenarioFile(filePath) {
    if (!existsSync(filePath)) {
        throw new Error(`Scenario file not found: ${filePath}`);
    }
    const raw = readFileSync(filePath, 'utf-8');
    let parsed;
    try {
        parsed = JSON.parse(raw);
    }
    catch {
        throw new Error(`Scenario file is not valid JSON: ${filePath}`);
    }
    if (!Array.isArray(parsed)) {
        throw new Error(`Scenario file must contain a JSON array: ${filePath}`);
    }
    return parsed.map((entry, i) => validateEntry(entry, i));
}
function validateEntry(entry, index) {
    if (typeof entry !== 'object' || entry === null) {
        throw new Error(`Scenario entry [${index}] must be an object`);
    }
    const obj = entry;
    // content is required
    if (typeof obj.content !== 'string') {
        throw new Error(`Scenario entry [${index}] must have a "content" string`);
    }
    // status defaults to 'done'
    const status = obj.status ?? 'done';
    if (typeof status !== 'string' || !STATUS_VALUES.includes(status)) {
        throw new Error(`Scenario entry [${index}] has invalid status "${String(status)}". Valid: ${STATUS_VALUES.join(', ')}`);
    }
    // persona is optional
    if (obj.persona !== undefined && typeof obj.persona !== 'string') {
        throw new Error(`Scenario entry [${index}] "persona" must be a string if provided`);
    }
    // structured_output is optional
    if (obj.structured_output !== undefined && (typeof obj.structured_output !== 'object' || obj.structured_output === null || Array.isArray(obj.structured_output))) {
        throw new Error(`Scenario entry [${index}] "structured_output" must be an object if provided`);
    }
    // delay_ms is optional
    if (obj.delay_ms !== undefined && typeof obj.delay_ms !== 'number') {
        throw new Error(`Scenario entry [${index}] "delay_ms" must be a number if provided`);
    }
    if (obj.error !== undefined && typeof obj.error !== 'string') {
        throw new Error(`Scenario entry [${index}] "error" must be a string if provided`);
    }
    if (obj.failure_category !== undefined
        && (typeof obj.failure_category !== 'string' || !AGENT_FAILURE_CATEGORY_VALUES.has(obj.failure_category))) {
        throw new Error(`Scenario entry [${index}] "failure_category" is invalid`);
    }
    return {
        persona: obj.persona,
        status: status,
        content: obj.content,
        structuredOutput: obj.structured_output,
        error: obj.error,
        failureCategory: obj.failure_category,
        delayMs: obj.delay_ms,
    };
}
//# sourceMappingURL=scenario.js.map