/*
 * Copyright OpenSearch Contributors
 * SPDX-License-Identifier: Apache-2.0
 */
/**
 * Async Test Case Storage
 *
 * Async wrapper around OpenSearch storage for test cases.
 * Provides a similar API to the synchronous localStorage-based storage.
 */
import { testCaseStorage as opensearchTestCases } from './opensearchClient.js';
import { buildLabels, parseLabels } from '../../lib/labels.js';
/**
 * Convert OpenSearch storage format to app TestCase format
 * Handles backward compatibility: if labels exist, use them; otherwise derive from legacy fields
 */
function toTestCase(stored) {
    // Build labels from storage or derive from legacy fields
    let labels = stored.labels || [];
    if (labels.length === 0 && (stored.category || stored.difficulty)) {
        labels = buildLabels({
            category: stored.category,
            subcategory: stored.subcategory,
            difficulty: stored.difficulty,
        });
    }
    // Parse labels for backward compat fields
    const parsed = parseLabels(labels);
    return {
        id: stored.id,
        name: stored.name,
        description: stored.description,
        labels,
        // Backward compat - derived from labels or fallback to stored values
        category: parsed.category || stored.category || 'General',
        subcategory: parsed.subcategory || stored.subcategory,
        difficulty: parsed.difficulty || stored.difficulty || 'Medium',
        currentVersion: stored.version,
        versions: [], // Versions fetched separately if needed
        // Source provenance — preserve so the UI can distinguish SDK / code-imported
        // test cases from JSON ones (CollapsibleTestCaseDefinition keys off these).
        sourceFile: stored.sourceFile,
        sourceHash: stored.sourceHash,
        isPromoted: stored.tags?.includes('promoted') ?? false,
        createdAt: stored.createdAt,
        updatedAt: stored.updatedAt,
        lastRunAt: stored.lastRunAt,
        initialPrompt: stored.initialPrompt,
        context: (stored.context || []),
        tools: stored.tools,
        expectedPPL: stored.expectedPPL,
        expectedOutcomes: stored.expectedOutcomes,
        expectedTrajectory: (stored.expectedTrajectory || []),
    };
}
/**
 * Convert app TestCase format to OpenSearch storage format
 * Writes labels as primary, legacy fields for backward compatibility
 */
