"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateTextConversationBuilderTests = generateTextConversationBuilderTests;
const decimal_js_1 = __importDefault(require("decimal.js"));
const crypto_1 = require("crypto");
const drizzle_orm_1 = require("drizzle-orm");
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const constants_1 = require("../../constants");
const updatedConversation_1 = require("../../conversationSimulator/updatedConversation");
const database_1 = require("../../database");
const tables_1 = require("../../database/tables");
const providers_1 = require("../../providers");
const defaults_1 = require("../../providers/defaults");
const datasetService_1 = require("../../server/services/datasetService");
const conversationBuilderTurnsDatasetColumns_1 = require("../conversationBuilderTurnsDatasetColumns");
const uploadFilesToAzure_1 = require("../uploadFilesToAzure");
// ============================================================================
// Utility Functions
// ============================================================================
/**
 * Formats generated prompt content by cleaning up markdown and escape sequences
 */
const formatGeneratedPrompt = (content) => {
    if (typeof content !== 'string') {
        return content;
    }
    return (content
        .replace(/\\n/g, '\n')
        .replace(/\\t/g, '\t')
        .replace(/\\r/g, '\r')
        .replace(/\*\*/g, '')
        .replace(/\*/g, '')
        .replace(/`/g, '')
        .replace(/#{1,6}\s/g, '')
        .replace(/\n\s*\n\s*\n/g, '\n\n')
        .replace(/^\s+|\s+$/g, '')
        .replace(/\n\s*\d+\s*\n/g, '\n')
        .replace(/\n\s*\d+\.\s*/g, '\n• ')
        .replace(/\\(.)/g, '$1'));
};
/**
 * Processes src keys in data by downloading and uploading to Azure
 */
const processSrcKey = async (data, token, context = 'unknown', orgId, projectId, controlPlaneUrl) => {
    if (!data || typeof data !== 'object') {
        return data;
    }
    if (data.src && typeof data.src === 'string') {
        try {
            const response = await fetch(data.src);
            if (!response.ok) {
                throw new Error(`Failed to fetch file: ${response.status} ${response.statusText}`);
            }
            const arrayBuffer = await response.arrayBuffer();
            let contentType = response.headers.get('content-type') || 'application/octet-stream';
            let extension = '';
            if (contentType.includes('image/') || contentType.includes('audio/') || contentType.includes('video/') || contentType.includes('text/')) {
                extension = contentType.split('/')[1];
            }
            else {
                const urlPath = new URL(data.src).pathname;
                const urlExtension = urlPath.split('.').pop();
                extension = urlExtension && urlExtension.length < 10 ? urlExtension : 'bin';
                const mimeMap = {
                    jpg: 'image/jpeg',
                    jpeg: 'image/jpeg',
                    png: 'image/png',
                    gif: 'image/gif',
                    webp: 'image/webp',
                    mp4: 'video/mp4',
                    mov: 'video/quicktime',
                    avi: 'video/x-msvideo',
                    txt: 'text/plain',
                    json: 'application/json',
                };
                contentType = mimeMap[urlExtension?.toLowerCase() || ''] || 'application/octet-stream';
            }
            const fileName = `${context}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.${extension}`;
            const tempDir = os.tmpdir();
            const filePath = path.join(tempDir, fileName);
            fs.writeFileSync(filePath, Buffer.from(arrayBuffer));
            const folderName = `workspace/${orgId}/project/${projectId}/dataset-uploads`;
            const blobUrl = await (0, uploadFilesToAzure_1.uploadFileToAzure)(filePath, fileName, token, folderName, contentType, false, true, true, true, controlPlaneUrl);
            try {
                return decodeURIComponent(blobUrl);
            }
            catch (err) {
                console.error(`Error decoding blob URL:`, err);
                return blobUrl;
            }
        }
        catch (err) {
            console.error(`Error processing src file for ${context}:`, err);
            return data.src;
        }
    }
    if (Array.isArray(data)) {
        return Promise.all(data.map((item) => processSrcKey(item, token, context, orgId, projectId, controlPlaneUrl)));
    }
    if (typeof data === 'object') {
        const processedData = {};
        const promises = Object.entries(data).map(async ([key, value]) => {
            const result = await processSrcKey(value, token, `${context}.${key}`, orgId, projectId, controlPlaneUrl);
            return { key, result };
        });
        const results = await Promise.all(promises);
        results.forEach(({ key, result }) => {
            processedData[key] = result;
        });
        return processedData;
    }
    return data;
};
/**
 * Extracts linked variables from raw prompt text (format: {{datasetName/columnName}})
 */
const extractLinkedVariables = (text) => {
    const regex = /\{\{([^}/]+)\/([^}]+)\}\}/g;
    const matches = [];
    let match;
    while ((match = regex.exec(text)) !== null) {
        matches.push({
            datasetName: match[1].trim(),
            columnName: match[2].trim(),
            fullMatch: match[0],
        });
    }
    return matches;
};
/**
 * Extracts column value from dataset map or record
 */
const extractColumnValue = (value) => {
    if (typeof value === 'string') {
        return value;
    }
    if (value && typeof value === 'object') {
        return value?.src || value?.text || value?.value || String(value);
    }
    return String(value || '');
};
/**
 * Replaces variables in raw prompt with dataset values
 */
const replaceRawPromptVariables = (rawPrompt, testIndex, huggingfaceMaps, datasetRecordsMaps, datasetsList) => {
    const linkedVariables = extractLinkedVariables(rawPrompt);
    if (linkedVariables.length === 0) {
        return rawPrompt;
    }
    let replacedPrompt = rawPrompt;
    for (const link of linkedVariables) {
        const dataset = datasetsList.find((d) => d.name === link.datasetName);
        if (!dataset || !dataset.id) {
            continue;
        }
        let columnValue = '';
        if (dataset.type === constants_1.DATASET_TYPE.CONNECT) {
            const mapKey = `${dataset.id}:${link.columnName}`;
            const datasetMap = huggingfaceMaps.get(mapKey);
            if (datasetMap?.has(testIndex)) {
                columnValue = extractColumnValue(datasetMap.get(testIndex));
            }
        }
        else {
            const mapKey = `${dataset.id}:${link.columnName}`;
            const recordsMap = datasetRecordsMaps.get(mapKey);
            if (recordsMap?.has(testIndex)) {
                const recordData = recordsMap.get(testIndex);
                if (recordData?.[link.columnName] !== undefined) {
                    columnValue = extractColumnValue(recordData[link.columnName]);
                }
            }
        }
        // Replace all occurrences of the same linked variable in the prompt.
        replacedPrompt = replacedPrompt.split(link.fullMatch).join(columnValue);
    }
    return replacedPrompt;
};
/**
 * Gets user message value from dataset based on action type
 */
const getUserMessageValue = (action, testIndex, huggingfaceMaps, datasetRecordsMaps, datasetsList) => {
    if (action.sequenceType !== 'sequence') {
        return '';
    }
    if (action.datasetSelectionType === 'raw' && action.rawPrompt) {
        const linkedVariables = extractLinkedVariables(action.rawPrompt);
        if (linkedVariables.length > 0 && datasetsList) {
            return replaceRawPromptVariables(action.rawPrompt, testIndex, huggingfaceMaps, datasetRecordsMaps, datasetsList);
        }
        return action.rawPrompt;
    }
    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(testIndex)) {
            return extractColumnValue(datasetMap.get(testIndex));
        }
    }
    else {
        const selectedColumnValues = action.selectedColumnValues || [];
        const datasetValue = selectedColumnValues[testIndex];
        if (datasetValue) {
            return datasetValue.value || datasetValue.text || '';
        }
    }
    return '';
};
/**
 * 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] || '',
    }));
};
// ============================================================================
// Data Collection Functions
// ============================================================================
/**
 * Collects sequence actions and calculates max sequence rows
 */
const collectSequenceActions = (turns) => {
    let maxSequenceRows = Number.POSITIVE_INFINITY;
    const huggingfaceDatasets = [];
    const rawPromptRequirements = [];
    for (const turn of turns) {
        const allActions = [...(turn.actionsAbove || []), ...(turn.actionsBelow || [])];
        for (const action of allActions) {
            if (action.type === 'userResponse' && action.sequenceType === 'sequence') {
                if (action.datasetSelectionType === 'raw' && action.rawPrompt) {
                    const linkedVariables = extractLinkedVariables(action.rawPrompt);
                    if (linkedVariables.length > 0) {
                        const totalTestCase = Number(action.totalTestCase || 0) || 0;
                        for (const link of linkedVariables) {
                            rawPromptRequirements.push({
                                datasetName: link.datasetName,
                                columnName: link.columnName,
                                totalTestCase,
                            });
                        }
                    }
                    const totalTestCase = action.totalTestCase || 0;
                    if (totalTestCase > 0) {
                        maxSequenceRows = Math.min(maxSequenceRows, totalTestCase);
                    }
                }
                else {
                    const totalTestCase = action.totalTestCase || 0;
                    const datasetType = action.datasetType;
                    if (datasetType === constants_1.DATASET_TYPE.CONNECT && action.datasetId && action.columnName && totalTestCase > 0) {
                        huggingfaceDatasets.push({
                            datasetId: action.datasetId,
                            columnName: action.columnName,
                            totalTestCase,
                        });
                    }
                    if (totalTestCase > 0) {
                        maxSequenceRows = Math.min(maxSequenceRows, totalTestCase);
                    }
                    else {
                        const selectedColumnValues = action.selectedColumnValues || [];
                        if (selectedColumnValues.length > 0) {
                            maxSequenceRows = Math.min(maxSequenceRows, selectedColumnValues.length);
                        }
                    }
                }
            }
        }
    }
    return {
        maxSequenceRows: Number.isFinite(maxSequenceRows) ? maxSequenceRows : 1,
        huggingfaceDatasets,
        rawPromptRequirements,
    };
};
/**
 * 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: '' }] };
};
// ============================================================================
// Dataset Fetching Functions
// ============================================================================
/**
 * Fetches HuggingFace dataset data and creates a map for column values
 */
const fetchHuggingfaceDatasetMap = async (datasetId, columnName, requiredLength, token, apiKey, orgId, projectId, controlPlaneUrl) => {
    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);
        const processPromises = rows.map(async (item, index) => {
            let columnValue = item.row[columnName];
            if (columnValue !== undefined) {
                const isUrl = typeof columnValue === 'string' && (columnValue.startsWith('http://') || columnValue.startsWith('https://'));
                const hasSrc = typeof columnValue === 'object' && columnValue !== null && 'src' in columnValue;
                if (isUrl || hasSrc) {
                    const processedValue = await processSrcKey(isUrl ? { src: columnValue } : columnValue, token, `huggingface.${datasetId}.${columnName}.${index}`, orgId, projectId, controlPlaneUrl);
                    if (typeof processedValue === 'string') {
                        columnValue = hasSrc ? { ...(typeof columnValue === 'object' ? columnValue : {}), src: processedValue } : processedValue;
                    }
                    else {
                        columnValue = processedValue;
                    }
                }
                return { index, value: columnValue };
            }
            return null;
        });
        const results = await Promise.all(processPromises);
        results.forEach((result) => {
            if (result) {
                datasetMap.set(result.index, result.value);
            }
        });
    }
    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, controlPlaneUrl) => {
    const huggingfaceMaps = new Map();
    const fetchPromises = datasets.map(async (dataset) => {
        const datasetMap = await fetchHuggingfaceDatasetMap(dataset.datasetId, dataset.columnName, dataset.totalTestCase, token, apiKey, orgId, projectId, controlPlaneUrl);
        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
 * message generation can work the same way as "normal" datasets.
 */
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.datasetSelectionType === 'raw') {
                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;
                const inferredType = cell && typeof cell === 'object' && cell.type
                    ? String(cell.type)
                    : cell && typeof cell === 'object' && (cell.voiceUrl || cell.url || cell.src)
                        ? 'voice'
                        : 'text';
                const inferredValue = cell && typeof cell === 'object'
                    ? cell.value ?? cell.voiceUrl ?? cell.url ?? cell.src ?? ''
                    : cell ?? '';
                selected.push({
                    id: (datasetRecordIds?.[i] ? String(datasetRecordIds[i]) : (0, crypto_1.randomUUID)()),
                    type: inferredType,
                    value: inferredValue,
                    recordIndex: i,
                });
            }
            action.selectedColumnValues = selected;
        }
    }
};
/**
 * Fetches dataset records from database for non-CONNECT datasets
 */
const fetchDatasetRecords = async (datasetId, totalTestCase) => {
    const recordsMap = new Map();
    const db = await (0, database_1.getDb)();
    try {
        const datasetRecords = await db
            .select({
            recordData: tables_1.datasetRecordsTable.recordData,
            recordIndex: tables_1.datasetRecordsTable.recordIndex,
        })
            .from(tables_1.datasetRecordsTable)
            .where((0, drizzle_orm_1.eq)(tables_1.datasetRecordsTable.datasetId, datasetId))
            .orderBy((0, drizzle_orm_1.asc)(tables_1.datasetRecordsTable.recordIndex))
            .limit(totalTestCase || 1000);
        datasetRecords.forEach((record, index) => {
            if (record.recordData && typeof record.recordData === 'object') {
                recordsMap.set(index, record.recordData);
            }
        });
    }
    catch (error) {
        console.error(`Error fetching dataset records for ${datasetId}:`, error);
    }
    return recordsMap;
};
/**
 * Fetches dataset metadata and records for raw prompt datasets
 */
const fetchRawPromptDatasets = async (rawPromptRequirements, turns, huggingfaceDatasets, huggingfaceMaps, token, apiKey, orgId, projectId, testRunId, controlPlaneUrl) => {
    const db = await (0, database_1.getDb)();
    const requiredNames = Array.from(new Set(rawPromptRequirements
        .map((r) => String(r.datasetName || '').trim())
        .filter((n) => n.length > 0)));
    const requiredIds = Array.from(new Set(huggingfaceDatasets.map((d) => d.datasetId)));
    // Batch fetch all dataset metadata needed for raw prompts + HF by id.
    const [byName, byId] = await Promise.all([
        requiredNames.length > 0
            ? db
                .select({
                id: tables_1.trusysDatasetsTable.id,
                name: tables_1.trusysDatasetsTable.name,
                type: tables_1.trusysDatasetsTable.type,
                fields: tables_1.trusysDatasetsTable.fields,
                config: tables_1.trusysDatasetsTable.config,
            })
                .from(tables_1.trusysDatasetsTable)
                .where((0, drizzle_orm_1.inArray)(tables_1.trusysDatasetsTable.name, requiredNames))
            : Promise.resolve([]),
        requiredIds.length > 0
            ? db
                .select({
                id: tables_1.trusysDatasetsTable.id,
                name: tables_1.trusysDatasetsTable.name,
                type: tables_1.trusysDatasetsTable.type,
                fields: tables_1.trusysDatasetsTable.fields,
                config: tables_1.trusysDatasetsTable.config,
            })
                .from(tables_1.trusysDatasetsTable)
                .where((0, drizzle_orm_1.inArray)(tables_1.trusysDatasetsTable.id, requiredIds))
            : Promise.resolve([]),
    ]);
    const datasetsMetadata = [...byId, ...byName].filter((d, idx, arr) => arr.findIndex((x) => x.id === d.id) === idx);
    const datasetsList = datasetsMetadata;
    const datasetByName = new Map(datasetsMetadata
        .filter((d) => typeof d.name === 'string')
        .map((d) => [String(d.name).trim(), d]));
    const datasetRecordsMaps = new Map();
    const updatedHuggingfaceMaps = new Map(huggingfaceMaps);
    const resolvedRequirements = rawPromptRequirements
        .map((req) => {
        const dataset = datasetByName.get(String(req.datasetName || '').trim());
        if (!dataset?.id) {
            return null;
        }
        return {
            datasetId: String(dataset.id),
            datasetName: String(dataset.name || req.datasetName),
            columnName: String(req.columnName),
            totalTestCase: Number(req.totalTestCase || 0) || 0,
            datasetType: dataset.type,
            datasetConfig: dataset.config,
        };
    })
        .filter(Boolean);
    // Process CONNECT type raw prompt datasets (HuggingFace URL datasets)
    const connectRawDatasets = resolvedRequirements.filter((d) => d.datasetType === constants_1.DATASET_TYPE.CONNECT);
    const connectFetchPromises = connectRawDatasets.map(async (rawDataset) => {
        const datasetMap = await fetchHuggingfaceDatasetMap(rawDataset.datasetId, rawDataset.columnName, rawDataset.totalTestCase, token, apiKey, orgId, projectId, controlPlaneUrl);
        return { mapKey: `${rawDataset.datasetId}:${rawDataset.columnName}`, datasetMap };
    });
    const connectResults = await Promise.all(connectFetchPromises);
    connectResults.forEach(({ mapKey, datasetMap }) => {
        updatedHuggingfaceMaps.set(mapKey, datasetMap);
    });
    // Process non-CONNECT type raw prompt datasets
    const nonConnectRawDatasets = resolvedRequirements.filter((d) => d.datasetType !== constants_1.DATASET_TYPE.CONNECT);
    const maxDynamicRowsByDatasetId = new Map();
    for (const rd of nonConnectRawDatasets) {
        const cfg = (rd.datasetConfig ?? null);
        const isDyn = (rd.datasetType === constants_1.DATASET_TYPE.GENERATE || rd.datasetType === constants_1.DATASET_TYPE.GENERATE_FROM_DOCUMENTS) &&
            Boolean(cfg && cfg.generateOnEvaluation === true);
        if (!isDyn) {
            continue;
        }
        const t = Number(rd.totalTestCase || 0) || 0;
        const id = rd.datasetId;
        maxDynamicRowsByDatasetId.set(id, Math.max(maxDynamicRowsByDatasetId.get(id) ?? 0, t));
    }
    const dynamicGenCache = new Map();
    const recordsFetchPromises = nonConnectRawDatasets.map(async (rawDataset) => {
        const datasetConfig = (rawDataset.datasetConfig ?? null);
        const isDynamic = (rawDataset.datasetType === constants_1.DATASET_TYPE.GENERATE || rawDataset.datasetType === constants_1.DATASET_TYPE.GENERATE_FROM_DOCUMENTS) &&
            Boolean(datasetConfig && datasetConfig.generateOnEvaluation === true);
        let recordsMap;
        if (isDynamic) {
            const cached = dynamicGenCache.get(rawDataset.datasetId);
            const generated = cached ??
                (await (async () => {
                    const meta = datasetsMetadata.find((d) => d.id === rawDataset.datasetId);
                    const datasetNames = Array.from(new Set([rawDataset.datasetName, meta?.name]
                        .filter((x) => typeof x === 'string' && x.trim().length > 0)
                        .map((x) => x.trim())));
                    const linkedColumns = (0, conversationBuilderTurnsDatasetColumns_1.extractLinkedColumnNamesForDatasetFromTurns)(turns, rawDataset.datasetId, {
                        datasetNames,
                    });
                    const requiredMax = maxDynamicRowsByDatasetId.get(rawDataset.datasetId) ?? (Number(rawDataset.totalTestCase || 0) || 0);
                    const resp = await datasetService_1.datasetService.generateDatasetForEvaluation({
                        datasetId: rawDataset.datasetId,
                        datasetConfig,
                        orgId,
                        projectId,
                        token,
                        apiKey,
                        fieldsToGenerate: linkedColumns,
                        fromConversationBuilder: true,
                        ...(requiredMax > 0 ? { maxGeneratedRows: requiredMax } : {}),
                    });
                    await datasetService_1.datasetService.mapTestRunToDatasetRecords({
                        testRunId,
                        datasetId: rawDataset.datasetId,
                        datasetRecordIds: resp.datasetRecordIds,
                    });
                    const entry = { ...resp, datasetConfig };
                    dynamicGenCache.set(rawDataset.datasetId, entry);
                    return entry;
                })());
            recordsMap = new Map();
            const total = Number(rawDataset.totalTestCase || 0) || 0;
            const slice = total > 0 ? generated.data.slice(0, total) : generated.data;
            slice.forEach((row, idx) => {
                if (row && typeof row === 'object') {
                    recordsMap.set(idx, row);
                }
            });
        }
        else {
            recordsMap = await fetchDatasetRecords(rawDataset.datasetId, rawDataset.totalTestCase);
        }
        return { mapKey: `${rawDataset.datasetId}:${rawDataset.columnName}`, recordsMap };
    });
    const recordsResults = await Promise.all(recordsFetchPromises);
    recordsResults.forEach(({ mapKey, recordsMap }) => {
        datasetRecordsMaps.set(mapKey, recordsMap);
    });
    return { datasetsList, datasetRecordsMaps, updatedHuggingfaceMaps };
};
// ============================================================================
// Test Case Generation Functions
// ============================================================================
/**
 * Generates AI conversations for all combinations
 */
const generateAIConversations = async (aiUserAction, aiCombinations, testSuite, aiConvGenCost, cliApiClient, cliCall = false, workspaceId) => {
    const aiGeneratedConversations = new Map();
    if (!aiUserAction) {
        return aiGeneratedConversations;
    }
    const loadedProviders = await (0, providers_1.loadApiProvider)(testSuite.providers[0].id, {
        env: testSuite.env,
        basePath: testSuite.providers[0].config?.basePath,
        options: { config: { ...(testSuite.providers[0]?.config ?? {}) } },
    });
    const callbackModel = {
        async generate(prompt) {
            const response = await loadedProviders.callApi(prompt);
            return {
                result: response.output,
                cost: response.tokenUsage?.total ?? 0,
            };
        },
    };
    const azureProvider = cliCall ? loadedProviders : (await (0, defaults_1.getDefaultProviders)(undefined, workspaceId)).synthesizeProvider;
    const conversationPromises = aiCombinations.map(async (combo, comboIndex) => {
        const conversationParams = {
            userIntentions: [combo.userIntention].filter((i) => i && i.trim() !== ''),
            userProfiles: [combo.userProfile].filter((p) => p && p.trim() !== ''),
            openingMessage: aiUserAction.openingMessage || '',
            minimumTurns: Number.parseInt(aiUserAction.minimumTurns || '1', 10),
            maximumTurns: Number.parseInt(aiUserAction.maximumTurns || '1', 10),
            chatStopCriteria: aiUserAction.chatStopCriteria || '',
        };
        const generatedPrompt = await (0, updatedConversation_1.runConversationSimulation)(callbackModel, azureProvider, conversationParams, aiConvGenCost, cliApiClient, cliCall);
        if (Array.isArray(generatedPrompt) && generatedPrompt.length > 0) {
            return { comboIndex, conversations: generatedPrompt };
        }
        return null;
    });
    const results = await Promise.all(conversationPromises);
    results.forEach((result) => {
        if (result) {
            aiGeneratedConversations.set(result.comboIndex, result.conversations);
        }
    });
    return aiGeneratedConversations;
};
/**
 * Builds sequence conversation turns for a test case
 */
const buildSequenceConversation = async (turns, sequenceRowIndex, huggingfaceMaps, datasetRecordsMaps, datasetsList, loadedProviders, aiConvGenCost) => {
    const sequenceConversationTurns = [];
    const _conversation = [];
    const processedTurns = turns.map((turn, turnIndex) => ({
        turn,
        turnIndex,
        previousTurn: turnIndex > 0 ? turns[turnIndex - 1] : null,
    }));
    for (const { turn, previousTurn } of processedTurns) {
        const actionsBeforeAI = [
            ...(previousTurn?.actionsBelow || []),
            ...(turn.actionsAbove || []),
        ];
        for (const action of actionsBeforeAI) {
            if (action.type === 'userResponse' && action.sequenceType === 'sequence') {
                if (action.datasetSelectionType === 'raw' && action.rawPrompt && action.totalTestCase) {
                    const linkedVariables = extractLinkedVariables(action.rawPrompt);
                    if (linkedVariables.length > 0 && (!action.selectedColumnValues || action.selectedColumnValues.length === 0)) {
                        action.selectedColumnValues = [];
                        for (let i = 0; i < action.totalTestCase; i++) {
                            const replacedPrompt = replaceRawPromptVariables(action.rawPrompt, i, huggingfaceMaps, datasetRecordsMaps, datasetsList);
                            action.selectedColumnValues.push({
                                value: replacedPrompt,
                                text: replacedPrompt,
                            });
                        }
                    }
                }
                const textValue = getUserMessageValue(action, sequenceRowIndex, huggingfaceMaps, datasetRecordsMaps, datasetsList);
                if (textValue) {
                    sequenceConversationTurns.push({
                        role: 'user',
                        content: textValue,
                    });
                    try {
                        const response = await loadedProviders.callApi(textValue, {
                            vars: { _conversation },
                        });
                        const assistantResponse = response.output
                            ? (typeof response.output === 'string' ? response.output : String(response.output))
                            : 'I understand. How can I assist you further?';
                        if (response.tokenUsage) {
                            aiConvGenCost.cost = aiConvGenCost.cost.plus(response.cost || 0);
                            aiConvGenCost.tokenUsage.prompt = aiConvGenCost.tokenUsage.prompt.plus(response.tokenUsage.prompt || 0);
                            aiConvGenCost.tokenUsage.completion = aiConvGenCost.tokenUsage.completion.plus(response.tokenUsage.completion || 0);
                            aiConvGenCost.tokenUsage.total = aiConvGenCost.tokenUsage.total.plus(response.tokenUsage.total || 0);
                        }
                        sequenceConversationTurns.push({
                            role: 'assistant',
                            content: assistantResponse,
                        });
                        _conversation.push({
                            prompt: textValue,
                            input: textValue,
                            output: assistantResponse,
                        });
                    }
                    catch (error) {
                        console.error('Error generating assistant response:', error);
                        const fallbackResponse = 'I understand. How can I assist you further?';
                        sequenceConversationTurns.push({
                            role: 'assistant',
                            content: fallbackResponse,
                        });
                        _conversation.push({
                            prompt: textValue,
                            input: textValue,
                            output: fallbackResponse,
                        });
                    }
                }
            }
        }
    }
    return sequenceConversationTurns;
};
/**
 * Generates a single test case
 */
const generateSingleTestCase = async (testCaseIndex, aiCombinations, turns, huggingfaceMaps, datasetRecordsMaps, datasetsList, loadedProviders, aiConvGenCost, aiGeneratedConversations, aiUserAction) => {
    const sequenceRowIndex = Math.floor(testCaseIndex / aiCombinations.length);
    const aiComboIndex = testCaseIndex % aiCombinations.length;
    const sequenceConversationTurns = await buildSequenceConversation(turns, sequenceRowIndex, huggingfaceMaps, datasetRecordsMaps, datasetsList, loadedProviders, aiConvGenCost);
    let finalConversationTurns = [...sequenceConversationTurns];
    if (aiUserAction && aiGeneratedConversations.has(aiComboIndex)) {
        const aiConversations = aiGeneratedConversations.get(aiComboIndex);
        if (aiConversations && aiConversations.length > 0) {
            const aiConversation = aiConversations[0];
            if (aiConversation.turns && Array.isArray(aiConversation.turns)) {
                const aiTurns = aiConversation.turns.map((turn) => ({
                    role: turn.role,
                    content: typeof turn.content === 'string' ? formatGeneratedPrompt(turn.content) : turn.content,
                }));
                finalConversationTurns = [...sequenceConversationTurns, ...aiTurns];
            }
        }
    }
    return finalConversationTurns.length > 0 ? { turns: finalConversationTurns } : null;
};
// ============================================================================
// Main Function
// ============================================================================
/**
 * Generate test cases from conversation_builder turns (for text conversations)
 *
 * Logic:
 * - If AI user action exists: Generate conversations for each combination of userIntentions and userProfiles
 * - Total test cases = m (sequence rows) * n (AI combinations)
 * - Each sequence row is paired with each AI-generated conversation
 */
async function generateTextConversationBuilderTests(testSuite, token, apiKey, loggingParams, controlPlaneUrl, cliApiClient, cliCall = false) {
    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
    // concrete user messages (and keep behavior consistent with non-dynamic datasets).
    await populateDynamicSelectedColumnValues({
        turns,
        token,
        apiKey,
        orgId,
        projectId,
        testRunId: loggingParams.testRunId,
    });
    // Step 1: Collect sequence actions and calculate max sequence rows
    const { maxSequenceRows, huggingfaceDatasets, rawPromptRequirements } = collectSequenceActions(turns);
    // Step 2: Find AI user action and generate combinations
    const { aiUserAction, aiCombinations } = findAIUserAction(turns);
    // Step 3: Calculate total test cases
    const totalTestCases = maxSequenceRows * aiCombinations.length;
    // Step 4: Fetch all datasets in parallel
    const [huggingfaceMaps, { datasetsList, datasetRecordsMaps, updatedHuggingfaceMaps }] = await Promise.all([
        fetchAllHuggingfaceDatasets(huggingfaceDatasets, token, apiKey, orgId, projectId, controlPlaneUrl),
        fetchRawPromptDatasets(rawPromptRequirements, turns, huggingfaceDatasets, new Map(), token, apiKey, orgId, projectId, loggingParams.testRunId, controlPlaneUrl),
    ]);
    // Merge HuggingFace maps
    const finalHuggingfaceMaps = new Map([...huggingfaceMaps, ...updatedHuggingfaceMaps]);
    // Step 5: Load providers for AI generation
    const loadedProviders = await (0, providers_1.loadApiProvider)(testSuite.providers[0].id, {
        env: testSuite.env,
        basePath: testSuite.providers[0].config?.basePath,
        options: { config: { ...(testSuite.providers[0]?.config ?? {}) } },
    });
    const aiConvGenCost = {
        cost: new decimal_js_1.default(0),
        tokenUsage: {
            prompt: new decimal_js_1.default(0),
            completion: new decimal_js_1.default(0),
            total: new decimal_js_1.default(0),
            cached: new decimal_js_1.default(0),
        },
    };
    // Step 6: Pre-generate all AI conversations
    const aiGeneratedConversations = await generateAIConversations(aiUserAction, aiCombinations, testSuite, aiConvGenCost, cliApiClient, cliCall, loggingParams.workspaceId);
    // Step 7: Generate all test cases in batches
    const generatedConversations = [];
    const BATCH_SIZE = 5;
    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, finalHuggingfaceMaps, datasetRecordsMaps, datasetsList, loadedProviders, aiConvGenCost, aiGeneratedConversations, aiUserAction));
        }
        const batchResults = await Promise.all(batchPromises);
        generatedConversations.push(...batchResults.filter((result) => result !== null));
    }
    console.log(`Generated ${generatedConversations.length} test cases (${maxSequenceRows} sequence rows × ${aiCombinations.length} AI combinations = ${totalTestCases})`);
    return { generatedConversations, aiConvGenCost };
}
//# sourceMappingURL=index.js.map