"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateCallConversationBuilderTests = generateCallConversationBuilderTests;
const drizzle_orm_1 = require("drizzle-orm");
const constants_1 = require("../../constants");
const database_1 = require("../../database");
const tables_1 = require("../../database/tables");
const datasetService_1 = require("../../server/services/datasetService");
const conversationBuilderTurnsDatasetColumns_1 = require("../conversationBuilderTurnsDatasetColumns");
const fetchBlobFilesFromAzure_1 = require("../fetchBlobFilesFromAzure");
// ============================================================================
// Utility Functions
// ============================================================================
/**
 * Generates combinations of userIntentions and userProfiles
 */
const generateAIUserActionCombinations = (action) => {
    const userIntentions = (action.userIntentions || []).filter((i) => i && i.trim() !== '');
    const userProfiles = (action.userProfiles || []).filter((p) => p && p.trim() !== '');
    if (userIntentions.length === 0 || userProfiles.length === 0) {
        return [];
    }
    return userIntentions.map((intention, index) => ({
        userIntention: intention,
        userProfile: userProfiles[index] || '',
    }));
};
/**
 * Extracts voice URL from column value
 */
const extractVoiceUrl = (columnValue) => {
    if (typeof columnValue === 'string') {
        return columnValue;
    }
    if (columnValue && typeof columnValue === 'object') {
        return columnValue?.src || columnValue?.text || columnValue?.value || columnValue?.voiceUrl || '';
    }
    return '';
};
/**
 * Extracts blob URL from Azure fetch response
 */
const extractBlobUrl = (response) => {
    if (typeof response === 'string') {
        return response;
    }
    if (response && typeof response === 'object') {
        return response.url || response.blobUrl || response.uri || Object.values(response)[0];
    }
    return undefined;
};
/**
 * Fetches and resolves blob URL from Azure
 */
const resolveBlobUrl = async (voiceUrl, token, apiKey) => {
    try {
        const resolvedBlobResponse = await (0, fetchBlobFilesFromAzure_1.fetchBlobFileFromAzure)(voiceUrl, token, apiKey, 17520);
        const resolvedBlobUrl = extractBlobUrl(resolvedBlobResponse);
        if (resolvedBlobUrl && typeof resolvedBlobUrl === 'string') {
            return decodeURI(resolvedBlobUrl);
        }
    }
    catch (error) {
        console.error('Error fetching blob URL from Azure:', error);
    }
    return voiceUrl; // Fallback to original URL
};
// ============================================================================
// Data Collection Functions
// ============================================================================
/**
 * Collects sequence user responses and HuggingFace datasets
 */
const collectSequenceActions = (turns) => {
    const sequenceUserResponses = [];
    const huggingfaceDatasets = [];
    for (const turn of turns) {
        const allActions = [...(turn.actionsAbove || []), ...(turn.actionsBelow || [])];
        for (const action of allActions) {
            if (action.type === 'userResponse' && action.sequenceType === 'sequence') {
                const datasetType = action.datasetType;
                const totalTestCase = action.totalTestCase || 0;
                if (datasetType === constants_1.DATASET_TYPE.CONNECT && action.datasetId && action.columnName && totalTestCase > 0) {
                    huggingfaceDatasets.push({
                        datasetId: action.datasetId,
                        columnName: action.columnName,
                        totalTestCase,
                    });
                }
                const selectedColumnValues = action.selectedColumnValues || [];
                if (selectedColumnValues.length > 0 || (datasetType === constants_1.DATASET_TYPE.CONNECT && totalTestCase > 0)) {
                    sequenceUserResponses.push({
                        turn,
                        action,
                        selectedColumnValues,
                        datasetType,
                        datasetId: action.datasetId,
                        columnName: action.columnName,
                        totalTestCase,
                    });
                }
            }
        }
    }
    return { sequenceUserResponses, huggingfaceDatasets };
};
/**
 * Calculates maximum sequence rows from sequence user responses
 */
