"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.convertTextToAudioWithProvider = convertTextToAudioWithProvider;
exports.sanitizeAudioFileNameSegment = sanitizeAudioFileNameSegment;
exports.convertTextToAudioFile = convertTextToAudioFile;
exports.registerTtsProvider = registerTtsProvider;
exports.getProviderMetadata = getProviderMetadata;
exports.getRegisteredProviders = getRegisteredProviders;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const uuid_1 = require("uuid");
const bson_1 = require("bson");
const constants_1 = require("../../constants");
const logger_1 = __importDefault(require("../../logger"));
const azureSpeech_1 = require("../../providers/tts/azureSpeech");
const elevenlabs_1 = require("../../providers/tts/elevenlabs");
const findWorkspaceProvider_1 = require("../findWorkspaceProvider/findWorkspaceProvider");
const defaults_1 = require("../../providers/defaults");
const sendBillingLogs_1 = require("../sendBillingLogs");
// ============================================================================
// Types & Interfaces
// ============================================================================
const COST_PER_MILLION_CHARS_ELEVENLABS = 62.15;
const COST_PER_MILLION_CHARS_AZURE = 20.8;
// ============================================================================
// Format Converters
// ============================================================================
class ElevenLabsFormatConverter {
    convert(outputFormat, sampleRate) {
        const normalizedFormat = outputFormat.toLowerCase();
        const normalizedRate = String(sampleRate || '').trim();
        // If caller already provided an ElevenLabs `output_format` string, pass through.
        if (ElevenLabsFormatConverter.SUPPORTED_ELEVENLABS_OUTPUT_FORMATS.has(normalizedFormat)) {
            return normalizedFormat;
        }
        // Back-compat: allow (mp3|wav|opus) + sampleRate, map to a supported output_format.
        if (normalizedFormat === 'wav' || normalizedFormat === 'pcm') {
            const pcm = `pcm_${normalizedRate || '44100'}`.toLowerCase();
            if (ElevenLabsFormatConverter.SUPPORTED_ELEVENLABS_OUTPUT_FORMATS.has(pcm)) {
                return pcm;
            }
            logger_1.default.warn(`Unsupported ElevenLabs sample rate: ${sampleRate} for format ${outputFormat}, defaulting to pcm_44100`);
            return 'pcm_44100';
        }
        if (normalizedFormat === 'mp3') {
            const mp3Map = {
                '22050': 'mp3_22050_32',
                '24000': 'mp3_24000_48',
                // ElevenLabs does not expose mp3_48000_*; use a high quality 44.1k preset.
                '44100': 'mp3_44100_128',
                '48000': 'mp3_44100_192',
            };
            const chosen = mp3Map[normalizedRate] || mp3Map['44100'];
            return chosen;
        }
        if (normalizedFormat === 'opus') {
            // ElevenLabs exposes opus only at 48k with multiple bitrates.
            return 'opus_48000_128';
        }
        logger_1.default.warn(`Unsupported ElevenLabs format: ${outputFormat}, defaulting to pcm_44100`);
        return 'pcm_44100';
    }
}
/**
 * ElevenLabs supports explicit `output_format` strings (not just "mp3/wav/opus").
 * Source: provider docs / API contract.
 */
