"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.TransformDatasetService = void 0;
const drizzle_orm_1 = require("drizzle-orm");
const fs = __importStar(require("fs"));
const lodash_1 = __importDefault(require("lodash"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const constants_1 = require("../../constants");
const database_1 = require("../../database");
const tables_1 = require("../../database/tables");
const logger_1 = __importDefault(require("../../logger"));
const fetchBlobFilesFromAzure_1 = require("../../util/fetchBlobFilesFromAzure");
const uploadFilesToAzure_1 = require("../../util/uploadFilesToAzure");
const datasetService_1 = require("./datasetService");
/**
 * Service for transforming datasets and processing test data
 * Handles different dataset types and transforms data accordingly
 */
class TransformDatasetService {
    constructor() {
        this.uploadedFileNames = [];
        this.datasetService = new datasetService_1.DatasetService();
    }
    /**
     * Acquire a slot in the voice fetch concurrency pool.
     * Ensures we never have more than MAX_VOICE_CONCURRENCY parallel blob fetches.
     */
    static async acquireVoiceSlot() {
        if (this.currentVoiceConcurrency < this.MAX_VOICE_CONCURRENCY) {
            this.currentVoiceConcurrency += 1;
            return;
        }
        await new Promise((resolve) => {
            this.voiceQueue.push(() => {
                this.currentVoiceConcurrency += 1;
                resolve();
            });
        });
    }
    /**
     * Release a slot and wake up the next waiting fetch, if any.
     */
    static releaseVoiceSlot() {
        this.currentVoiceConcurrency = Math.max(0, this.currentVoiceConcurrency - 1);
        const next = this.voiceQueue.shift();
        if (next) {
            next();
        }
    }
    /**
     * Helper to run a function under the voice concurrency limiter.
     */
    async runWithVoiceConcurrency(fn) {
        await TransformDatasetService.acquireVoiceSlot();
        try {
            return await fn();
        }
        finally {
            TransformDatasetService.releaseVoiceSlot();
        }
    }
    // Helper function to process src keys in data by downloading and uploading to Azure
    async processSrcKey(data, token, context = 'unknown', orgId, projectId) {
        if (!data || typeof data !== 'object') {
            return data;
        }
        if (data.src && typeof data.src === 'string') {
            try {
                // Download the file from the src URL
                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();
                // Determine file extension/type from Content-Type header or URL
                let contentType = response.headers.get('content-type') || 'application/octet-stream';
                let extension = '';
                if (contentType.includes('image/')) {
                    extension = contentType.split('/')[1];
                }
                else if (contentType.includes('audio/')) {
                    extension = contentType.split('/')[1];
                }
                else if (contentType.includes('video/')) {
                    extension = contentType.split('/')[1];
                }
                else if (contentType.includes('text/')) {
                    extension = contentType.split('/')[1];
                }
                else {
                    // Try to get extension from URL
                    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';
                }
                // Generate a unique file name
                const fileName = `${context}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.${extension}`;
                // Track this file name for cleanup
                this.uploadedFileNames.push(fileName);
                // Write the file to a temporary location
                const tempDir = os.tmpdir();
                const filePath = path.join(tempDir, fileName);
                fs.writeFileSync(filePath, Buffer.from(arrayBuffer));
                // Upload the file to Azure
                const folderName = `workspace/${orgId}/project/${projectId}/dataset-uploads`;
                const blobUrl = await (0, uploadFilesToAzure_1.uploadFileToAzure)(filePath, fileName, token, folderName, contentType, false, // don't add UUID (we already made it unique)
                true, // cleanup after upload (handles temp file deletion)
                true, // private container
                true);
                // Return the updated object with the new blob URL, decoded if necessary
                try {
                    // Some Azure URLs may be double-encoded, so decode repeatedly until stable
                    const decodedUrl = decodeURIComponent(blobUrl);
                    return decodedUrl;
                }
                catch (err) {
                    // If decoding fails, just return the original blobUrl
                    console.error(`Error decoding blob URL:`, err);
                    return blobUrl;
                }
            }
            catch (err) {
                console.error(`Error processing src file for ${context}:`, err);
                return data.src; // Return original data on error
            }
        }
        // If it's an array, process each item
        if (Array.isArray(data)) {
            return Promise.all(data.map((item) => this.processSrcKey(item, token, context, orgId, projectId)));
        }
        // If it's an object, process each property in parallel
        if (typeof data === 'object') {
            const processedData = {};
            const promises = Object.entries(data).map(async ([key, value]) => {
                const result = await this.processSrcKey(value, token, `${context}.${key}`, orgId, projectId);
                return { key, result };
            });
            const results = await Promise.all(promises);
            results.forEach(({ key, result }) => {
                processedData[key] = result;
            });
            return processedData;
        }
        return data;
    }
    ;
    /**
     * Replace dataset column placeholder in metric value or return column value if present
     * @param metricValue - The metric value string that may contain {{columnName}}
     * @param columnName - The column name to look for in the placeholder
     * @param columnValue - The value to replace the placeholder with, or return if no placeholder found
     * @returns The replaced value, columnValue if present, or original metricValue
     */
    replaceMetricColumnValue(metricValue, columnName, columnValue) {
        // If metricValue is not a string, return it as is
        if (typeof metricValue !== 'string') {
            // If columnValue is present, return it; otherwise return metricValue
            return lodash_1.default.isEmpty(columnValue) ? metricValue : columnValue;
        }
        // Check if metricValue contains {{columnName}}
        const placeholder = `{{${columnName}}}`;
        if (metricValue?.includes(placeholder) && !lodash_1.default.isEmpty(columnValue)) {
            // Replace all occurrences of {{columnName}} with columnValue
            return metricValue.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), columnValue);
        }
        // If no placeholder found, return columnValue if present, otherwise return metricValue
        if (!lodash_1.default.isEmpty(columnValue)) {
            return columnValue;
        }
        return metricValue;
    }
    /**
     * Main entry point for transforming datasets
     */
    async transformDataset(options) {
        try {
            const { tests, datasetIds, token, apiKey, orgId, projectId, parentPromptId, testRunId, prompts, } = options;
            // Reset the tracking array for this transformation
            this.uploadedFileNames = [];
            if (!Array.isArray(tests) || tests.length === 0) {
                return tests;
            }
            if (lodash_1.default.isEmpty(datasetIds)) {
                return this.stripDatasetColumnsFromTestAssertions(tests);
            }
            // Fetch all datasets
            const datasets = await this.fetchDatasets(datasetIds);
            // Generate datasets that need to be generated on evaluation
            await this.generateDatasetsForEvaluation(datasets, {
                tests,
                orgId,
                projectId,
                token,
                apiKey,
                prompts,
                testRunId,
            });
            // Process each test independently
            const processedTests = await this.processTests(tests, datasets, {
                token,
                apiKey,
                orgId,
                projectId,
                parentPromptId,
                testRunId,
            });
            // Flatten the result (since some tests expand to multiple tests)
            return processedTests.flat();
        }
        catch (error) {
            logger_1.default.error(`Error in transformDataset: ${error}`);
            throw error;
        }
    }
    /**
     * When no datasets are attached, remove datasetColumn from assertions so eval uses
     * static values only (no row-backed columns).
     */
    stripDatasetColumnsFromTestAssertions(tests) {
        return tests.map((test) => {
            if (!test?.assert || !Array.isArray(test.assert)) {
                return test;
            }
            return {
                ...test,
                assert: test.assert.map((item) => this.stripDatasetColumnFromAssertionItem(item)),
            };
        });
    }
    stripDatasetColumnFromAssertionItem(item) {
        if (item === null || item === undefined || typeof item !== 'object') {
            return item;
        }
        const withoutTop = lodash_1.default.omit(item, 'datasetColumn');
        if (Array.isArray(withoutTop.value)) {
            return {
                ...withoutTop,
                value: withoutTop?.value?.map((arrayItem) => {
                    if (arrayItem && typeof arrayItem === 'object' && 'datasetColumn' in arrayItem) {
                        return arrayItem?.value;
                    }
                    return arrayItem;
                }),
            };
        }
        return withoutTop;
    }
    /**
     * Fetch datasets from database
     */
    async fetchDatasets(datasetIds) {
        const db = await (0, database_1.getDb)();
        const datasets = await db
            .select({
            id: tables_1.trusysDatasetsTable.id,
            type: tables_1.trusysDatasetsTable.type,
            config: tables_1.trusysDatasetsTable.config,
            fields: tables_1.trusysDatasetsTable.fields,
        })
            .from(tables_1.trusysDatasetsTable)
            .where((0, drizzle_orm_1.inArray)(tables_1.trusysDatasetsTable.id, datasetIds));
        if (datasets.length === 0) {
            throw new Error(`No datasets found for ids: ${datasetIds.join(', ')}`);
        }
        return datasets;
    }
    computeFieldsToGenerateByDatasetId(params) {
        const { datasetsToGenerate, tests } = params;
        const requiredFieldsByDatasetId = new Map();
        const addRequired = (datasetId, fieldName) => {
            if (!datasetId || !fieldName) {
                return;
            }
            const set = requiredFieldsByDatasetId.get(datasetId) ?? new Set();
            set.add(fieldName);
            requiredFieldsByDatasetId.set(datasetId, set);
        };
        const extractAssertionDatasetColumns = (assertItem) => {
            if (!assertItem || typeof assertItem !== 'object') {
                return [];
            }
            const cols = [];
            if (typeof assertItem.datasetColumn === 'string' && assertItem.datasetColumn.trim()) {
                cols.push(assertItem.datasetColumn.trim());
            }
            if (Array.isArray(assertItem.value)) {
                for (const v of assertItem.value) {
                    if (v &&
                        typeof v === 'object' &&
                        typeof v.datasetColumn === 'string' &&
                        v.datasetColumn.trim()) {
                        cols.push(v.datasetColumn.trim());
                    }
                }
            }
            return cols;
        };
        if (Array.isArray(tests) && tests.length > 0) {
            for (const test of tests) {
                const datasetId = test?.dataset?.id;
                if (!datasetId) {
                    continue;
                }
                const dataset = datasetsToGenerate.find((d) => d?.id === datasetId);
                if (!dataset) {
                    continue;
                }
                const datasetFieldNames = new Set((Array.isArray(dataset.fields) ? dataset.fields : [])
                    .map((f) => (typeof f === 'object' ? f?.name : f))
                    .filter(Boolean)
                    .map((n) => String(n)));
                const varsKeys = test?.vars && typeof test.vars === 'object' ? Object.keys(test.vars) : [];
                for (const k of varsKeys) {
                    if (datasetFieldNames.has(k)) {
                        addRequired(datasetId, k);
                    }
                }
                const assertions = Array.isArray(test?.assert) ? test.assert : [];
                for (const a of assertions) {
                    for (const col of extractAssertionDatasetColumns(a)) {
                        if (datasetFieldNames.has(col)) {
                            addRequired(datasetId, col);
                        }
                    }
                }
            }
        }
        const out = new Map();
        for (const dataset of datasetsToGenerate) {
            out.set(dataset.id, Array.from(requiredFieldsByDatasetId.get(dataset.id) ?? []));
        }
        return out;
    }
    /**
     * Generate datasets that need to be generated on evaluation
     */
    async generateDatasetsForEvaluation(datasets, options) {
        const datasetsToGenerate = datasets.filter((dataset) => dataset?.config?.generateOnEvaluation === true);
        const fieldsToGenerateByDatasetId = this.computeFieldsToGenerateByDatasetId({
            datasetsToGenerate,
            tests: options.tests,
        });
        for (const dataset of datasetsToGenerate) {
            logger_1.default.info(`Generating dataset ${dataset.id} for evaluation (generateOnEvaluation=true)`);
            const fieldsToGenerate = fieldsToGenerateByDatasetId.get(dataset?.id) ?? [];
            const generationResult = await this.datasetService.generateDatasetForEvaluation({
                datasetId: dataset.id,
                datasetConfig: dataset.config,
                orgId: options.orgId,
                projectId: options.projectId,
                token: options.token,
                apiKey: options.apiKey,
                prompts: options.prompts,
                fieldsToGenerate,
            });
            logger_1.default.info(`Generated ${generationResult.datasetRecordIds.length} records for dataset ${dataset.id}. Record IDs: ${generationResult.datasetRecordIds.slice(0, 5).join(', ')}${generationResult.datasetRecordIds.length > 5 ? '...' : ''}`);
            // Map test run to dataset records (must be synchronous so downstream fetch can see them)
            if (options.testRunId && generationResult.datasetRecordIds.length > 0) {
                await this.datasetService.mapTestRunToDatasetRecords({
                    testRunId: options.testRunId,
                    datasetId: dataset.id,
                    datasetRecordIds: generationResult.datasetRecordIds,
                });
            }
        }
    }
    /**
     * Map test run to dataset records in background
     */
    mapTestRunToDatasetRecordsInBackground(testRunId, datasetId, datasetRecordIds) {
        setImmediate(async () => {
            try {
                await this.datasetService.mapTestRunToDatasetRecords({
                    testRunId,
                    datasetId,
                    datasetRecordIds,
                });
                logger_1.default.info(`Mapped ${datasetRecordIds.length} records for test run ${testRunId} and dataset ${datasetId}`);
            }
            catch (error) {
                logger_1.default.error(`Error mapping test run to dataset records: ${error}`);
            }
        });
    }
    /**
     * Process all tests with their datasets
     */
    async processTests(tests, datasets, options) {
        return Promise.all(tests.map(async (test) => {
            const datasetId = test?.dataset?.id;
            if (!datasetId) {
                return test;
            }
            const dataset = datasets.find((item) => item?.id === datasetId);
            if (!dataset) {
                throw new Error(`Dataset with id ${datasetId} not found in fetched datasets`);
            }
            // Fetch dataset records
            const shouldScopeToTestRun = dataset?.config?.generateOnEvaluation === true;
            const datasetRecords = await this.fetchDatasetRecords(datasetId, shouldScopeToTestRun ? options.testRunId : undefined);
            // Transform dataset data based on type
            const datasetData = await this.transformDatasetDataByType(dataset, datasetRecords, test, {
                token: options.token,
                apiKey: options.apiKey,
            });
            // Expand test for each row in the dataset
            return this.expandTestForDatasetRows(test, datasetData, {
                token: options.token,
                apiKey: options.apiKey,
                orgId: options.orgId,
                projectId: options.projectId,
                parentPromptId: options.parentPromptId,
            });
        }));
    }
    /**
     * Fetch dataset records from database
     */
    async fetchDatasetRecords(datasetId, testRunId) {
        const db = await (0, database_1.getDb)();
        let datasetRecords;
        if (testRunId) {
            // For dynamic datasets generated per evaluation, only return records mapped to this testRunId
            datasetRecords = await db
                .select({
                recordData: tables_1.datasetRecordsTable.recordData,
                recordId: tables_1.testRunsToDatasetRecordsTable.datasetRecordId,
            })
                .from(tables_1.testRunsToDatasetRecordsTable)
                .innerJoin(tables_1.datasetRecordsTable, (0, drizzle_orm_1.eq)(tables_1.testRunsToDatasetRecordsTable.datasetRecordId, tables_1.datasetRecordsTable.id))
                .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.testRunsToDatasetRecordsTable.testRunId, testRunId), (0, drizzle_orm_1.eq)(tables_1.datasetRecordsTable.datasetId, datasetId)));
        }
        else {
            // Fallback: return all records for this dataset
            datasetRecords = await db
                .select({
                recordData: tables_1.datasetRecordsTable.recordData,
                recordId: tables_1.datasetRecordsTable.id,
            })
                .from(tables_1.datasetRecordsTable)
                .where((0, drizzle_orm_1.eq)(tables_1.datasetRecordsTable.datasetId, datasetId));
        }
        logger_1.default.info(`Fetched ${datasetRecords.length} records for dataset ${datasetId}. First record sample: ${JSON.stringify(datasetRecords[0]?.recordData || {}).substring(0, 100)}`);
        return datasetRecords;
    }
    /**
     * Transform dataset data based on dataset type
     */
    async transformDatasetDataByType(dataset, datasetRecords, test, options) {
        // Convert records to dataset data format
        const datasetData = datasetRecords.map((record) => {
            const recordData = record.recordData;
            return {
                ...recordData,
                datasetRecordId: record.recordId,
            };
        });
        // Transform based on dataset type
        switch (dataset.type) {
            case constants_1.DATASET_TYPE.CONNECT:
                return this.transformConnectDataset(dataset);
            case constants_1.DATASET_TYPE.GENERATE:
            case constants_1.DATASET_TYPE.GENERATE_FROM_DOCUMENTS:
                return this.transformGenerateDataset(dataset, datasetData, test, options);
            case constants_1.DATASET_TYPE.UPLOAD:
                return this.transformUploadDataset(datasetData, test, options);
            default:
                return datasetData;
        }
    }
    /**
     * Transform CONNECT type dataset
     */
    async transformConnectDataset(dataset) {
        const response = await fetch(dataset?.config?.datasetUrl);
        const responseData = await response.json();
        return responseData?.rows?.map((item) => item?.row) || [];
    }
    /**
     * Transform GENERATE or GENERATE_FROM_DOCUMENTS type dataset
     */
    async transformGenerateDataset(dataset, datasetData, test, options) {
        const fields = Array.isArray(dataset.fields) ? dataset.fields : [];
        return Promise.all(datasetData.map(async (item) => {
            const transformedRow = {
                datasetRecordId: item?.datasetRecordId,
            };
            // Check if item is already in the expected format (has type/value structure)
            const isStructured = Object.values(item).some((val) => val && typeof val === 'object' && 'type' in val && 'value' in val);
            if (isStructured) {
                // Already structured, process normally
                await this.processStructuredRow(item, transformedRow, test, options);
            }
            else {
                // Transform plain object to structured format
                this.transformPlainRowToStructured(item, transformedRow, fields);
            }
            return transformedRow;
        }));
    }
    /**
     * Process a row that's already in structured format
     */
    async processStructuredRow(item, transformedRow, test, options) {
        const columnPromises = Object.entries(item).map(async ([col, colData]) => {
            if (col === 'datasetRecordId') {
                return; // Skip datasetRecordId
            }
            if (colData?.type === constants_1.DATASET_VAR_TYPE.VOICE) {
                await this.processVoiceColumn(col, colData, transformedRow, test, options);
            }
            else {
                transformedRow[col] = colData?.value;
            }
        });
        await Promise.all(columnPromises);
    }
    /**
     * Process a voice column (fetch audio blob if needed)
     */
    async processVoiceColumn(col, colData, transformedRow, test, options) {
        const isInVars = test?.vars && col in test.vars;
        const isInAssert = test?.assert?.some((assert) => assert?.datasetColumn === col);
        if (isInVars || isInAssert) {
            try {
                const audioBlobResponse = await this.runWithVoiceConcurrency(() => (0, fetchBlobFilesFromAzure_1.fetchBlobFileFromAzure)(colData?.voiceUrl, options.token, options.apiKey, 17520));
                if (audioBlobResponse) {
                    const response = await fetch(decodeURI(audioBlobResponse));
                    const blob = await response.blob();
                    const arrayBuffer = await blob.arrayBuffer();
                    const base64Data = Buffer.from(arrayBuffer).toString('base64');
                    transformedRow[col] = base64Data;
                }
                else {
                    transformedRow[col] = colData?.value;
                }
            }
            catch (error) {
                logger_1.default.error(`Error fetching audio blob: ${error}`);
                transformedRow[col] = colData?.value;
            }
        }
        else {
            transformedRow[col] = colData?.value;
        }
    }
    /**
     * Transform plain row to structured format
     */
    transformPlainRowToStructured(item, transformedRow, fields) {
        for (const [col, colValue] of Object.entries(item)) {
            if (col === 'datasetRecordId') {
                continue; // Skip datasetRecordId
            }
            // Find field definition to get type
            const fieldDef = fields.find((f) => f?.name === col);
            const fieldType = fieldDef?.type || constants_1.DATASET_VAR_TYPE.TEXT;
            transformedRow[col] = {
                type: fieldType,
                value: colValue,
            };
        }
    }
    /**
     * Transform UPLOAD type dataset
     */
    async transformUploadDataset(datasetData, test, options) {
        return Promise.all(datasetData.map(async (item) => {
            const transformedRow = {
                datasetRecordId: item?.datasetRecordId,
            };
            const columnPromises = Object.entries(item).map(async ([col, colData]) => {
                if ([
                    constants_1.DATASET_VAR_TYPE.VOICE,
                    constants_1.DATASET_VAR_TYPE.VIDEO,
                    constants_1.DATASET_VAR_TYPE.IMAGE,
                    constants_1.DATASET_VAR_TYPE.CONVERSATION,
                ].includes(colData?.type)) {
                    await this.processMediaColumn(col, colData, transformedRow, test, options);
                }
                else {
                    transformedRow[col] = colData;
                }
            });
            await Promise.all(columnPromises);
            return transformedRow;
        }));
    }
    /**
     * Process media column (voice, video, image, conversation)
     */
    async processMediaColumn(col, colData, transformedRow, test, options) {
        const isInVars = test?.vars && col in test.vars;
        const isInAssert = test?.assert?.some((assert) => assert?.datasetColumn === col);
        if (isInVars || isInAssert) {
            try {
                const blobUrl = colData?.voiceUrl ||
                    colData?.videoUrl ||
                    colData?.imageUrl ||
                    colData?.conversationUrl;
                if (colData?.type === constants_1.DATASET_VAR_TYPE.VOICE && blobUrl) {
                    await this.processVoiceBlob(blobUrl, transformedRow, col, options);
                }
                else if (colData?.type === constants_1.DATASET_VAR_TYPE.CONVERSATION && blobUrl) {
                    await this.processConversationBlob(blobUrl, transformedRow, col);
                }
                else if (blobUrl) {
                    // For other media types, just decode the URL
                    transformedRow[col] = decodeURI(blobUrl);
                }
            }
            catch (error) {
                logger_1.default.error(`Error fetching blob: ${error}`);
                transformedRow[col] = colData?.value;
            }
        }
        else {
            transformedRow[col] = colData?.value;
        }
    }
    /**
     * Process voice blob
     */
    async processVoiceBlob(blobUrl, transformedRow, col, options) {
        let resolvedBlobUrl = blobUrl;
        try {
            resolvedBlobUrl = await this.runWithVoiceConcurrency(() => (0, fetchBlobFilesFromAzure_1.fetchBlobFileFromAzure)(blobUrl, options.token, options.apiKey, 17520));
            transformedRow[col] = decodeURI(resolvedBlobUrl);
        }
        catch (e) {
            logger_1.default.error(`Error fetching blob URL from Azure: ${e}`);
            transformedRow[col] = decodeURI(resolvedBlobUrl);
        }
    }
    /**
     * Process conversation blob
     */
    async processConversationBlob(blobUrl, transformedRow, col) {
        try {
            const response = await fetch(decodeURI(blobUrl));
            if (response.ok) {
                const content = await response.text();
                transformedRow[col] = content;
            }
            else {
                logger_1.default.error(`Failed to fetch conversation file: ${response.status}`);
                transformedRow[col] = decodeURI(blobUrl);
            }
        }
        catch (fetchError) {
            logger_1.default.error(`Error fetching conversation content: ${fetchError}`);
            transformedRow[col] = decodeURI(blobUrl);
        }
    }
    /**
     * Expand test for each row in the dataset
     */
    async expandTestForDatasetRows(test, datasetData, options) {
        return Promise.all(datasetData.map(async (row) => {
            // Process vars
            const newVars = await this.processVars(test, row, options);
            // Process assertions
            const processedAssertions = await this.processAssertions(test, row, options);
            return {
                vars: newVars,
                assert: processedAssertions,
                description: test?.description,
                datasetRecordId: row?.datasetRecordId,
                parentPromptId: options.parentPromptId,
            };
        }));
    }
    /**
     * Process vars from test and row data
     */
    async processVars(test, row, options) {
        const vars = test?.vars || {};
        const newVars = {};
        const varPromises = Object.keys(vars)
            .filter((key) => row[key] !== undefined)
            .map(async (key) => {
            // Extract value from row - handle both plain values and structured { type, value } format
            let rowValue = row[key];
            if (rowValue && typeof rowValue === 'object' && 'value' in rowValue && 'type' in rowValue) {
                // Structured format: extract the value
                rowValue = rowValue.value;
            }
            const result = await this.processSrcKey(rowValue, options.token, `vars.${key}`, options.orgId, options.projectId);
            return { key, result };
        });
        const varResults = await Promise.all(varPromises);
        varResults.forEach(({ key, result }) => {
            newVars[key] = result;
        });
        return newVars;
    }
    /**
     * Process assertions from test and row data
     */
    async processAssertions(test, row, options) {
        if (!test?.assert) {
            return test?.assert;
        }
        return Promise.all(test.assert.map(async (item) => {
            if (Array.isArray(item?.value)) {
                return this.processArrayAssertion(item, row, options);
            }
            else if (item?.datasetColumn) {
                return this.processSingleColumnAssertion(item, row, options);
            }
            else {
                return this.processDirectAssertion(item, options);
            }
        }));
    }
    /**
     * Process assertion with array value
     */
    async processArrayAssertion(item, row, options) {
        const processedValue = await Promise.all(item.value.map(async (arrayItem) => {
            if (typeof arrayItem === 'object' && arrayItem !== null && arrayItem.datasetColumn) {
                // This array item has a datasetColumn, replace with actual value and process src
                let value = row[arrayItem.datasetColumn] || arrayItem.value;
                // Extract value from structured format if needed
                if (value && typeof value === 'object' && 'value' in value && 'type' in value) {
                    value = value.value;
                }
                return await this.processSrcKey(value, options.token, `assert.${item.type || 'unknown'}.${arrayItem.datasetColumn}`, options.orgId, options.projectId);
            }
            else if (typeof arrayItem === 'object' &&
                arrayItem !== null &&
                'datasetColumn' in arrayItem &&
                (!arrayItem.datasetColumn || arrayItem.datasetColumn === '')) {
                // This array item has datasetColumn but it's empty, extract just the value
                return await this.processSrcKey(arrayItem.value, options.token, `assert.${item.type || 'unknown'}.array`, options.orgId, options.projectId);
            }
            else {
                // This array item doesn't have a datasetColumn, process src if present
                return await this.processSrcKey(arrayItem.value || arrayItem, options.token, `assert.${item.type || 'unknown'}.array`, options.orgId, options.projectId);
            }
        }));
        return {
            ...item,
            value: this.replaceMetricColumnValue(item?.value || '', item.datasetColumn, processedValue),
        };
    }
    /**
     * Process assertion with single datasetColumn
     */
    async processSingleColumnAssertion(item, row, options) {
        let value = row[item.datasetColumn];
        // Extract value from structured format if needed
        if (value && typeof value === 'object' && 'value' in value && 'type' in value) {
            value = value.value;
        }
        const processedValue = await this.processSrcKey(value, options.token, `assert.${item.type || 'unknown'}.${item.datasetColumn}`, options.orgId, options.projectId);
        return {
            ...item,
            type: item?.type,
            value: this.replaceMetricColumnValue(item?.value || '', item.datasetColumn, processedValue),
        };
    }
    /**
     * Process direct assertion (no datasetColumn)
     */
    async processDirectAssertion(item, options) {
        return await this.processSrcKey(item, options.token, `assert.${item.type || 'unknown'}`, options.orgId, options.projectId);
    }
}
exports.TransformDatasetService = TransformDatasetService;
// Global concurrency control for voice/media blob fetches from Azure
TransformDatasetService.MAX_VOICE_CONCURRENCY = 5;
TransformDatasetService.currentVoiceConcurrency = 0;
TransformDatasetService.voiceQueue = [];
//# sourceMappingURL=transformDatasetService.js.map