const calculateMaxSequenceRows = (sequenceUserResponses) => {
    if (sequenceUserResponses.length === 0) {
        return 1;
    }
    return Math.min(...sequenceUserResponses.map((s) => {
        if (s.datasetType === constants_1.DATASET_TYPE.CONNECT && s.totalTestCase) {
            return s.totalTestCase;
        }
        return s.selectedColumnValues.length;
    }));
};
/**
 * Finds AI user action and generates combinations
 */
const findAIUserAction = (turns) => {
    for (const turn of turns) {
        const allActions = [...(turn.actionsAbove || []), ...(turn.actionsBelow || [])];
        for (const action of allActions) {
            if (action.type === 'userResponse' && action.sequenceType === 'ai') {
                const aiCombinations = generateAIUserActionCombinations(action);
                return {
                    aiUserAction: action,
                    aiCombinations: aiCombinations.length > 0 ? aiCombinations : [{ userIntention: '', userProfile: '' }],
                };
            }
        }
    }
    return { aiUserAction: null, aiCombinations: [{ userIntention: '', userProfile: '' }] };
};
/**
 * Checks if AI speaks first
 */
const checkAISpeaksFirst = (turns) => {
    return (turns.length > 0 &&
        turns[0] &&
        turns[0].actionsAbove &&
        Array.isArray(turns[0].actionsAbove) &&
        turns[0].actionsAbove.length > 0);
};
/**
 * Extracts default assert and description from test suite
 */
const extractDefaultTestMetadata = (testSuite) => {
    const testsArray = Array.isArray(testSuite.tests) ? testSuite.tests : [];
    const firstTest = testsArray.length > 0 && typeof testsArray[0] === 'object' ? testsArray[0] : null;
    const defaultAssert = firstTest && firstTest.assert && Array.isArray(firstTest.assert) ? firstTest.assert : [];
    const defaultDescription = firstTest && firstTest.description && typeof firstTest.description === 'string'
        ? firstTest.description
        : '';
    return { defaultAssert, defaultDescription };
};
// ============================================================================
// Dataset Fetching Functions
// ============================================================================
/**
 * Fetches HuggingFace dataset data and creates a map for column values
 */
const fetchHuggingfaceDatasetMap = async (datasetId, columnName, requiredLength, token, apiKey, orgId, projectId) => {
    const datasetMap = new Map();
    const db = await (0, database_1.getDb)();
    try {
        const dataset = await db
            .select({
            id: tables_1.trusysDatasetsTable.id,
            type: tables_1.trusysDatasetsTable.type,
            config: tables_1.trusysDatasetsTable.config,
        })
            .from(tables_1.trusysDatasetsTable)
            .where((0, drizzle_orm_1.eq)(tables_1.trusysDatasetsTable.id, datasetId))
            .limit(1);
        if (dataset.length === 0 || dataset[0].type !== constants_1.DATASET_TYPE.CONNECT) {
            return datasetMap;
        }
        const datasetConfig = dataset[0].config;
        if (!datasetConfig?.datasetUrl) {
            return datasetMap;
        }
        const hfToken = process.env.HF_TOKEN || process.env.HF_API_TOKEN || process.env.HUGGING_FACE_HUB_TOKEN;
        const headers = {};
        if (hfToken) {
            headers.Authorization = `Bearer ${hfToken}`;
        }
        const url = new URL(datasetConfig.datasetUrl);
        const response = await fetch(url.toString(), { headers });
        if (!response.ok) {
            console.error(`Failed to fetch HuggingFace dataset: ${response.statusText}`);
            return datasetMap;
        }
        const responseData = await response.json();
        const rows = responseData.rows.slice(0, requiredLength);
        rows.forEach((item, index) => {
            const columnValue = item.row[columnName];
            if (columnValue !== undefined) {
                datasetMap.set(index, columnValue);
            }
        });
    }
    catch (error) {
        console.error(`Error fetching HuggingFace dataset ${datasetId}:`, error);
    }
    return datasetMap;
};
/**
 * Fetches all HuggingFace datasets in parallel
 */