ElevenLabsFormatConverter.SUPPORTED_ELEVENLABS_OUTPUT_FORMATS = new Set([
    'pcm_8000',
    'pcm_16000',
    'pcm_22050',
    'pcm_24000',
    'pcm_32000',
    'pcm_44100',
    'pcm_48000',
    'mp3_22050_32',
    'mp3_24000_48',
    'mp3_44100_32',
    'mp3_44100_64',
    'mp3_44100_96',
    'mp3_44100_128',
    'mp3_44100_192',
    'opus_48000_32',
    'opus_48000_64',
    'opus_48000_96',
    'opus_48000_128',
    'opus_48000_192',
]);
class AzureSpeechFormatConverter {
    constructor() {
        this.formatMap = {
            mp3: {
                '16000': 'audio-16khz-128kbitrate-mono-mp3',
                '24000': 'audio-24khz-160kbitrate-mono-mp3',
                '48000': 'audio-48khz-192kbitrate-mono-mp3',
            },
            wav: {
                '16000': 'riff-16khz-16bit-mono-pcm',
                '24000': 'riff-24khz-16bit-mono-pcm',
            },
            opus: {
                '16000': 'ogg-opus-16khz-16bit-mono-opus',
                '24000': 'ogg-opus-24khz-16bit-mono-opus',
                '48000': 'ogg-opus-48khz-16bit-mono-opus',
            },
        };
    }
    convert(outputFormat, sampleRate) {
        const normalizedFormat = outputFormat.toLowerCase();
        const formatOptions = this.formatMap[normalizedFormat];
        if (!formatOptions) {
            logger_1.default.warn(`Unsupported Azure Speech format: ${outputFormat}, defaulting to WAV 16kHz`);
            return 'riff-16khz-16bit-mono-pcm';
        }
        const formatString = formatOptions[sampleRate];
        if (!formatString) {
            logger_1.default.warn(`Unsupported Azure Speech sample rate: ${sampleRate} for format ${outputFormat}, defaulting to 16kHz`);
            return formatOptions['16000'] || 'riff-16khz-16bit-mono-pcm';
        }
        return formatString;
    }
}
// ============================================================================
// Provider Factories
// ============================================================================
class ElevenLabsProviderFactory {
    constructor() {
        this.formatConverter = new ElevenLabsFormatConverter();
        this.metadata = {
            name: 'elevenlabs',
            displayName: 'ElevenLabs',
            defaultVoiceId: 'JBFqnCBsd6RMkjVDRZzb',
            defaultModelId: 'eleven_v3',
            supportedFormats: ['mp3', 'wav', 'opus'],
            supportedSampleRates: ['8000', '16000', '22050', '24000', '32000', '44100', '48000'],
            costPerMillionChars: COST_PER_MILLION_CHARS_ELEVENLABS,
        };
    }
    create(config, outputFormat, sampleRate) {
        const formatString = this.formatConverter.convert(outputFormat, sampleRate);
        logger_1.default.debug(`ElevenLabsProviderFactory: outputFormat=${outputFormat}, sampleRate=${sampleRate}, formatString=${formatString}`);
        return new elevenlabs_1.ElevenLabsTtsProvider(this.metadata.defaultModelId, {
            config: {
                voiceId: config.voiceId || this.metadata.defaultVoiceId,
                modelId: config.modelId || this.metadata.defaultModelId,
                outputFormat: formatString,
                stability: config.stability ?? 0.5,
                similarityBoost: config.similarityBoost ?? 0.75,
                style: config.style,
                useSpeakerBoost: config.useSpeakerBoost,
            },
        });
    }
    getMetadata() {
        return this.metadata;
    }
}
class AzureSpeechProviderFactory {
    constructor() {
        this.formatConverter = new AzureSpeechFormatConverter();
        this.metadata = {
            name: 'azure',
            defaultModelId: 'azure-speech',
            displayName: 'Azure Speech',
            defaultVoiceName: 'en-US-AndrewMultilingualNeural',
            supportedFormats: ['mp3', 'wav', 'opus'],
            supportedSampleRates: ['16000', '24000', '48000'],
            costPerMillionChars: COST_PER_MILLION_CHARS_AZURE,
        };
    }
    create(config, outputFormat, sampleRate) {
        const formatString = this.formatConverter.convert(outputFormat, sampleRate);
        return new azureSpeech_1.AzureSpeechTtsProvider('azure-speech', {
            config: {
                language: config.language || 'en-US',
                voiceName: config.voiceName || this.metadata.defaultVoiceName,
                outputFormat: formatString,
                pitch: config.pitch ? String(config.pitch) : undefined,
                rate: config.rate ? String(config.rate) : undefined,
                volume: config.volume ? String(config.volume) : undefined,
            },
        });
    }
    getMetadata() {
        return this.metadata;
    }
}
// ============================================================================
// Provider Registry
// ============================================================================
class TtsProviderRegistry {
    constructor() {
        this.factories = new Map();
        this.fallbackOrder = ['elevenlabs', 'azure'];
        // Register built-in providers
        this.register('elevenlabs', new ElevenLabsProviderFactory());
        this.register('azure', new AzureSpeechProviderFactory());
    }
    /**
     * Register a new TTS provider factory
     * @param name - Provider identifier
     * @param factory - Factory instance that creates the provider
     */
    register(name, factory) {
        this.factories.set(name, factory);
        logger_1.default.debug(`Registered TTS provider: ${name}`);
    }
    /**
     * Get a provider factory by name
     */
    getFactory(name) {
        return this.factories.get(name);
    }
    /**
     * Get provider metadata
     */
    getMetadata(name) {
        return this.factories.get(name)?.getMetadata();
    }
    /**
     * Get fallback provider order
     */
    getFallbackOrder() {
        return [...this.fallbackOrder];
    }
    /**
     * Set custom fallback order
     */
    setFallbackOrder(order) {
        this.fallbackOrder = order;
    }
    /**
     * Get all registered provider names
     */
    getRegisteredProviders() {
        return Array.from(this.factories.keys());
    }
}
// Singleton instance
const providerRegistry = new TtsProviderRegistry();
// ============================================================================
// Billing Service
// ============================================================================
class TtsBillingService {
    /**
     * Calculate TTS cost based on character count and provider
     */
    calculateCost(characterCount, providerName) {
        if (characterCount <= 0) {
            return 0;
        }
        const metadata = providerRegistry.getMetadata(providerName);
        if (!metadata) {
            logger_1.default.warn(`Unknown provider for billing: ${providerName}`);
            return 0;
        }
        return (characterCount / 1000000) * metadata.costPerMillionChars;
    }
    /**
     * Send billing logs to API
     */
    async sendBillingLog(workspaceId, projectId, providerName, characterCount, outputFormat, sampleRate, language) {
        try {
            const cost = this.calculateCost(characterCount, providerName);
            if (cost <= 0) {
                return;
            }
            const logEntry = {
                id: new bson_1.ObjectId().toHexString(),
                workspaceId,
                projectId,
                type: 'text_to_audio',
                typeId: (0, uuid_1.v4)(),
                provider: providerName,
                model: providerRegistry.getMetadata(providerName)?.defaultModelId || 'eleven_v3',
                currency: 'TRUCREDS',
                cost,
                tokenUsage: {
                    prompt: 0,
                    completion: 0,
                    total: 0,
                    cached: 0,
                    characters: characterCount,
                },
                timestamp: new Date().toISOString(),
                metadata: {
                    provider: providerName,
                    outputFormat,
                    sampleRate,
                    language,
                    characterCount,
                },
            };
            await (0, sendBillingLogs_1.sendBillingLogsToApi)([logEntry]);
            logger_1.default.debug(`Sent billing log for ${characterCount} characters using ${providerName}`);
        }
        catch (error) {
            logger_1.default.error(`Error sending billing logs: ${error}`);
            // Don't throw - billing failure shouldn't break audio generation
        }
    }
}
const billingService = new TtsBillingService();
class ProviderResolver {
    /**
     * Resolve and create TTS provider from workspace configuration
     */
    async resolveFromWorkspace(workspaceId, outputFormat, sampleRate, language) {
        const providerConfig = await (0, findWorkspaceProvider_1.findProvider)(workspaceId, constants_1.PROVIDER_TYPE.TTS);
        if (!providerConfig) {
            return null;
        }
        // Map TTS_PROVIDER enum to our provider names
        const providerNameMap = {
            [constants_1.TTS_PROVIDER.ELEVENLABS]: 'elevenlabs',
            [constants_1.TTS_PROVIDER.AZURE_SPEECH]: 'azure',
        };
        const providerName = providerNameMap[providerConfig.providerName];
        if (!providerName) {
            logger_1.default.warn(`Unknown TTS provider: ${providerConfig.providerName}, falling back to defaults`);
            return null;
        }
        const factory = providerRegistry.getFactory(providerName);
        if (!factory) {
            logger_1.default.warn(`Provider factory not found: ${providerName}`);
            return null;
        }
        const config = {
            ...(providerConfig.config || {}),
            language: providerConfig.config?.language || language,
        };
        const provider = factory.create(config, outputFormat, sampleRate);
        return { provider, providerName };
    }
    /**
     * Create provider using fallback order
     */
    async createWithFallback(outputFormat, sampleRate, language, text) {
        const fallbackOrder = providerRegistry.getFallbackOrder();
        for (const providerName of fallbackOrder) {
            const factory = providerRegistry.getFactory(providerName);
            if (!factory) {
                logger_1.default.warn(`Provider factory not found: ${providerName}, skipping`);
                continue;
            }
            try {
                const metadata = factory.getMetadata();
                const defaultConfig = {
                    voiceId: metadata.defaultVoiceId,
                    modelId: metadata.defaultModelId,
                    voiceName: metadata.defaultVoiceName,
                    language,
                };
                const provider = factory.create(defaultConfig, outputFormat, sampleRate);
                // Test the provider by making a call
                const testResponse = await provider.callApi(text);
                if (!testResponse.error && testResponse.output) {
                    logger_1.default.debug(`Successfully initialized ${metadata.displayName} as fallback provider`);
                    return { provider, providerName };
                }
                logger_1.default.warn(`${metadata.displayName} failed: ${testResponse.error}, trying next provider`);
            }
            catch (error) {
                logger_1.default.warn(`Error initializing ${providerName}: ${error}, trying next provider`);
            }
        }
        throw new Error('All fallback TTS providers failed to initialize');
    }
}
const providerResolver = new ProviderResolver();
// ============================================================================
// Main Export Functions
// ============================================================================
/**
 * Converts text to audio using configured TTS provider from workspace
 * Falls back to default providers if no provider is configured
 *
 * @param text - Text to convert to audio
 * @param workspaceId - Workspace ID to find TTS provider configuration
 * @param outputFormat - Output format: 'mp3', 'wav', or 'opus'
 * @param sampleRate - Sample rate: '16000', '22050', '24000', '44100', '48000'
 * @param language - Language code (optional, defaults to 'en-US')
 * @param options - Optional parameters for billing
 * @param options.projectId - Project ID for billing logs
 * @param options.sendBillingLogs - Whether to send billing logs (default: false)
 * @returns Base64 encoded audio string
 */
