/* global axios */ import ApiClient from '../ApiClient'; class VoiceAPI extends ApiClient { constructor() { // Use 'voice' as the resource with accountScoped: true super('voice', { accountScoped: true }); // Client-side Twilio device this.device = null; this.activeConnection = null; this.initialized = false; } // Initiate a call to a contact initiateCall(contactId) { if (!contactId) { throw new Error('Contact ID is required to initiate a call'); } // Based on the route definition, the correct URL path is /api/v1/accounts/{accountId}/contacts/{contactId}/call // The endpoint is defined in the contacts namespace, not voice namespace return axios.post(`${this.baseUrl().replace('/voice', '')}/contacts/${contactId}/call`); } // End an active call endCall(callSid, conversationId) { if (!conversationId) { throw new Error('Conversation ID is required to end a call'); } if (!callSid) { throw new Error('Call SID is required to end a call'); } // Validate call SID format - Twilio call SID starts with 'CA' or 'TJ' if (!callSid.startsWith('CA') && !callSid.startsWith('TJ')) { throw new Error( 'Invalid call SID format. Expected Twilio call SID starting with CA or TJ.' ); } return axios.post(`${this.url}/end_call`, { call_sid: callSid, conversation_id: conversationId, id: conversationId, }); } // Get call status getCallStatus(callSid) { if (!callSid) { throw new Error('Call SID is required to get call status'); } return axios.get(`${this.url}/call_status`, { params: { call_sid: callSid }, }); } // Join an incoming call as an agent (join the conference) // This is used for the WebRTC client-side setup, not for phone calls anymore joinCall(params) { // Check if we have individual parameters or a params object const conversationId = params.conversation_id || params.conversationId; const callSid = params.call_sid || params.callSid; const accountId = params.account_id; if (!conversationId) { throw new Error('Conversation ID is required to join a call'); } if (!callSid) { throw new Error('Call SID is required to join a call'); } // Build request payload with proper naming convention const payload = { call_sid: callSid, conversation_id: conversationId, }; // Add account_id if provided if (accountId) { payload.account_id = accountId; } console.log('Calling join_call API endpoint with payload:', payload); return axios.post(`${this.url}/join_call`, payload); } // Reject an incoming call as an agent (don't join the conference) rejectCall(callSid, conversationId) { if (!conversationId) { throw new Error('Conversation ID is required to reject a call'); } if (!callSid) { throw new Error('Call SID is required to reject a call'); } return axios.post(`${this.url}/reject_call`, { call_sid: callSid, conversation_id: conversationId, }); } // Client SDK methods // Get a capability token for the Twilio Client getToken(inboxId) { console.log(`Requesting token for inbox ID: ${inboxId} at URL: ${this.url}/tokens`); // Log the base URL for debugging console.log(`Base URL: ${this.baseUrl()}`); // Check if inboxId is valid if (!inboxId) { console.error('No inbox ID provided for token request'); return Promise.reject(new Error('Inbox ID is required')); } // Add more request details to help debugging return axios.post(`${this.url}/tokens`, { inbox_id: inboxId }, { headers: { 'Content-Type': 'application/json' }, }).catch(error => { // Extract useful error details for debugging const errorInfo = { status: error.response?.status, statusText: error.response?.statusText, data: error.response?.data, url: `${this.url}/tokens`, inboxId, }; console.error('Token request error details:', errorInfo); // Try to extract a more useful error message from the HTML response if it's a 500 error if (error.response?.status === 500 && typeof error.response.data === 'string') { // Look for specific error patterns in the HTML const htmlData = error.response.data; // Check for common Ruby/Rails error patterns const nameMatchResult = htmlData.match(/

(.*?)<\/h2>/); const detailsMatchResult = htmlData.match(/
([\s\S]*?)<\/pre>/);
        
        const errorName = nameMatchResult ? nameMatchResult[1] : null;
        const errorDetails = detailsMatchResult ? detailsMatchResult[1] : null;
        
        if (errorName || errorDetails) {
          const enhancedError = new Error(`Server error: ${errorName || 'Internal Server Error'}`);
          enhancedError.details = errorDetails;
          enhancedError.originalError = error;
          throw enhancedError;
        }
      }
      
      throw error;
    });
  }
  
  // Initialize the Twilio Device
  async initializeDevice(inboxId) {
    // If already initialized, return the existing device after checking its health
    if (this.initialized && this.device) {
      const deviceState = this.device.state;
      console.log('Device already initialized, current state:', deviceState);
      
      // If the device is in a bad state, destroy and reinitialize
      if (deviceState === 'error' || deviceState === 'unregistered') {
        console.log('Device is in a bad state, destroying and reinitializing...');
        try {
          this.device.destroy();
        } catch (e) {
          console.log('Error destroying device:', e);
        }
        this.device = null;
        this.initialized = false;
      } else {
        // Device is in a good state, return it
        return this.device;
      }
    }
    
    // Device needs to be initialized or reinitialized
    try {
      console.log(`Starting Twilio Device initialization for inbox: ${inboxId}`);
      
      // Import the Twilio Voice SDK
      let Device;
      try {
        // We know the package is installed via package.json
        const { Device: TwilioDevice } = await import('@twilio/voice-sdk');
        Device = TwilioDevice;
        console.log('✓ Twilio Voice SDK imported successfully');
      } catch (importError) {
        console.error('✗ Failed to import Twilio Voice SDK:', importError);
        throw new Error(`Failed to load Twilio Voice SDK: ${importError.message}`);
      }
      
      // Validate inbox ID
      if (!inboxId) {
        throw new Error('Inbox ID is required to initialize the Twilio Device');
      }
      
      // Step 1: Get a token from the server
      console.log(`Requesting Twilio token for inbox: ${inboxId}`);
      let response;
      try {
        response = await this.getToken(inboxId);
        console.log(`✓ Token response received with status: ${response.status}`);
      } catch (tokenError) {
        console.error('✗ Token request failed:', tokenError);
        
        // Enhanced error handling for token requests
        if (tokenError.details) {
          // If we already have extracted details from the error, include those
          console.error('Token error details:', tokenError.details);
          throw new Error(`Failed to get token: ${tokenError.message}`);
        }
        
        // Check for specific HTTP error status codes
        if (tokenError.response) {
          const status = tokenError.response.status;
          const data = tokenError.response.data;
          
          if (status === 401) {
            throw new Error('Authentication error: Please check your Twilio credentials');
          } else if (status === 403) {
            throw new Error('Permission denied: You don\'t have access to this inbox');
          } else if (status === 404) {
            throw new Error('Inbox not found or does not have voice capability');
          } else if (status === 500) {
            throw new Error('Server error: The server encountered an error processing your request. Check your Twilio configuration.');
          } else if (data && data.error) {
            throw new Error(`Server error: ${data.error}`);
          }
        }
        
        throw new Error(`Failed to get token: ${tokenError.message}`);
      }
      
      // Validate token response
      if (!response.data || !response.data.token) {
        console.error('✗ Invalid token response data:', response.data);
        
        // Check if we have an error message in the response
        if (response.data && response.data.error) {
          throw new Error(`Server did not return a valid token: ${response.data.error}`);
        } else {
          throw new Error('Server did not return a valid token');
        }
      }
      
      // Check for warnings about missing TwiML App SID
      if (response.data.warning) {
        console.warn('⚠️ Twilio Voice Warning:', response.data.warning);
        
        if (!response.data.has_twiml_app) {
          console.error(
            '🚨 IMPORTANT: Missing TwiML App SID. Browser-based calling requires a ' +
            'TwiML App configured in Twilio Console. Set the Voice Request URL to: ' +
            response.data.twiml_endpoint
          );
        }
      }
      
      // Extract token data
      const { token, identity, voice_enabled, account_sid } = response.data;
      
      // Log diagnostic information
      console.log(`✓ Token data received for identity: ${identity}`);
      console.log(`✓ Voice enabled: ${voice_enabled}`);
      console.log(`✓ Twilio Account SID available: ${!!account_sid}`);
      
      // Log the TwiML endpoint that will be used
      if (response.data.twiml_endpoint) {
        console.log(`✓ TwiML endpoint: ${response.data.twiml_endpoint}`);
      } else {
        console.warn('⚠️ No TwiML endpoint found in token response');
      }
      
      // Check if voice is enabled
      if (!voice_enabled) {
        throw new Error('Voice is not enabled for this inbox. Check your Twilio configuration.');
      }
      
      // Store the TwiML endpoint URL for later use
      this.twimlEndpoint = response.data.twiml_endpoint;

      // Step 2: Create Twilio Device with better options
      const deviceOptions = {
        // Use absolute minimal options - less is more for audio compatibility
        allowIncomingWhileBusy: true, // Allow incoming calls while already on a call
        debug: true, // Enable debug logging
        warnings: true, // Show warnings in console
        disableAudioContextSounds: true, // Disable browser audio context for sounds
        // Add explicit edge parameter - this helps avoid connectivity issues
        edge: ['ashburn', 'sydney', 'roaming'],
        // Explicitly set codec preferences
        codecPreferences: ['opus', 'pcmu'],
        // Add the account ID to any calls made by this device
        appParams: {
          account_id: response.data.account_id,
        }
      };
      
      console.log('Creating Twilio Device with options:', deviceOptions);
      
      try {
        this.device = new Device(token, deviceOptions);
        console.log('✓ Twilio Device created successfully');
      } catch (deviceError) {
        console.error('✗ Failed to create Twilio Device:', deviceError);
        throw new Error(`Failed to create Twilio Device: ${deviceError.message}`);
      }
      
      // Step 3: Set up event listeners with enhanced error handling
      this._setupDeviceEventListeners(inboxId);
      
      // Step 4: Register the device with Twilio
      console.log('Registering Twilio Device...');
      try {
        await this.device.register();
        console.log('✓ Twilio Device registered successfully');
        this.initialized = true;
        return this.device;
      } catch (registerError) {
        console.error('✗ Failed to register Twilio Device:', registerError);
        
        // Handle specific registration errors
        if (registerError.message && registerError.message.includes('token')) {
          throw new Error('Invalid Twilio token. Check your account credentials.');
        } else if (registerError.message && registerError.message.includes('permission')) {
          throw new Error('Missing microphone permission. Please allow microphone access.');
        }
        
        throw new Error(`Failed to register device: ${registerError.message}`);
      }
    } catch (error) {
      // Clear device and initialized flag in case of error
      this.device = null;
      this.initialized = false;
      
      console.error('Failed to initialize Twilio Device:', error);
      
      // Create a detailed error with context for debugging
      const enhancedError = new Error(`Twilio Device initialization failed: ${error.message}`);
      enhancedError.originalError = error;
      enhancedError.inboxId = inboxId;
      enhancedError.timestamp = new Date().toISOString();
      enhancedError.browserInfo = {
        userAgent: navigator.userAgent,
        hasGetUserMedia: !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),
      };
      
      // Add specific advice for known error cases
      if (error.message.includes('permission')) {
        enhancedError.advice = 'Please ensure your browser allows microphone access.';
      } else if (error.message.includes('token')) {
        enhancedError.advice = 'Check your Twilio credentials in the Voice channel settings.';
      } else if (error.message.includes('TwiML')) {
        enhancedError.advice = 'Set up a valid TwiML app in your Twilio console and configure it in the inbox settings.';
      } else if (error.message.includes('configuration')) {
        enhancedError.advice = 'Review your Voice inbox configuration to ensure all required fields are completed.';
      }
      
      throw enhancedError;
    }
  }
  
  // Helper method to set up device event listeners
  _setupDeviceEventListeners(inboxId) {
    if (!this.device) return;
    
    // Remove any existing listeners to prevent duplicates
    this.device.removeAllListeners();
    
    // Add standard event listeners
    this.device.on('registered', () => {
      console.log('✓ Twilio Device registered with Twilio servers');
    });
    
    this.device.on('unregistered', () => {
      console.log('⚠️ Twilio Device unregistered from Twilio servers');
    });
    
    this.device.on('tokenWillExpire', () => {
      console.log('⚠️ Twilio token is about to expire, refreshing...');
      this.getToken(inboxId)
        .then(newTokenResponse => {
          if (newTokenResponse.data && newTokenResponse.data.token) {
            console.log('✓ Successfully obtained new token');
            this.device.updateToken(newTokenResponse.data.token);
          } else {
            console.error('✗ Failed to get a valid token for renewal');
          }
        })
        .catch(tokenError => {
          console.error('✗ Error refreshing token:', tokenError);
        });
    });
    
    this.device.on('incoming', connection => {
      console.log('📞 Incoming call received via Twilio Device');
      this.activeConnection = connection;
      
      // Set up connection-specific events
      this._setupConnectionEventListeners(connection);
    });
    
    this.device.on('error', error => {
      // Enhanced error logging with full details
      const errorDetails = {
        code: error.code,
        message: error.message,
        description: error.description || 'No description',
        twilioErrorObject: error,
        connectionInfo: this.activeConnection ? {
          parameters: this.activeConnection.parameters,
          status: this.activeConnection.status && this.activeConnection.status(),
          direction: this.activeConnection.direction,
        } : 'No active connection',
        deviceState: this.device.state,
        browserInfo: {
          userAgent: navigator.userAgent,
          platform: navigator.platform
        },
        timestamp: new Date().toISOString()
      };
      
      console.error('❌ DETAILED Twilio Device Error:', errorDetails);
      
      // Provide helpful troubleshooting tips based on error code
      switch (error.code) {
        case 31000:
          console.error('⚠️ Error 31000: General Error. This could be an authentication, configuration, or network issue.');
          console.error('31000 Error Details:', {
            sdp: error.sdp || 'No SDP data',
            callState: error.call ? error.call.state : 'No call state',
            connectionState: error.connection ? error.connection.state : 'No connection state',
            peerConnectionState: error.peerConnection ? error.peerConnection.iceConnectionState : 'No ICE state',
            message: error.message,
            twilioError: error,
            info: error.info || 'No additional info',
            solution: 'Check Twilio account status, SDP negotiations, and network connectivity'
          });
          
          // Create a network diagnostic to check connectivity
          fetch('https://status.twilio.com/api/v2/status.json')
            .then(response => response.json())
            .then(data => {
              console.log('Twilio service status check:', data);
            })
            .catch(statusError => {
              console.error('Failed to check Twilio status:', statusError);
            });
          break;
        case 31002:
          console.error('⚠️ Error 31002: Permission Denied. Your browser microphone is blocked or unavailable.');
          break;
        case 31003:
          console.error('⚠️ Error 31003: TwiML App Error. Your TwiML application does not exist or is misconfigured.');
          break;
        case 31005:
          console.error('⚠️ Error 31005: Error sent from gateway in HANGUP. This usually means the TwiML endpoint is not reachable or returning invalid TwiML.');
          console.error('Additional details for 31005:', {
            activeConnection: this.activeConnection ? 'Yes' : 'No',
            deviceState: this.device ? this.device.state : 'No device',
            params: this.activeConnection ? this.activeConnection.parameters : 'No params',
            twimlEndpoint: this.activeConnection && this.activeConnection.parameters ? 
              this.activeConnection.parameters.To : 'Unknown endpoint',
            hangupReason: error.hangupReason || 'Unknown',  // Capture hangup reason
            message: error.message,
            description: error.description,
            customMessage: error.customMessage,
            originalError: error.originalError ? JSON.stringify(error.originalError) : 'None'
          });
          break;
        case 31008:
          console.error('⚠️ Error 31008: Connection Error. The call could not be established.');
          break;
        case 31204:
          console.error('⚠️ Error 31204: ICE Connection Failed. WebRTC connection failure, check firewall settings.');
          break;
        default:
          console.error(`⚠️ Unspecified error with code ${error.code}: ${error.message}`);
      }
    });
    
    this.device.on('connect', connection => {
      console.log('📞 Call connected');
      this.activeConnection = connection;
      this._setupConnectionEventListeners(connection);
    });
    
    this.device.on('disconnect', () => {
      console.log('📞 Call disconnected');
      this.activeConnection = null;
    });
  }
  
  // Set up event listeners for the active connection with enhanced audio diagnostic logging
  _setupConnectionEventListeners(connection) {
    if (!connection) return;
    
    // Add advanced audio debug data
    const getAudioDiagnostics = () => {
      const audioContext = window.AudioContext || window.webkitAudioContext;
      let audioInfo = { supported: !!audioContext };
      
      try {
        if (audioContext) {
          const context = new audioContext();
          audioInfo = {
            ...audioInfo,
            sampleRate: context.sampleRate,
            state: context.state,
            baseLatency: context.baseLatency,
            outputLatency: context.outputLatency,
            destination: {
              maxChannelCount: context.destination.maxChannelCount,
              numberOfInputs: context.destination.numberOfInputs,
              numberOfOutputs: context.destination.numberOfOutputs
            }
          };
          context.close();
        }
      } catch (e) {
        audioInfo.error = e.message;
      }
      
      // Check if microphone is accessible
      let microphoneInfo = { detected: false, active: false, tracks: [] };
      if (window.activeAudioStream) {
        const tracks = window.activeAudioStream.getAudioTracks();
        microphoneInfo = {
          detected: true,
          active: tracks.some(track => track.enabled && track.readyState === 'live'),
          tracks: tracks.map(track => ({
            id: track.id,
            label: track.label,
            enabled: track.enabled,
            muted: track.muted,
            readyState: track.readyState,
            constraints: track.getConstraints()
          }))
        };
      }
      
      return { 
        audioContext: audioInfo, 
        microphone: microphoneInfo,
        speakersMuted: typeof window.speechSynthesis !== 'undefined' ? 
          window.speechSynthesis.speaking === false : 'unknown'
      };
    };
    
    connection.on('error', error => {
      // Significantly enhanced connection error logging with audio diagnostics
      const diagnostics = getAudioDiagnostics();
      
      const connectionErrorDetails = {
        code: error.code,
        message: error.message,
        description: error.description || 'No description',
        twilioErrorObject: error,
        connectionInfo: {
          parameters: connection.parameters,
          status: connection.status && connection.status(),
          direction: connection.direction,
        },
        deviceState: this.device ? this.device.state : 'No device',
        timestamp: new Date().toISOString(),
        // Audio diagnostics for troubleshooting
        audioDiagnostics: diagnostics,
        // Browser media permissions
        mediaPermissions: {
          hasGetUserMedia: !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),
          activeAudioStream: !!window.activeAudioStream,
          activeAudioTracks: window.activeAudioStream ? 
            window.activeAudioStream.getAudioTracks().length : 0
        }
      };
      
      console.error('❌ DETAILED Connection Error with Audio Diagnostics:', connectionErrorDetails);
    });
    
    connection.on('mute', isMuted => {
      console.log(`📞 Call ${isMuted ? 'muted' : 'unmuted'}`);
    });
    
    connection.on('accept', () => {
      // Enhanced logging for accept event with audio diagnostics
      const diagnostics = getAudioDiagnostics();
      
      console.log('📞 Call accepted with audio diagnostics:', {
        connectionParameters: connection.parameters,
        status: connection.status && connection.status(),
        audioDiagnostics: diagnostics,
        activeAudioStream: window.activeAudioStream ? {
          active: window.activeAudioStream.active,
          id: window.activeAudioStream.id,
          trackCount: window.activeAudioStream.getTracks().length
        } : 'No active stream'
      });
      
      // AUDIO HEALTH CHECK AFTER CONNECTION
      setTimeout(() => {
        console.log('🔊 AUDIO HEALTH CHECK:', {
          connectionActive: this.activeConnection === connection,
          connectionState: connection.status && connection.status(),
          audioTracks: window.activeAudioStream ? 
            window.activeAudioStream.getAudioTracks().map(track => ({
              label: track.label,
              enabled: track.enabled,
              readyState: track.readyState,
              muted: track.muted
            })) : 'No active stream',
          // Device state after 5 seconds
          deviceState: this.device ? this.device.state : 'No device'
        });
      }, 5000);
    });
    
    connection.on('disconnect', () => {
      console.log('📞 Call disconnected', {
        disconnectCause: connection.parameters ? connection.parameters.DisconnectCause : 'Unknown',
        finalStatus: connection.status && connection.status(),
        audioDiagnostics: getAudioDiagnostics()
      });
      this.activeConnection = null;
    });
    
    connection.on('reject', () => {
      console.log('📞 Call rejected', {
        rejectCause: connection.parameters ? connection.parameters.DisconnectCause : 'Unknown',
        audioDiagnostics: getAudioDiagnostics()
      });
      this.activeConnection = null;
    });
    
    // Additional event for warning messages
    connection.on('warning', warning => {
      console.warn('⚠️ Connection Warning:', warning);
    });
    
    // Listen for TwiML processing events
    connection.on('twiml-processing', twiml => {
      console.log('📄 Processing TwiML:', twiml);
    });
    
    // Enhanced audio events for debugging
    if (typeof connection.on === 'function') {
      try {
        // Check for volume events
        connection.on('volume', (inputVolume, outputVolume) => {
          // Log only significant volume changes to avoid console spam
          if (Math.abs(inputVolume) > 50 || Math.abs(outputVolume) > 50) {
            console.log(`🔊 Volume change - Input: ${inputVolume}, Output: ${outputVolume}`);
          }
        });
        
        // Check for media stream events if supported
        if (typeof connection.getRemoteStream === 'function') {
          const remoteStream = connection.getRemoteStream();
          if (remoteStream) {
            console.log('✅ Remote audio stream available:', {
              active: remoteStream.active,
              id: remoteStream.id,
              tracks: remoteStream.getTracks().map(t => ({
                kind: t.kind,
                enabled: t.enabled,
                readyState: t.readyState
              }))
            });
          } else {
            console.warn('⚠️ No remote audio stream available');
          }
        }
      } catch (e) {
        console.warn('Error setting up enhanced audio events:', e);
      }
    }
  }
  
  // Make a call using the Twilio Client
  makeClientCall(params) {
    if (!this.device || !this.initialized) {
      throw new Error('Twilio Device not initialized');
    }
    
    this.activeConnection = this.device.connect(params);
    return this.activeConnection;
  }
  
  // Join a conference call using the Twilio Client
  joinClientCall(conferenceParams) {
    if (!this.device || !this.initialized) {
      throw new Error('Twilio Device not initialized');
    }

    try {
      // IMPORTANT: Do NOT try to register if already registered
      // Only check state is ready
      if (this.device.state !== 'ready' && this.device.state !== 'registered') {
        // Don't try to register again if already registered
      }

      // This is CRITICAL for Twilio - params must be formatted exactly right
      // and passed directly in the format Twilio expects
      const params = {
        // REQUIRED: Twilio Voice JS SDK expects 'To' parameter to be a properly formatted string
        To: `${conferenceParams.To}`,

        // Additional params for our server
        account_id: conferenceParams.account_id,
        is_agent: 'true'
      };

      // Check To parameter exists - fail if missing
      if (!params.To) {
        throw new Error('Missing To parameter for conference');
      }

      // Make sure 'To' is explicitly a string
      const stringifiedTo = String(params.To);
      console.log('🎯 CRITICAL CONFERENCE CONNECTION: Connecting agent to conference with To=', stringifiedTo);

      // Follow Twilio documentation format - params should be nested under 'params' property
      console.log('🎯 TRYING CONNECTION: Using documented format with params property');

      // Just use the minimal required parameters
      const connection = this.device.connect({
        params: {
          To: stringifiedTo,  // Conference ID
          is_agent: 'true'    // Flag to indicate agent is joining
        }
      });

      console.log('🎯 CONFERENCE CONNECTION RESULT:', connection ? 'Success' : 'Failed');
      this.activeConnection = connection;

      if (connection && typeof connection.then === 'function') {
        // It's a Promise - newer Twilio SDK version
        connection.then(resolvedConnection => {
          this.activeConnection = resolvedConnection;
          try {
            if (typeof resolvedConnection.on === 'function') {
              resolvedConnection.on('accept', () => {
                // Connection accepted
              });
            }
          } catch (listenerError) {
            // Could not add listeners to Promise connection
          }
        }).catch(connError => {
          // WebRTC Promise connection error
        });
      } else {
        // It's a synchronous connection - older Twilio SDK
      }
      return connection;
    } catch (error) {
      // Error joining conference
    }
  }

  // Get the status of the device with additional diagnostic info
  getDeviceStatus() {
    if (!this.device) {
      return 'not_initialized';
    }
    
    const deviceState = this.device.state;
    
    // Append a recommended action based on the state
    switch (deviceState) {
      case 'registered':
        return 'ready';
      case 'unregistered':
        return 'disconnected';
      case 'destroyed':
        return 'terminated';
      case 'busy':
        return 'busy';
      case 'error':
        return 'error';
      default:
        return deviceState;
    }
  }
  
  // Get comprehensive diagnostic information about the device and connection
  getDiagnosticInfo() {
    const browserInfo = {
      userAgent: navigator.userAgent,
      platform: navigator.platform,
      vendor: navigator.vendor,
      hasMediaDevices: !!navigator.mediaDevices,
      hasGetUserMedia: !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),
    };
    
    const deviceInfo = this.device ? {
      state: this.device.state,
      isInitialized: this.initialized,
      capabilities: this.device.capabilities || {},
      isBusy: this.device.isBusy || false,
      audio: {
        isAudioSelectionSupported: this.device.isAudioSelectionSupported || false
      }
    } : { state: 'not_initialized' };
    
    const connectionInfo = this.activeConnection ? {
      status: this.activeConnection.status(),
      isMuted: this.activeConnection.isMuted(),
      direction: this.activeConnection.direction,
      parameters: this.activeConnection.parameters,
    } : { status: 'no_connection' };
    
    return {
      timestamp: new Date().toISOString(),
      browser: browserInfo,
      device: deviceInfo,
      connection: connectionInfo
    };
  }
  
  // Get the status of the active connection
  getConnectionStatus() {
    if (!this.activeConnection) {
      return 'no_connection';
    }
    
    const status = this.activeConnection.status();
    
    // Translate connection statuses to more user-friendly terms
    switch (status) {
      case 'pending':
        return 'connecting';
      case 'open':
        return 'connected';
      case 'connecting':
        return 'connecting';
      case 'ringing':
        return 'ringing';
      case 'closed':
        return 'ended';
      default:
        return status;
    }
  }
}

export default new VoiceAPI();