const fetchAllHuggingfaceDatasets = async (datasets, token, apiKey, orgId, projectId) => {
    const huggingfaceMaps = new Map();
    const fetchPromises = datasets.map(async (dataset) => {
        const datasetMap = await fetchHuggingfaceDatasetMap(dataset.datasetId, dataset.columnName, dataset.totalTestCase, token, apiKey, orgId, projectId);
        return { mapKey: `${dataset.datasetId}:${dataset.columnName}`, datasetMap };
    });
    const results = await Promise.all(fetchPromises);
    results.forEach(({ mapKey, datasetMap }) => {
        huggingfaceMaps.set(mapKey, datasetMap);
    });
    return huggingfaceMaps;
};
/**
 * For dynamic (generateOnEvaluation) datasets used in sequence actions, generate rows
 * (scoped/mapped to this testRun) and populate action.selectedColumnValues so downstream
 * step generation can work the same way as "normal" datasets (including audio via voiceUrl).
 */
const populateDynamicSelectedColumnValues = async (params) => {
    const db = await (0, database_1.getDb)();
    const byDatasetId = new Map();
    let totalOverallTestCase = 1;
    for (const turn of params.turns) {
        const allActions = [...(turn.actionsAbove || []), ...(turn.actionsBelow || [])];
        for (const action of allActions) {
            if (action.type !== 'userResponse' || action.sequenceType !== 'sequence') {
                continue;
            }
            if (!action.datasetId || !action.columnName) {
                continue;
            }
            if (action.datasetType === constants_1.DATASET_TYPE.CONNECT) {
                continue;
            }
            const totalTestCase = Number(action.totalTestCase || 0);
            if (!Number.isFinite(totalTestCase) || totalTestCase <= 0) {
                continue;
            }
            else {
                totalOverallTestCase = Math.min(totalOverallTestCase, totalTestCase);
            }
            const selectedColumnValues = Array.isArray(action.selectedColumnValues) ? action.selectedColumnValues : [];
            if (selectedColumnValues.length > 0) {
                continue;
            }
            const datasetId = String(action.datasetId);
            const list = byDatasetId.get(datasetId) ?? [];
            list.push({
                action,
                columnName: String(action.columnName),
                totalTestCase,
            });
            byDatasetId.set(datasetId, list);
        }
    }
    if (byDatasetId.size === 0) {
        return;
    }
    const datasets = await db
        .select({
        id: tables_1.trusysDatasetsTable.id,
        name: tables_1.trusysDatasetsTable.name,
        type: tables_1.trusysDatasetsTable.type,
        config: tables_1.trusysDatasetsTable.config,
    })
        .from(tables_1.trusysDatasetsTable)
        .where((0, drizzle_orm_1.inArray)(tables_1.trusysDatasetsTable.id, Array.from(byDatasetId.keys())));
    const byId = new Map(datasets.map((d) => [d.id, d]));
    for (const [datasetId, items] of byDatasetId) {
        const dataset = byId.get(datasetId);
        const datasetConfig = (dataset?.config ?? null);
        const isDynamic = (dataset?.type === constants_1.DATASET_TYPE.GENERATE || dataset?.type === constants_1.DATASET_TYPE.GENERATE_FROM_DOCUMENTS) &&
            Boolean(datasetConfig && datasetConfig.generateOnEvaluation === true);
        if (!isDynamic) {
            continue;
        }
        const datasetNames = typeof dataset?.name === 'string' && dataset.name.trim() ? [dataset.name.trim()] : [];
        const linkedColumns = (0, conversationBuilderTurnsDatasetColumns_1.extractLinkedColumnNamesForDatasetFromTurns)(params.turns, datasetId, {
            datasetNames,
        });
        const requiredRowCount = totalOverallTestCase;
        const { datasetRecordIds, data } = await datasetService_1.datasetService.generateDatasetForEvaluation({
            datasetId,
            datasetConfig,
            orgId: params.orgId,
            projectId: params.projectId,
            token: params.token,
            apiKey: params.apiKey,
            fieldsToGenerate: linkedColumns,
            fromConversationBuilder: true,
            ...(requiredRowCount > 0 ? { maxGeneratedRows: requiredRowCount } : {}),
        });
        await datasetService_1.datasetService.mapTestRunToDatasetRecords({
            testRunId: params.testRunId,
            datasetId,
            datasetRecordIds,
        });
        for (const { action, columnName, totalTestCase } of items) {
            const selected = [];
            for (let i = 0; i < totalTestCase; i++) {
                const row = data?.[i];
                const cell = row && typeof row === 'object' ? row[columnName] : undefined;
                selected.push(cell ?? {});
            }
            action.selectedColumnValues = selected;
        }
    }
};
// ============================================================================
// Test Step Generation Functions
// ============================================================================
/**
 * Gets voice URL from action based on dataset type
 */
