"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.initiateAdvancedOutboundCall = initiateAdvancedOutboundCall;
// @ts-ignore - twilio types may not be available
const axios_1 = __importDefault(require("axios"));
const twilio_1 = __importDefault(require("twilio"));
const call_data_storage_1 = require("./call-data-storage");
const call_billing_1 = require("./call-billing");
const TWILIO_ACCOUNT_SID = process.env.TWILIO_ACCOUNT_SID ?? '';
const TWILIO_AUTH_TOKEN = process.env.TWILIO_AUTH_TOKEN ?? '';
const TWILIO_PHONE_NUMBER = process.env.TWILIO_PHONE_NUMBER ?? '';
const TWILIO_BASE_URL = process.env.TWILIO_BASE_URL ?? '';
let twilioClient = null;
if (TWILIO_ACCOUNT_SID && TWILIO_AUTH_TOKEN) {
    try {
        twilioClient = (0, twilio_1.default)(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN);
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        console.warn(`[Phone Util] Failed to initialize Twilio client: ${message}`);
        twilioClient = null;
    }
}
/**
 * Wait for call to complete by polling Twilio API
 */
async function waitForCallCompletion(callSid, timeoutMs = 300000) {
    const startTime = Date.now();
    const pollInterval = 2000; // Check every 2 seconds
    while (Date.now() - startTime < timeoutMs) {
        if (!twilioClient) {
            throw new Error('Twilio client not initialized');
        }
        try {
            const call = await twilioClient.calls(callSid).fetch();
            if (call.status === 'completed' ||
                call.status === 'failed' ||
                call.status === 'busy' ||
                call.status === 'no-answer') {
                return;
            }
        }
        catch (error) {
            console.error(`Error checking call status: ${error}`);
        }
        await new Promise((resolve) => setTimeout(resolve, pollInterval));
    }
    throw new Error(`Call ${callSid} did not complete within ${timeoutMs}ms`);
}
/**
 * Fetch call recording from Twilio
 */
async function fetchCallRecording(callSid, timeoutMs = 300000) {
    if (!twilioClient) {
        throw new Error('Twilio client not initialized');
    }
    const startTime = Date.now();
    const pollInterval = 3000; // Check every 3 seconds
    while (Date.now() - startTime < timeoutMs) {
        try {
            // Fetch recordings for this call
            const recordings = await twilioClient.recordings.list({ callSid, limit: 1 });
            if (recordings && recordings.length > 0) {
                const recording = recordings[0];
                // Log recording status for debugging
                if (recording.status !== 'completed') {
                    console.log(`Recording status for call ${callSid}: ${recording.status}`);
                }
                // If recording is completed, download it
                if (recording.status === 'completed') {
                    // Build full URL for the recording
                    // Twilio returns relative URIs, so we need to prepend the API base URL
                    let recordingUrl = recording.uri.replace('.json', '.wav');
                    if (recordingUrl.startsWith('/')) {
                        recordingUrl = `https://api.twilio.com${recordingUrl}`;
                    }
                    // Download the recording
                    const recordingResponse = await axios_1.default.get(recordingUrl, {
                        responseType: 'arraybuffer',
                        auth: {
                            username: TWILIO_ACCOUNT_SID || '',
                            password: TWILIO_AUTH_TOKEN || '',
                        },
                    });
                    return {
                        url: recordingUrl,
                        duration: recording.duration || '0',
                        sid: recording.sid,
                        audioData: Buffer.from(recordingResponse.data),
                    };
                }
                // If recording is processing, continue polling (don't throw error)
                if (recording.status === 'processing') {
                    console.log(`Recording for call ${callSid} is still processing, waiting... (${Math.round((Date.now() - startTime) / 1000)}s elapsed)`);
                    // Continue polling - don't throw error
                }
                else if (recording.status === 'deleted') {
                    // Only throw error for deleted status
                    throw new Error(`Recording for call ${callSid} has been deleted`);
                }
            }
        }
        catch (error) {
            // If recording not found yet, continue polling
            if (error.status === 404 || error.code === 20404) {
                // Recording not found yet, continue polling
                console.log(`Recording not found yet for call ${callSid}, continuing to poll...`);
            }
            else if (error.message && error.message.includes('is in')) {
                // Re-throw status errors
                throw error;
            }
            else {
                console.error(`Error fetching recording: ${error.message}`);
            }
        }
        await new Promise((resolve) => setTimeout(resolve, pollInterval));
    }
    throw new Error(`Recording for call ${callSid} not available within ${timeoutMs}ms`);
}
/**
 * Initiates an advanced outbound call with configurable steps
 * @param to - Phone number to call (E.164 format, e.g., +1234567890)
 * @param steps - Array of call steps to execute in sequence
 * @param systemPrompt - Custom system prompt for the AI assistant (optional)
 * @param overallDelay - Delay in seconds before the first step executes (optional, default: 5)
 * @param totalCallLength - Maximum call duration in seconds (optional, default: 180, max: 14400)
 * @param aiSpeaksFirst - Whether AI should speak first in realtime conversation (optional, default: true for outbound calls)
 * @returns Promise with call information
 */