async function convertTextToAudioWithProvider(text, workspaceId, outputFormat = 'wav', sampleRate = '44100', language = 'en-US', options) {
    try {
        // Normalize input
        const normalizedFormat = outputFormat.toLowerCase();
        const normalizedLanguage = language || 'en-US';
        // Fetch audioProvider from getDefaultProviders (for reference/logging)
        let defaultAudioProvider = null;
        try {
            const defaultProviders = await (0, defaults_1.getDefaultProviders)(undefined, workspaceId);
            defaultAudioProvider = defaultProviders.audioProvider;
            if (defaultAudioProvider) {
                logger_1.default.debug(`Audio provider from getDefaultProviders: ${defaultAudioProvider.id()} (for workspace ${workspaceId})`);
            }
        }
        catch (error) {
            logger_1.default.warn(`Failed to fetch audioProvider from getDefaultProviders: ${error}`);
        }
        // Try to resolve provider from workspace configuration
        let result = await providerResolver.resolveFromWorkspace(workspaceId, normalizedFormat, sampleRate, normalizedLanguage);
        let usedProvider;
        // If no workspace provider, use fallback
        if (!result) {
            logger_1.default.debug(`No TTS provider configured for workspace ${workspaceId}, using fallback providers`);
            result = await providerResolver.createWithFallback(normalizedFormat, sampleRate, normalizedLanguage, text);
            usedProvider = result.providerName;
            // Send billing for early return case (fallback provider)
            if (options?.sendBillingLogs && options?.projectId && text.length > 0) {
                await billingService.sendBillingLog(workspaceId, options.projectId, usedProvider, text.length, normalizedFormat, sampleRate, normalizedLanguage);
            }
            // Call provider to get output
            const response = await result.provider.callApi(text);
            if (response.error || !response.output) {
                throw new Error(response.error || 'No output from fallback provider');
            }
            return response.output;
        }
        usedProvider = result.providerName;
        const provider = result.provider;
        // Call the TTS provider to generate audio
        const response = await provider.callApi(text);
        if (response.error) {
            logger_1.default.error(`TTS provider error: ${response.error}`);
            throw new Error(`Failed to generate audio: ${response.error}`);
        }
        if (!response.output) {
            logger_1.default.error('TTS provider returned no output');
            throw new Error('Failed to generate audio: No output from TTS provider');
        }
        logger_1.default.debug(`Successfully generated audio using ${usedProvider}`);
        // Send billing logs if requested
        if (options?.sendBillingLogs && options?.projectId && text.length > 0) {
            await billingService.sendBillingLog(workspaceId, options.projectId, usedProvider, text.length, normalizedFormat, sampleRate, normalizedLanguage);
        }
        return response.output;
    }
    catch (error) {
        logger_1.default.error(`Error converting text to audio: ${error}`);
        throw error;
    }
}
/**
 * Converts text to audio file using configured TTS provider
 * Saves the audio to a file and returns the file path
 *
 * @param text - Text to convert to audio
 * @param workspaceId - Workspace ID to find TTS provider configuration
 * @param outputFormat - Output format: 'mp3', 'wav', or 'opus' (.webm/.ogg)
 * @param sampleRate - Sample rate: '16000', '22050', '24000', '44100', '48000'
 * @param columnName - Column name for file naming
 * @param resultCounter - Counter for unique file naming
 * @param audioFolder - Folder to save audio files
 * @param language - Language code (optional, defaults to 'en-US')
 * @param projectId - Project ID for billing logs
 * @returns File path of generated audio
 */