const getVoiceUrlFromAction = (action, sequenceRowIndex, huggingfaceMaps) => {
    const datasetType = action.datasetType;
    if (datasetType === constants_1.DATASET_TYPE.CONNECT && action.datasetId && action.columnName) {
        const mapKey = `${action.datasetId}:${action.columnName}`;
        const datasetMap = huggingfaceMaps.get(mapKey);
        if (datasetMap?.has(sequenceRowIndex)) {
            return extractVoiceUrl(datasetMap.get(sequenceRowIndex));
        }
    }
    else {
        const selectedColumnValues = action.selectedColumnValues || [];
        if (selectedColumnValues[sequenceRowIndex]?.voiceUrl) {
            return selectedColumnValues[sequenceRowIndex].voiceUrl;
        }
    }
    return undefined;
};
/**
 * Processes a single action and adds appropriate step
 */
const processAction = async (action, sequenceRowIndex, aiComboIndex, huggingfaceMaps, aiCombinations, token, apiKey) => {
    if (action.type === 'keypad') {
        return {
            type: 'keyboard',
            value: action.value || '',
        };
    }
    if (action.type === 'userResponse' && action.sequenceType === 'sequence') {
        const voiceUrl = getVoiceUrlFromAction(action, sequenceRowIndex, huggingfaceMaps);
        if (voiceUrl) {
            const resolvedUrl = await resolveBlobUrl(voiceUrl, token, apiKey);
            return {
                type: 'audio',
                value: resolvedUrl,
            };
        }
        return null;
    }
    if (action.type === 'delay') {
        return {
            type: 'delay',
            value: action.value || '',
        };
    }
    if (action.type === 'startAfter') {
        return {
            type: 'startAfter',
            value: action.value || '',
        };
    }
    if (action.type === 'userResponse' && action.sequenceType === 'ai') {
        const combo = aiCombinations[aiComboIndex] || { userIntention: '', userProfile: '' };
        return {
            type: 'realtime',
            value: {
                userIntentions: [combo.userIntention].filter((i) => i && i.trim() !== ''),
                userProfiles: [combo.userProfile].filter((p) => p && p.trim() !== ''),
                openingMessage: action.openingMessage || '',
                minimumTurns: action.minimumTurns || '',
                maximumTurns: action.maximumTurns || '',
                chatStopCriteria: action.chatStopCriteria || '',
            },
        };
    }
    return null;
};
/**
 * Builds steps for a single test case
 */