async function initiateAdvancedOutboundCall({ to, steps, systemPrompt, overallDelay = 5, totalCallLength = 180, aiSpeaksFirst = true, workspaceId, projectId, }) {
    if (!twilioClient) {
        throw new Error('Twilio client not initialized. Please set TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN in .env file.');
    }
    if (!TWILIO_PHONE_NUMBER) {
        throw new Error('TWILIO_PHONE_NUMBER not set in .env file.');
    }
    if (!TWILIO_BASE_URL) {
        throw new Error('TWILIO_BASE_URL not set in .env file.');
    }
    if (!to) {
        throw new Error('Missing "to" parameter. Please provide a phone number to call.');
    }
    if (!steps || !Array.isArray(steps) || steps.length === 0) {
        throw new Error('Missing or invalid "steps" parameter. Steps must be a non-empty array.');
    }
    // Default system prompt if not provided
    const DEFAULT_SYSTEM_MESSAGE = `You are a real person named Ashutosh Nayak. If someone asks for your name, tell them your name is Ashutosh Nayak.
    Never mention that you are an AI, AI assistant, AI agent, chatbot, voice assistant, or anything related to technology. You are a real human being having a natural phone conversation.
    Behave like a greedy customer who asks more loan amount. Tries to convince the caller to give more loan amount by creating some stories.     
    Answer in same language and answer in one sentence.`;
    const finalSystemPrompt = systemPrompt && systemPrompt.trim() ? systemPrompt.trim() : DEFAULT_SYSTEM_MESSAGE;
    // Validate and set overallDelay
    const finalOverallDelay = overallDelay &&
        !Number.isNaN(Number.parseInt(String(overallDelay), 10)) &&
        Number.parseInt(String(overallDelay), 10) >= 0
        ? Number.parseInt(String(overallDelay), 10)
        : 5;
    // Validate and set totalCallLength
    const finalTotalCallLength = totalCallLength &&
        !Number.isNaN(Number.parseInt(String(totalCallLength), 10)) &&
        Number.parseInt(String(totalCallLength), 10) > 0
        ? Number.parseInt(String(totalCallLength), 10)
        : 180;
    // Validate steps
    for (let i = 0; i < steps.length; i++) {
        const step = steps[i];
        if (!step.type ||
            !['keyboard', 'audio', 'realtime', 'delay', 'startAfter', 'next'].includes(step.type)) {
            throw new Error(`Invalid step type at index ${i}. Must be "keyboard", "audio", "realtime", "delay", "startAfter", or "next".`);
        }
        // startAfter and next don't require value, delay can have optional value
        if (step.type !== 'realtime' && step.type !== 'next' && !step.value) {
            throw new Error(`Step at index ${i} (type: ${step.type}) requires a "value" parameter.`);
        }
        // startAfter requires a numeric value (seconds)
        if (step.type === 'startAfter' && step.value) {
            const seconds = Number.parseInt(step.value, 10);
            if (Number.isNaN(seconds) || seconds < 0) {
                throw new Error(`Step at index ${i} (type: startAfter) requires a valid positive number (seconds).`);
            }
        }
    }
    // Ensure last step is realtime
    /*   const lastStep = steps[steps.length - 1];
      if (lastStep.type !== 'realtime') {
        steps.push({ type: 'realtime' });
      } */
    // Build answer URL (must include full API path)
    const answerUrl = `${TWILIO_BASE_URL}/api/phone/outbound-call-advanced-answered?outbound=${aiSpeaksFirst}`;
    // Store call data before making the call (webhook will need this)
    // Note: We'll store it after getting the callSid, but we need to prepare it
    const callDataForStorage = {
        steps,
        systemPrompt: finalSystemPrompt,
        overallDelay: finalOverallDelay,
        aiSpeaksFirst,
        workspaceId,
        projectId,
    };
    // Make the outbound call
    const call = await twilioClient.calls.create({
        to,
        from: TWILIO_PHONE_NUMBER,
        url: answerUrl,
        method: 'POST',
        record: true, // Enable call recording
        timeLimit: finalTotalCallLength, // Total call length in seconds
    });
    // Store call data for webhook handler BEFORE Twilio calls the webhook
    // This ensures data is available when the webhook is triggered
    await (0, call_data_storage_1.storeCallData)(call.sid, callDataForStorage);
    console.log(`[Phone Util] Stored call data for SID: ${call.sid}`);
    console.log(`[Phone Util] Call data stored: ${JSON.stringify(callDataForStorage, null, 2)}`);
    // Prepare call data to return
    const callData = {
        steps,
        startTime: Date.now(),
        callSid: call.sid,
        systemPrompt: finalSystemPrompt,
        overallDelay: finalOverallDelay,
        totalCallLength: finalTotalCallLength,
    };
    // Wait for call to complete and fetch recording
    let recording;
    try {
        await waitForCallCompletion(call.sid);
        // Set timeout based on call length: call duration + 5 minutes buffer for processing
        // Convert to milliseconds and add buffer (increased buffer for processing time)
        const recordingTimeout = finalTotalCallLength * 1000 + 300000; // call length + 5 min buffer
        console.log(`Waiting for recording for call ${call.sid} (timeout: ${recordingTimeout / 1000}s)...`);
        recording = await fetchCallRecording(call.sid, recordingTimeout);
        console.log(`Recording fetched successfully for call ${call.sid}`);
    }
    catch (error) {
        console.error(`Error waiting for call recording: ${error.message}`);
        // Continue even if recording fetch fails - we'll return without recording
        recording = undefined;
    }
    // Fetch final call status
    const finalCall = await twilioClient.calls(call.sid).fetch();
    // Only include recording if audioData is present
    let recordingResult;
    if (recording && recording.audioData) {
        // Convert Buffer to base64 for JSON serialization
        const audioDataBase64 = recording.audioData.toString('base64');
        // Track Twilio call duration for billing
        if (recording?.duration) {
            const durationSeconds = typeof recording.duration === 'string' ? Number.parseFloat(recording.duration) : recording.duration;
            if (!Number.isNaN(durationSeconds) && durationSeconds > 0) {
                (0, call_billing_1.trackTwilioCallDuration)(call.sid, durationSeconds).catch((error) => {
                    console.error(`[Billing] Error tracking Twilio call duration: ${error}`);
                });
            }
        }
        recordingResult = {
            url: recording.url,
            duration: recording.duration,
            sid: recording.sid,
            audioDataBase64,
            mimeType: 'audio/wav', // Twilio recordings are in WAV format
        };
    }
    else {
        recordingResult = undefined;
    }
    await new Promise((resolve) => setTimeout(resolve, 1000)); // 2 second buffer
    (0, call_billing_1.logFinalCallBilling)(call.sid).catch((error) => {
        const err = error;
        console.error(`[Billing] Error logging final billing on call end: ${err.message}`);
    });
    return {
        success: true,
        callSid: call.sid,
        status: finalCall.status,
        to,
        from: TWILIO_PHONE_NUMBER,
        steps: steps.length,
        message: 'Advanced call initiated successfully',
        recording: recordingResult,
        callData,
    };
}
//# sourceMappingURL=phone-util.js.map