/** Flatten voice column names (e.g. "About_Him/Her") into a single safe filename segment. */
function sanitizeAudioFileNameSegment(name) {
    const flattened = String(name ?? '')
        .replace(/[/\\]/g, '_')
        .replace(/[:*?"<>|]/g, '_')
        .replace(/\s+/g, '_')
        .replace(/_+/g, '_')
        .replace(/^\.+/, '')
        .trim();
    return flattened || 'voice';
}
async function convertTextToAudioFile(text, workspaceId, outputFormat, sampleRate, columnName, resultCounter, audioFolder, language = 'en-US', projectId) {
    try {
        // Get base64 audio from provider
        const base64Audio = await convertTextToAudioWithProvider(text, workspaceId, outputFormat, sampleRate, language, {
            projectId,
            sendBillingLogs: true, // Always send billing logs when generating a file
        });
        // Determine file extension based on outputFormat (dataset voice uploads allow .wav, .mp3, .opus)
        const extensionMap = {
            mp3: 'mp3',
            wav: 'wav',
            opus: 'opus',
        };
        const fileExtension = extensionMap[outputFormat.toLowerCase()] || 'wav';
        const safeColumnName = sanitizeAudioFileNameSegment(columnName);
        const audioFileName = path.join(audioFolder, `${resultCounter}-${safeColumnName}.${fileExtension}`);
        fs.mkdirSync(path.dirname(audioFileName), { recursive: true });
        // Convert base64 to buffer and write to file
        const audioBuffer = Buffer.from(base64Audio, 'base64');
        fs.writeFileSync(audioFileName, audioBuffer);
        logger_1.default.debug(`Generated audio file: ${audioFileName}`);
        return audioFileName;
    }
    catch (error) {
        logger_1.default.error(`Error generating audio file: ${error}`);
        throw error;
    }
}
// ============================================================================
// Public API for Extensibility
// ============================================================================
/**
 * Register a new TTS provider
 * This allows external code to add custom TTS providers
 *
 * @example
 * ```typescript
 * registerTtsProvider('custom', {
 *   create: (config, format, rate) => new CustomProvider(config),
 *   getMetadata: () => ({
 *     name: 'custom',
 *     displayName: 'Custom TTS',
 *     costPerMillionChars: 50.0,
 *     supportedFormats: ['mp3', 'wav'],
 *     supportedSampleRates: ['44100'],
 *   }),
 * });
 * ```
 */
function registerTtsProvider(name, factory) {
    providerRegistry.register(name, factory);
}
/**
 * Get metadata for a registered provider
 */
function getProviderMetadata(name) {
    return providerRegistry.getMetadata(name);
}
/**
 * Get all registered provider names
 */
function getRegisteredProviders() {
    return providerRegistry.getRegisteredProviders();
}
//# sourceMappingURL=convertTextToAudioWithProvider.js.map