function toStorageFormat(testCase) {
    const tags = testCase.tags || [];
    if (testCase.isPromoted && !tags.includes('promoted')) {
        tags.push('promoted');
    }
    // Use provided labels or build from legacy fields
    let labels = testCase.labels;
    if (!labels && (testCase.category || testCase.difficulty)) {
        labels = buildLabels({
            category: testCase.category,
            subcategory: testCase.subcategory,
            difficulty: testCase.difficulty,
        });
    }
    return {
        name: testCase.name,
        description: testCase.description,
        initialPrompt: testCase.initialPrompt,
        tools: testCase.tools,
        messages: [],
        context: testCase.context,
        forwardedProps: {},
        expectedPPL: testCase.expectedPPL,
        expectedOutcomes: testCase.expectedOutcomes,
        expectedTrajectory: testCase.expectedTrajectory,
        labels,
        // Legacy fields - kept for backward compatibility
        category: testCase.category,
        subcategory: testCase.subcategory,
        difficulty: testCase.difficulty,
        tags,
        author: testCase.author,
    };
}
class AsyncTestCaseStorage {
    async getAll(options) {
        const isPaginated = options?.size !== undefined;
        const result = await opensearchTestCases.getAll({
            fields: options?.summary ? 'summary' : undefined,
            size: options?.size,
            after: options?.after,
            includeSample: options?.includeSample,
        });
        const testCases = result.testCases.map(toTestCase);
        // Sort by lastActivity (max of lastRunAt, updatedAt, createdAt) descending
        const lastActivity = (tc) => Math.max(tc.lastRunAt ? new Date(tc.lastRunAt).getTime() : 0, tc.updatedAt ? new Date(tc.updatedAt).getTime() : 0, tc.createdAt ? new Date(tc.createdAt).getTime() : 0);
        const sorted = testCases.sort((a, b) => lastActivity(b) - lastActivity(a));
        if (isPaginated) {
            return {
                testCases: sorted,
                total: result.total,
                after: result.after,
                hasMore: result.hasMore,
            };
        }
        return sorted;
    }
    /**
     * Get only promoted test cases (for Experiments)
     */
    async getPromoted() {
        const all = await this.getAll();
        return all.filter(tc => tc.isPromoted);
    }
    /**
     * Get test cases by specific IDs (for efficient filtered fetching)
     * Used when you only need test cases for a specific benchmark
     */
    async getByIds(ids) {
        if (ids.length === 0)
            return [];
        const stored = await opensearchTestCases.getByIds(ids);
        const testCases = stored.map(toTestCase);
        // Maintain order of requested IDs
        const idOrder = new Map(ids.map((id, index) => [id, index]));
        return testCases.sort((a, b) => (idOrder.get(a.id) ?? Infinity) - (idOrder.get(b.id) ?? Infinity));
    }
    /**
     * Get a test case by ID (latest version)
     */
    async getById(id) {
        const stored = await opensearchTestCases.getById(id);
        return stored ? toTestCase(stored) : null;
    }
    /**
     * Create a new test case
     */
    async create(input) {
        const storageData = toStorageFormat(input);
        const created = await opensearchTestCases.create(storageData);
        return toTestCase(created);
    }
    /**
     * Update a test case (creates new version automatically in OpenSearch)
     */
    async update(id, updates) {
        // First get current to merge with updates
        const current = await opensearchTestCases.getById(id);
        if (!current) {
            return null;
        }
        const merged = {
            ...current,
            ...toStorageFormat(updates),
        };
        // Remove fields that shouldn't be sent
        delete merged.id;
        delete merged.version;
        delete merged.createdAt;
        delete merged.updatedAt;
        const updated = await opensearchTestCases.update(id, merged);
        return toTestCase(updated);
    }
    /**
     * Delete a test case (all versions)
     */
    async delete(id) {
        const result = await opensearchTestCases.delete(id);
        return result.deleted > 0;
    }
    /**
     * Set promoted status for a test case
     */
    async setPromoted(id, isPromoted) {
        const current = await opensearchTestCases.getById(id);
        if (!current) {
            return false;
        }
        const tags = current.tags || [];
        if (isPromoted && !tags.includes('promoted')) {
            tags.push('promoted');
        }
        else if (!isPromoted) {
            const idx = tags.indexOf('promoted');
            if (idx > -1)
                tags.splice(idx, 1);
        }
        await opensearchTestCases.update(id, { ...current, tags });
        return true;
    }
    // ==================== Version Operations ====================
    /**
     * Get all versions of a test case
     */
    async getVersions(testCaseId) {
        const stored = await opensearchTestCases.getVersions(testCaseId);
        return stored.map(s => ({
            version: s.version,
            createdAt: s.createdAt,
            initialPrompt: s.initialPrompt,
            context: (s.context || []),
            tools: s.tools,
            expectedPPL: s.expectedPPL,
            expectedOutcomes: s.expectedOutcomes, // NEW
            expectedTrajectory: (s.expectedTrajectory || []),
        }));
    }
    /**
     * Get a specific version of a test case
     */
    async getVersion(testCaseId, version) {
        const stored = await opensearchTestCases.getVersion(testCaseId, version);
        if (!stored)
            return null;
        return {
            version: stored.version,
            createdAt: stored.createdAt,
            initialPrompt: stored.initialPrompt,
            context: (stored.context || []),
            tools: stored.tools,
            expectedPPL: stored.expectedPPL,
            expectedOutcomes: stored.expectedOutcomes, // NEW
            expectedTrajectory: (stored.expectedTrajectory || []),
        };
    }
    // ==================== Utility Methods ====================
    /**
     * Generate a unique ID
     */
    generateId() {
        return `tc-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
    }
    /**
     * Get unique categories from all test cases
     * @deprecated Use getLabels() instead
     */
    async getCategories() {
        const testCases = await this.getAll();
        const categories = new Set();
        testCases.forEach(tc => categories.add(tc.category));
        return Array.from(categories).sort();
    }
    /**
     * Get all unique labels from all test cases
     */
    async getLabels() {
        const testCases = await this.getAll();
        const labels = new Set();
        testCases.forEach(tc => tc.labels?.forEach(l => labels.add(l)));
        return Array.from(labels).sort();
    }
    /**
     * Get test cases count
     */
    async getCount() {
        const testCases = await this.getAll();
        return testCases.length;
    }
    /**
     * Get promoted count
     */
    async getPromotedCount() {
        const promoted = await this.getPromoted();
        return promoted.length;
    }
    /**
     * Bulk create test cases (for migration)
     */
    async bulkCreate(testCases) {
        const storageData = testCases.map(tc => toStorageFormat(tc));
        return opensearchTestCases.bulkCreate(storageData);
    }
}
// Export singleton instance
export const asyncTestCaseStorage = new AsyncTestCaseStorage();
//# sourceMappingURL=asyncTestCaseStorage.js.map