const buildTestSteps = async (turns, sequenceRowIndex, aiComboIndex, huggingfaceMaps, aiCombinations, token, apiKey) => {
    const steps = [];
    for (const turn of turns) {
        const allActionsArrays = [turn.actionsAbove, turn.actionsBelow].filter((actions) => Array.isArray(actions) && actions.length > 0);
        for (const actionsArray of allActionsArrays) {
            for (const action of actionsArray) {
                const step = await processAction(action, sequenceRowIndex, aiComboIndex, huggingfaceMaps, aiCombinations, token, apiKey);
                if (step) {
                    steps.push(step);
                }
            }
            // If last step is 'realtime', skip adding 'next' and exit
            if (steps.length > 0 && steps[steps.length - 1].type === 'realtime') {
                break;
            }
            else {
                steps.push({
                    type: 'next',
                    value: '',
                });
            }
        }
    }
    return steps;
};
/**
 * Generates a single test case
 */
const generateSingleTestCase = async (testCaseIndex, aiCombinations, turns, huggingfaceMaps, defaultAssert, defaultDescription, token, apiKey) => {
    const sequenceRowIndex = Math.floor(testCaseIndex / aiCombinations.length);
    const aiComboIndex = testCaseIndex % aiCombinations.length;
    const steps = await buildTestSteps(turns, sequenceRowIndex, aiComboIndex, huggingfaceMaps, aiCombinations, token, apiKey);
    return {
        vars: {
            prompt: steps,
        },
        assert: defaultAssert,
        description: defaultDescription,
    };
};
// ============================================================================
// Main Function
// ============================================================================
/**
 * Generate test cases from conversation_builder turns (for call conversations)
 *
 * Logic:
 * - If AI user action exists: Generate test cases for each combination of userIntentions and userProfiles
 * - Total test cases = m (sequence rows) * n (AI combinations)
 * - Each sequence row is paired with each AI combination
 */
async function generateCallConversationBuilderTests(testSuite, token, loggingParams, apiKey) {
    const turns = testSuite.turns || [];
    const orgId = loggingParams.workspaceId;
    const projectId = loggingParams.projectId;
    // If sequence actions reference a dynamic dataset, generate rows now so we can build steps
    // with concrete values (including audio via voiceUrl).
    await populateDynamicSelectedColumnValues({
        turns,
        token,
        apiKey,
        orgId,
        projectId,
        testRunId: loggingParams.testRunId,
    });
    // Step 1: Extract default test metadata
    const { defaultAssert, defaultDescription } = extractDefaultTestMetadata(testSuite);
    // Step 2: Collect sequence actions and datasets
    const { sequenceUserResponses, huggingfaceDatasets } = collectSequenceActions(turns);
    // Step 3: Calculate max sequence rows
    const maxSequenceRows = calculateMaxSequenceRows(sequenceUserResponses);
    // Step 4: Find AI user action and generate combinations
    const { aiCombinations } = findAIUserAction(turns);
    // Step 5: Calculate total test cases
    const totalTestCases = maxSequenceRows * aiCombinations.length;
    // Step 6: Check if AI speaks first
    const aiSpeaksFirst = checkAISpeaksFirst(turns);
    // Step 7: Fetch all HuggingFace datasets in parallel
    const huggingfaceMaps = await fetchAllHuggingfaceDatasets(huggingfaceDatasets, token, apiKey, orgId, projectId);
    // Step 8: Generate all test cases in batches
    const generatedTests = [];
    const BATCH_SIZE = 10; // Larger batch size for call tests since they're simpler
    for (let batchStart = 0; batchStart < totalTestCases; batchStart += BATCH_SIZE) {
        const batchEnd = Math.min(batchStart + BATCH_SIZE, totalTestCases);
        const batchPromises = [];
        for (let testCaseIndex = batchStart; testCaseIndex < batchEnd; testCaseIndex++) {
            batchPromises.push(generateSingleTestCase(testCaseIndex, aiCombinations, turns, huggingfaceMaps, defaultAssert, defaultDescription, token, apiKey));
        }
        const batchResults = await Promise.all(batchPromises);
        generatedTests.push(...batchResults);
    }
    return { generatedTests, aiSpeaksFirst };
}
//# sourceMappingURL=index.js.map