<?php
/**
 * Class for handling ElevenLabs API interactions
 *
 * @link       https://mementor.no
 * @since      1.1.0
 * @package    TTSWP
 * @subpackage Mementor_TTS/includes
 */

// Exit if accessed directly
if (!defined('ABSPATH')) {
    exit;
}

// Check for WordPress environment
if (!function_exists('add_action') || 
    !function_exists('get_option') || 
    !function_exists('esc_html__') || 
    !function_exists('plugin_dir_path') ||
    !function_exists('esc_url_raw') ||
    !function_exists('absint') ||
    !function_exists('wp_remote_post') ||
    !function_exists('wp_remote_retrieve_response_code') ||
    !function_exists('wp_remote_retrieve_body') ||
    !function_exists('wp_remote_retrieve_header')) {
    exit('This file requires WordPress functions to be available');
}

/**
 * ElevenLabs API Integration Class
 *
 * @package TTSWP
 */

/**
 * ElevenLabs API Integration Class
 *
 * @package    TTSWP
 * @subpackage Mementor_TTS/includes
 */
class Mementor_TTS_ElevenLabs_API {
    /**
     * The single instance of this class
     *
     * @var Mementor_TTS_ElevenLabs_API
     */
    private static $instance = null;

    /**
     * Track if initialization has already occurred
     */
    private static $initialized = false;

    /**
     * Get the singleton instance
     *
     * @return Mementor_TTS_ElevenLabs_API
     */
    public static function get_instance() {
        if (self::$instance instanceof self) {
            return self::$instance;
        }
        
        self::$instance = new self();
        return self::$instance;
    }

    /**
     * API Base URL
     * Note: While API base is v1, we use v2 for getting voices and v1 for other endpoints like text-to-speech
     * @see https://elevenlabs.io/docs/api-reference/text-to-speech/convert
     * @see https://elevenlabs.io/docs/api-reference/voices/search
     */
    private $api_base_url = 'https://api.elevenlabs.io/v1';

    /**
     * API Key
     */
    private $api_key;
    
    /**
     * Shared key manager instance
     */
    

    /**
     * Debug mode
     */
    private $debug_mode;

    /**
     * Cache expiration time in seconds (3 hours)
     */
    private static $cache_expiration = 10800; // 3 hours

    /**
     * Request cache
     */
    private static $request_cache = array();

    /**
     * Reset the instance
     * 
     * This is useful for testing or when you want to ensure a fresh instance
     */
    public static function reset_instance() {
        self::$instance = null;
        self::$request_cache = array();
        self::$initialized = false;
    }

    /**
     * Constructor
     */
    private function __construct() {
        // Check if we've already initialized this instance to prevent duplicate work
        // Note: Each HTTP request gets its own PHP process, so this won't affect
        // concurrent requests from different admins
        if (self::$initialized) {
            return;
        }

        // Mark as initialized
        self::$initialized = true;

        // Initialize debug mode
        $this->debug_mode = get_option('mementor_tts_debug_mode', '0') === '1';
        
        // Get the API key from options
        $encrypted_key = get_option('mementor_tts_api_key', '');
        
        // If no API key is set, return early
        if (empty($encrypted_key)) {
            $this->api_key = '';
            $this->log_message('No API key found in options (constructor)', 'debug');
            return;
        }
        
        $this->log_message('Found encrypted key in options, length: ' . strlen($encrypted_key), 'debug');
        
        // Try to decrypt the API key
        try {
            if (!class_exists('Mementor_TTS_Encryption')) {
                require_once plugin_dir_path(__FILE__) . 'class-mementor-tts-encryption.php';
            }
            $encryption = new Mementor_TTS_Encryption();
            $decrypted_key = $encryption->decrypt($encrypted_key);
            
            if ($decrypted_key !== false && !empty($decrypted_key)) {
                // Sanitize the API key after decryption
                $this->api_key = $this->sanitize_api_key($decrypted_key);
            } else {
                $this->log_message('Failed to decrypt API key - encryption may be required', 'error');
                // Do not fall back to using unencrypted key for security
                $this->api_key = '';
                add_action('admin_notices', array($this, 'show_encryption_error_notice'));
            }
        } catch (Exception $e) {
            $this->log_message('Exception while decrypting API key: ' . esc_html($e->getMessage()), 'error');
            // Do not fall back to using unencrypted key for security
            $this->api_key = '';
            add_action('admin_notices', array($this, 'show_encryption_error_notice'));
        }
    }

    /**
     * Log a message to the error log
     *
     * @param string $message The message to log
     * @param string $level The log level (error, warning, info)
     */
    private function log_message($message, $level = 'info') {
        // Create unique key for message to prevent duplicate logging
        $message_key = md5($message . $level);
        
        // Static array to track already logged messages in this request
        static $logged_messages = array();
        
        // Only log each unique message once per request
        if (!isset($logged_messages[$message_key])) {
            // Mark this message as logged
            $logged_messages[$message_key] = true;
            
            // Only log if WP_DEBUG_LOG is enabled
            if (!defined('WP_DEBUG_LOG') || !WP_DEBUG_LOG) {
                return;
            }

            // Sanitize any sensitive data
            $safe_message = preg_replace('/API key|token|secret|password/i', '[REDACTED]', $message);
            
            $prefix = '[Mementor TTS ElevenLabs API]';
            if ($level === 'error') {
                $prefix .= ' Error';
            } elseif ($level === 'warning') {
                $prefix .= ' Warning';
            } elseif ($level === 'debug') {
                $prefix .= ' Debug';
            }

            // Use WordPress debug logging if available
            if (function_exists('wp_debug_log')) {
                wp_debug_log($prefix . ': ' . $safe_message);
            } else {
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Conditional logging based on WP_DEBUG_LOG.
                error_log($prefix . ': ' . $safe_message);
            }
        }
    }
    
    /**
     * Debug logging wrapper - only logs when debug mode is enabled
     * 
     * @param string $message The message to log
     * @param string $level The log level (error, warning, info)
     */
    private function debug_log($message, $level = 'info') {
        // Only log in debug mode
        if ($this->debug_mode && defined('WP_DEBUG') && WP_DEBUG === true) {
            $this->log_message($message, $level);
        }
    }

    /**
     * Make API Request with improved error handling and rate limiting
     */
    public function make_request($endpoint, $args = array()) {
        // Always get the API key via get_api_key_to_use to allow filters and fallback logic to work
        $api_key = $this->get_api_key_to_use('', null, $args);
        
        if (empty($api_key)) {
            $this->log_message('No API key provided for request', 'error');
            return new WP_Error('no_api_key', esc_html__('No API key provided', 'text-to-speech-tts'));
        }

        try {
            $url = esc_url_raw($this->api_base_url . $endpoint);
            
            // Set default headers
            $default_headers = array(
                'xi-api-key' => $api_key,
                'Accept' => 'application/json'
            );

            $args['headers'] = array_merge(
                isset($args['headers']) ? $args['headers'] : array(),
                $default_headers
            );

            $response = wp_remote_request($url, $args);
            $status_code = wp_remote_retrieve_response_code($response);
            $body = wp_remote_retrieve_body($response);

            if (is_wp_error($response)) {
                throw new Exception($response->get_error_message());
            }
            if ($status_code >= 400) {
                $error_data = json_decode($body, true);
                $error_message = $this->parse_error_response($error_data, $status_code);
                throw new Exception($error_message);
            }

            $result = array(
                'status' => $status_code,
                'data' => $body,
                'error' => false
            );
            
            return $result;

        } catch (Exception $e) {
            $this->log_message('Exception in make_request: ' . $e->getMessage(), 'error');
            return new WP_Error('api_error', $e->getMessage());
        }
    }

    /**
     * Make API request with retry logic
     * 
     * @param string $endpoint API endpoint
     * @param array $args Request arguments
     * @param int $max_retries Maximum number of retries
     * @param int $initial_delay_ms Initial delay in milliseconds
     * @return array|WP_Error Response or error
     */
    private function make_api_request_with_retry($endpoint, $args, $max_retries = 3, $initial_delay_ms = 1000) {
        $attempts = 0;
        $delay_ms = $initial_delay_ms;
        $last_error = null;
        
        while ($attempts < $max_retries) {
            // Make the actual API request
            $response = wp_remote_request($endpoint, $args);
            $attempts++;
            
            // Check for network errors
            if (is_wp_error($response)) {
                $error_message = $response->get_error_message();
                $this->log_message("API request network error: {$error_message}", 'error');
                $last_error = $response;
                continue; // Retry on network errors
            }
            
            // Check HTTP status code
            $status_code = wp_remote_retrieve_response_code($response);
            
            // Success
            if ($status_code >= 200 && $status_code < 300) {
                return $response;
            }
            
            // Check for retryable errors
            // 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout
            if (in_array($status_code, array(502, 503, 504))) {
                $this->log_message("Received retryable error: HTTP {$status_code}, will retry", 'warning');
                $last_error = $response;
                continue;
            }
            
            // For other errors, don't retry
            return $response;
        }
        
        // If we get here, we've exhausted our retries
        $this->log_message("Max retries ({$max_retries}) exceeded, giving up", 'error');
        
        // Return the last error we encountered
        return $last_error;
    }

    /**
     * Parse error response
     * 
     * @param array|null $error_data The error data from the API
     * @param int $status_code The HTTP status code
     * @return string The parsed error message
     */
    private function parse_error_response($error_data, $status_code) {
        // Debug log the error data
        $this->log_message('ElevenLabs API Error Data: ' . wp_json_encode($error_data), 'error');
        
        // Check specifically for voice_limit_reached status
        if (json_last_error() === JSON_ERROR_NONE && !empty($error_data)) {
            // Check in detail.status structure
            if (isset($error_data['detail']) && is_array($error_data['detail']) && 
                isset($error_data['detail']['status']) && $error_data['detail']['status'] === 'voice_limit_reached') {
                // Return specific error code that frontend will detect
                return 'voice_limit_reached';
            }
            
            // Normal error handling
            if (isset($error_data['detail'])) {
                if (is_array($error_data['detail'])) {
                    if (isset($error_data['detail']['status']) && isset($error_data['detail']['message'])) {
                        return esc_html($error_data['detail']['status']) . ': ' . esc_html($error_data['detail']['message']);
                    }
                    return wp_json_encode($error_data['detail']);
                }
                return esc_html($error_data['detail']);
            }
            return wp_json_encode($error_data);
        }
        
        /* translators: %1$s: HTTP error code, %2$s: Error message */
        return sprintf(__('HTTP Error %1$s: %2$s', 'text-to-speech-tts'), intval($status_code), esc_html__('Unknown error', 'text-to-speech-tts'));
    }

    /**
     * Validate API Key
     */
    public function validate_api_key() {
        $this->log_message('TTS Plugin: Starting API key validation');

        if (empty($this->api_key)) {
            $this->log_message('TTS Plugin: API key is empty', 'error');
            return new WP_Error('empty_api_key', __('API key is required', 'text-to-speech-tts'));
        }

        try {
            $this->log_message('TTS Plugin: Making validation request to ElevenLabs API');
            
            // Verify that the key has a valid format before even making a request
            if (!$this->is_valid_api_key_format($this->api_key)) {
                $this->log_message('TTS Plugin: API key has invalid format (not 32 chars or sk_ prefix)', 'error');
                return new WP_Error('invalid_key_format', __('API key format is invalid', 'text-to-speech-tts'));
            }

            // Make a request to the user subscription endpoint to validate the key
            $response = $this->make_request('/user/subscription', array(
                'method' => 'GET',
                'timeout' => 15
            ));

            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Output used only within log_message which checks WP_DEBUG_LOG.
            $this->log_message('TTS Plugin: Raw API Response: ' . print_r($response, true));

            if (is_wp_error($response)) {
                // Check if the error is authorization related but the key has the right format
                $error_code = $response->get_error_code();
                $error_message = $response->get_error_message();
                
                $this->log_message('TTS Plugin: API validation WP_Error - Code: ' . $error_code . ', Message: ' . $error_message);
                
                if (strpos(strtolower($error_message), 'invalid_authorization') !== false || 
                    strpos(strtolower($error_message), 'unauthorized') !== false ||
                    $error_code === 401) {
                    // The key is invalid or expired
                    $this->log_message('TTS Plugin: API key validation failed with 401 error', 'error');
                    
                    // Check if the error message contains information about missing permissions
                    if (strpos($error_message, 'missing_permissions') !== false || 
                        strpos($error_message, 'missing the permission user_read') !== false) {
                        return new WP_Error('missing_permissions', __('Your API key is missing the user_read permission. Please enable this permission in your ElevenLabs API settings.', 'text-to-speech-tts'));
                    }
                    
                    // Return proper error for invalid/expired keys
                    return new WP_Error('invalid_api_key', __('Authentication failed: Your API key is invalid or has expired. Please check your API key and try again.', 'text-to-speech-tts'));
                }
                
                // Check for 403 Forbidden which might indicate permission issues
                if ($error_code === 'api_error' && strpos($error_message, '403') !== false) {
                    $this->log_message('TTS Plugin: Got 403 error, checking if it\'s permission related');
                    // This is likely a permission issue, try the voices endpoint
                    $voices_response = $this->make_request('/voices', array(
                        'method' => 'GET',
                        'timeout' => 15
                    ));
                    
                    if (!is_wp_error($voices_response) && !empty($voices_response['data'])) {
                        // Voices endpoint works, so key is valid but lacks user_read
                        $this->log_message('TTS Plugin: Voices endpoint works - key valid but lacks user_read permission');
                        return array(
                            'status' => 'success',
                            'message' => __('API key is valid but lacks user_read permission. You can generate audio but cannot view usage statistics.', 'text-to-speech-tts'),
                            'data' => array(
                                'character_count' => 0,
                                'character_limit' => 0,
                                'limited_permissions' => true,
                                'missing_user_read' => true
                            )
                        );
                    }
                }
                
                $this->log_message('TTS Plugin: API Key Validation Error: ' . esc_html($response->get_error_message()), 'error');
                return $response;
            }

            // Check if there's an error in the response
            if (!empty($response['error'])) {
                $this->log_message('TTS Plugin: API validation error detected: ' . $response['error']);
                // If the error is about missing permissions, we need to validate differently
                if (strpos($response['error'], 'missing_permissions') !== false || 
                    strpos($response['error'], 'missing the permission user_read') !== false) {
                    // The API key is valid but lacks user_read permission
                    // Try to validate using a different endpoint that doesn't require user_read
                    $this->log_message('TTS Plugin: API key lacks user_read permission, trying alternative validation');
                    
                    // Try the voices endpoint which should work without user_read
                    $voices_response = $this->make_request('/voices', array(
                        'method' => 'GET',
                        'timeout' => 15
                    ));
                    
                    if (is_wp_error($voices_response)) {
                        // If voices endpoint also fails, the key is truly invalid
                        return new WP_Error('invalid_api_key', __('Authentication failed: Your API key is invalid or has expired.', 'text-to-speech-tts'));
                    }
                    
                    // API key is valid but lacks user_read permission
                    return array(
                        'status' => 'success',
                        'message' => __('API key is valid but lacks user_read permission. You can generate audio but cannot view usage statistics.', 'text-to-speech-tts'),
                        'data' => array(
                            'character_count' => 0,
                            'character_limit' => 0,
                            'limited_permissions' => true,
                            'missing_user_read' => true
                        )
                    );
                }
                
                $this->log_message('TTS Plugin: API Error: ' . esc_html($response['error']), 'error');
                return new WP_Error('api_error', $response['error']);
            }

            // Validate response structure
            if (!isset($response['data'])) {
                $this->log_message('TTS Plugin: Invalid response structure - missing data field', 'error');
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Output used only within log_message which checks WP_DEBUG_LOG.
                $this->log_message('TTS Plugin: Response: ' . print_r($response, true), 'error');
                return new WP_Error('invalid_response', __('Invalid response structure from API', 'text-to-speech-tts'));
            }

            // Try to decode the JSON response
            $data = json_decode($response['data'], true);
            
            // If JSON decoding failed, log the error and return
            if (json_last_error() !== JSON_ERROR_NONE) {
                $this->log_message('TTS Plugin: JSON decode error: ' . esc_html(json_last_error_msg()), 'error');
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Output used only within log_message which checks WP_DEBUG_LOG.
                $this->log_message('TTS Plugin: Raw response data: ' . print_r($response['data'], true), 'error');
                
                // Special handling for newer API keys with sk_ prefix - they may respond differently
                if (strpos($this->api_key, 'sk_') === 0) {
                    $this->log_message('TTS Plugin: Detected sk_ format API key, treating as valid');
                    return array(
                        'status' => 'success',
                        'message' => __('API key is valid (sk_ format)', 'text-to-speech-tts'),
                        'data' => array(
                            'character_count' => 0,
                            'character_limit' => 0,
                        )
                    );
                }
                
                return new WP_Error('invalid_json', __('Invalid JSON response from API', 'text-to-speech-tts'));
            }
            
            // Check if response contains voices array
            if (!isset($data['tier'])) {
                $this->log_message('TTS Plugin: Invalid response format - empty or not an array', 'error');
                return new WP_Error('invalid_response', __('Invalid API response format', 'text-to-speech-tts'));
            }

            // If we have a tier field, the API key is valid
            $this->log_message('TTS Plugin: API key validation successful');
            
            // Extract character info if available
            $character_count = isset($data['character_count']) ? $data['character_count'] : 0;
            $character_limit = isset($data['character_limit']) ? $data['character_limit'] : 0;
            
            return array(
                'status' => 'success',
                'message' => __('API key is valid', 'text-to-speech-tts'),
                'data' => array(
                    'character_count' => $character_count,
                    'character_limit' => $character_limit,
                )
            );
        } catch (Exception $e) {
            $this->log_message('TTS Plugin: Exception during API key validation: ' . esc_html($e->getMessage()), 'error');
            return new WP_Error('validation_exception', $e->getMessage());
        }
    }

    /**
     * Check if the API key has a valid format
     * 
     * @param string $key The API key to check
     * @return bool Whether the key has a valid format
     */
    private function is_valid_api_key_format($key) {
        // ElevenLabs API keys can be in different formats:
        // 1. Classic format: 32 characters, alphanumeric
        // 2. New format: starts with "sk_" followed by characters
        
        // Clean the key of any whitespace
        $key = trim($key);
        
        // Check if it matches the classic format (32 characters)
        if (strlen($key) === 32 && ctype_alnum($key)) {
            return true;
        }
        
        // Check if it matches the new format (starts with sk_)
        if (strpos($key, 'sk_') === 0 && strlen($key) > 5) {
            return true;
        }
        

        // Try to decode the JSON response
        $data = json_decode($response['data'], true);
        
        // If JSON decoding failed, log the error and return
        if (json_last_error() !== JSON_ERROR_NONE) {
            $this->log_message('TTS Plugin: JSON decode error: ' . esc_html(json_last_error_msg()), 'error');
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Output used only within log_message which checks WP_DEBUG_LOG.
            $this->log_message('TTS Plugin: Raw response data: ' . print_r($response['data'], true), 'error');
            
            // Special handling for newer API keys with sk_ prefix - they may respond differently
            if (strpos($this->api_key, 'sk_') === 0) {
                $this->log_message('TTS Plugin: Detected sk_ format API key, treating as valid');
                return array(
                    'status' => 'success',
                    'message' => __('API key is valid (sk_ format)', 'text-to-speech-tts'),
                    'data' => array(
                        'character_count' => 0,
                        'character_limit' => 0,
                    )
                );
            }
            
            return new WP_Error('invalid_json', __('Invalid JSON response from API', 'text-to-speech-tts'));
        }
        
        // Check if response contains voices array
        if (!isset($data['tier'])) {
            $this->log_message('TTS Plugin: Invalid response format - empty or not an array', 'error');
            return new WP_Error('invalid_response', __('Invalid API response format', 'text-to-speech-tts'));
        }
        
        // Get the user's API key instead of using a fixed one
        $api_key_to_use = $this->get_api_key_to_use('', null, array());
        
        if (empty($api_key_to_use)) {
            $error_message = 'ElevenLabs API Error: No valid API key found. Please add your ElevenLabs API key in the plugin settings.';
            $this->log_message($error_message, 'error');
            return $this->format_api_error_response($error_message);
        }
        
        $this->log_message('ElevenLabs API: Using user API key for fetching voices');
        
        // Make API request
        $headers = array(
            'xi-api-key' => $api_key_to_use,
            'Accept'     => 'application/json',
        );
        
        // Make the request
        $response = wp_remote_request(
            $api_url,
            array(
                'method'  => 'GET',
                'headers' => $headers,
                'timeout' => 60,
                'sslverify' => apply_filters( 'mementor_tts_ssl_verify', true )
            )
        );
        
        // Check for errors
        if ( is_wp_error( $response ) ) {
            $error_message = 'ElevenLabs API Request Error: ' . $response->get_error_message();
            $this->log_message( $error_message, 'error' );
            return $this->format_api_error_response( $error_message );
        }

        // Get the response code
        $response_code = apply_filters( 'mementor_tts_custom_http_response_code', wp_remote_retrieve_response_code( $response ) );
        
        // Process the response
        if ( $response_code >= 200 && $response_code < 300 ) {
            // Get the body
            $body = wp_remote_retrieve_body( $response );
            $response_body = $body; // Store for additional logging
            
            // Decode the JSON response
            $json_body = json_decode( $body, true );
            
            if ( empty( $json_body ) ) {
                $error_message = 'ElevenLabs API Error: Empty or invalid JSON response';
                $this->log_message( $error_message, 'error' );
                return $this->format_api_error_response( $error_message );
            }

            // Check if response contains voices array
            if ( !isset( $json_body['voices'] ) || !is_array( $json_body['voices'] ) ) {
                $error_message = 'No valid voices array in API response';
                $this->log_message( $error_message, 'error' );
                return $this->format_api_error_response( $error_message );
            }
            
            // Format the response data
            $voices_array = array_values( $json_body['voices'] );
            $result = array(
                'success' => true,
                'voices' => $voices_array,
                'error' => null,
            );
            
            // Cache the successful response
            $transient_key = 'mementor_tts_elevenlabs_voices_' . md5($api_key_to_use);
            set_transient( $transient_key, $result, self::$cache_expiration );
            
            return $result;
        } else {
            // Handle error response
            $body = wp_remote_retrieve_body( $response );
            $json_body = json_decode( $body, true );
            
            // Generic error handling
            {
                // Generic error handling
                $error_message = 'ElevenLabs API Error ' . $response_code . ': ';
                if ( ! empty( $json_body['detail'] ) ) {
                    // Handle detail as either string or array
                    if ( is_array( $json_body['detail'] ) ) {
                        $error_message .= esc_html( wp_json_encode( $json_body['detail'] ) );
                    } else {
                        $error_message .= esc_html( $json_body['detail'] );
                    }
                } else {
                    $error_message .= 'Unknown error';
                }
                $this->log_message( $error_message, 'error' );
            }
            
            return $this->format_api_error_response( $error_message );
        }
    }

    /**
     * Get the API key to use for a request
     *
     * @param string $text        Text to process
     * @param string $custom_api_key API key
     * @param array  $options     Additional options
     * @return string API key to use
     */
    private function get_api_key_to_use($text, $custom_api_key = null, $options = array()) {
        // Start with empty API key
        $api_key = null;

        $debug_mode = get_option('mementor_tts_debug_mode', '0') === '1';
        if ($debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
            error_log('[TTS API] get_api_key_to_use() called - text length: ' . strlen($text));
        }

        // 1. Check if a custom API key is provided directly
        if (!empty($custom_api_key)) {
            if ($debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
                error_log('[TTS API] Using custom API key provided directly');
            }
            return $this->sanitize_api_key($custom_api_key);
        }

        // 1.5. Check for active special license key (extra credits tier)
        $special_license = Mementor_TTS_Special_License::get_instance();
        $special_info = $special_license->get_active_license();
        if ( $special_info !== false && ! empty( $special_info['api_key'] ) ) {
            if ( $debug_mode && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
                error_log( '[TTS API] Using special license API key (tier: ' . $special_info['tier'] . ')' );
            }
            return $this->sanitize_api_key( $special_info['api_key'] );
        }

        // 2. Fallback: Use explicitly set API key (e.g., during validation or when user has own key)
        if (!empty($this->api_key)) {
            if ($debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
                $key_prefix = substr($this->api_key, 0, 5);
                $key_suffix = substr($this->api_key, -4);
                error_log('[TTS API] Using explicitly set API key: ' . $key_prefix . '...' . $key_suffix);
            }
            return $this->api_key;
        }

        // 3. Get the stored API key if available
        $encrypted_key = get_option('mementor_tts_elevenlabs_api_key', '');
        
        // 4. If no API key in options, try the regular option name
        if (empty($encrypted_key)) {
            $encrypted_key = get_option('mementor_tts_api_key', '');
        }
        
        // 5. Check if the API key is available from options and decrypt it
        if (!empty($encrypted_key)) {
            // Try to decrypt the key
            if (!class_exists('Mementor_TTS_Encryption')) {
                require_once plugin_dir_path(__FILE__) . 'class-mementor-tts-encryption.php';
            }
            $encryption = new Mementor_TTS_Encryption();
            $decrypted_key = $encryption->decrypt($encrypted_key);
            
            // Only use the key if it was successfully decrypted and is not empty
            if ($decrypted_key !== false && !empty($decrypted_key)) {
                // Debug log to show which key is being used
                $debug_mode = get_option('mementor_tts_debug_mode', '0') === '1';
                if ($debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
                    $key_prefix = substr($decrypted_key, 0, 5);
                    $key_suffix = substr($decrypted_key, -4);
                    error_log('[TTS API] Using user\'s own API key from options: ' . $key_prefix . '...' . $key_suffix);
                }
                return $this->sanitize_api_key($decrypted_key);
            } else {
                $debug_mode = get_option('mementor_tts_debug_mode', '0') === '1';
                if ($debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
                    error_log('[TTS API] Failed to decrypt API key from options, will check for shared key');
                }
            }
        }
        
        // 6. Use fallback key only if available
        $fallback_key = apply_filters('mementor_tts_fallback_api_key', '');
        
        if (!empty($fallback_key)) {
            return $this->sanitize_api_key($fallback_key);
        }

        // 7. Final fallback: Use shared API key for free tier if user has credits remaining
        $debug_mode = get_option('mementor_tts_debug_mode', '0') === '1';
        $can_use_shared_key = $this->can_use_shared_api_key();
        
        if ($debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
            error_log('[TTS API] Checking shared key eligibility: ' . ($can_use_shared_key ? 'YES' : 'NO'));
        }
        
        if ($can_use_shared_key) {
            $shared_key = $this->get_decrypted_shared_key();
            if (!empty($shared_key)) {
                if ($debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
                    error_log('[TTS API] Using shared API key for free tier');
                }
                return $this->sanitize_api_key($shared_key);
            } else {
                if ($debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
                    error_log('[TTS API] Shared key decryption failed or empty');
                }
            }
        }
        
        $this->log_message('No API key available from any source', 'error');
        return null;
    }

    /**
     * Convert text to speech using the ElevenLabs API
     *
     * @param string $text Text to convert to speech
     * @param int $post_id Post ID  
     * @param string $voice_id Voice ID
     * @param array  $options Options for the request
     * @return array|WP_Error Result of the API call
     */
    public function text_to_speech($text, $post_id, $voice_id, $options = array()) {
        // Ensure voice_id is a string
        if (is_array($voice_id)) {
            $voice_id = isset($voice_id[0]) ? $voice_id[0] : '';
        }
        
        // Privacy mode handling
        $privacy_mode = isset($options['privacy_mode']) && $options['privacy_mode'];
        
        try {
            // Validate inputs
            if (empty($text)) {
                $this->log_message('Error: Empty text provided to text_to_speech', 'error');
                return array(
                    'success' => false,
                    'message' => __('No text provided for conversion.', 'text-to-speech-tts')
                );
            }

            if (empty($voice_id)) {
                $this->log_message('Error: Empty voice_id provided to text_to_speech', 'error');
                return array(
                    'success' => false,
                    'message' => __('No voice selected for conversion.', 'text-to-speech-tts')
                );
            }

            // Check if custom API key is provided in options
            $custom_api_key = null;
            if (isset($options['api_key'])) {
                $custom_api_key = $options['api_key'];
            }

            // Get the API key to use (special license, user key, or shared key fallback)
            $api_key_to_use = $this->get_api_key_to_use($text, $custom_api_key, $options);

            // Initialize headers array with content type
            $headers = array('Content-Type' => 'application/json');

            // Set default headers
            $default_headers = array(
                'xi-api-key' => $api_key_to_use,
                'Accept' => 'application/json'
            );

            $headers = array_merge($headers, $default_headers);

            // Set up the API endpoint
            $api_endpoint = $this->api_base_url . '/text-to-speech/';
            
            // Append voice_id to the endpoint
            $api_endpoint .= $voice_id;
            
            // Add output format as query parameter for ElevenLabs API
            if (!empty($mp3_quality)) {
                $api_endpoint = add_query_arg('output_format', $mp3_quality, $api_endpoint);
            }
            
            // Log the voice ID being used
            $this->log_message('API call using voice ID: ' . $voice_id, 'debug');
            $this->log_message('Full API endpoint: ' . $api_endpoint, 'debug');
            
            // Extract stability and similarity settings
            $stability = isset($options['stability']) ? floatval($options['stability']) : 0.5;
            $similarity_boost = isset($options['similarity_boost']) ? floatval($options['similarity_boost']) : 0.75;
            $style = isset($options['style']) ? floatval($options['style']) : 0.0;
            // Check for both 'use_speaker_boost' and 'speaker_boost' for compatibility
            $use_speaker_boost = isset($options['use_speaker_boost']) ? (bool)$options['use_speaker_boost'] : 
                                (isset($options['speaker_boost']) ? (bool)$options['speaker_boost'] : false);
            
            // Debug logging for speaker boost
            if ($this->debug_mode) {
                $this->debug_log('Speaker boost options received: use_speaker_boost=' . 
                    (isset($options['use_speaker_boost']) ? var_export($options['use_speaker_boost'], true) : 'not set') . 
                    ', speaker_boost=' . (isset($options['speaker_boost']) ? var_export($options['speaker_boost'], true) : 'not set'));
                $this->debug_log('Final use_speaker_boost value: ' . ($use_speaker_boost ? 'true' : 'false'));
            }
            
            // Extract additional parameters
            // Get MP3 quality from options or settings
            $mp3_quality_raw = get_option('mementor_tts_quality', '128');
            // Convert the quality value to the API format
            if ($mp3_quality_raw === '128') {
                $mp3_quality_default = 'mp3_44100_128';
            } elseif ($mp3_quality_raw === 'mp3_44100_192') {
                $mp3_quality_default = 'mp3_44100_192';
            } else {
                // Default to 128kbps for any other value
                $mp3_quality_default = 'mp3_44100_128';
            }
            $mp3_quality = isset($options['mp3_quality']) ? $options['mp3_quality'] : $mp3_quality_default;
            $speed = isset($options['speed']) ? floatval($options['speed']) : 1.0;
            
            // Log the MP3 quality being used
            $this->log_message('MP3 quality raw setting: ' . $mp3_quality_raw, 'debug');
            $this->log_message('MP3 quality being used: ' . $mp3_quality, 'debug');
            
            // Get the model ID from options or settings
            $saved_model_id = get_option('mementor_tts_model', 'eleven_flash_v2_5');
            
            // Use the model ID from options only if provided, otherwise use the saved setting
            $model_id = isset($options['model_id']) ? $options['model_id'] : $saved_model_id;
            
            // Log the model ID being used
            $this->log_message('Model ID from settings: ' . $saved_model_id, 'debug');
            $this->log_message('Model ID being used: ' . $model_id, 'debug');
            
            $language_code = isset($options['language_code']) ? $options['language_code'] : '';
            
            // Prepare the request body
            $body = array(
                'text' => $text,
                'model_id' => $model_id,
                'voice_settings' => array(
                    'stability' => $stability,
                    'similarity_boost' => $similarity_boost,
                    'style' => $style,
                    'use_speaker_boost' => $use_speaker_boost,
                    'speed' => $speed
                )
            );
            
            // Check if model is one of the ones that supports language_code parameter
            if (in_array($model_id, array('eleven_flash_v2_5', 'eleven_turbo_v2_5', 'eleven_multilingual_v2', 'eleven_monolingual_v1'))) {
                // Extract primary language code (e.g., 'en' from 'en-US' or 'en_US')
                if (!empty($language_code)) {
                    $lang_parts = preg_split('/[-_]/', $language_code, 2);
                    $primary_language_code = $lang_parts[0]; // Get just the primary code (e.g., "en", "sk")

                    // Map BCP 47 / deprecated codes to ISO 639-1 codes for ElevenLabs API compatibility
                    $language_code_map = array(
                        'nb' => 'no', // Norwegian Bokmål -> Norwegian
                        'nn' => 'no', // Norwegian Nynorsk -> Norwegian
                        'iw' => 'he', // Hebrew (deprecated Java code) -> Hebrew
                        'in' => 'id', // Indonesian (deprecated) -> Indonesian
                        'ji' => 'yi', // Yiddish (deprecated) -> Yiddish
                    );

                    if (isset($language_code_map[$primary_language_code])) {
                        $primary_language_code = $language_code_map[$primary_language_code];
                        $this->log_message('Mapped language code to ISO 639-1: ' . $primary_language_code, 'debug');
                    }

                    $body['language_code'] = $primary_language_code;
                }
            }
            
            // Add enable_logging=false parameter if privacy mode is enabled
            if ($privacy_mode) {
                $body['enable_logging'] = false;
            }
            
            // Prepare the request arguments
            $args = array(
                'method' => 'POST',
                'headers' => $headers,
                'body' => wp_json_encode($body),
                'timeout' => 60, // Increase timeout for larger texts
                'sslverify' => apply_filters('mementor_tts_ssl_verify', true)
            );
            
            // Debug logging - log the full request details before making the API call
            $this->log_message('ElevenLabs API Request Details:', 'debug');
            $this->log_message('API Endpoint URL: ' . $api_endpoint, 'debug');
            $this->log_message('Request Method: ' . $args['method'], 'debug');
            $this->log_message('Request Headers: ' . wp_json_encode($args['headers']), 'debug');
            $this->log_message('Request Body: ' . $args['body'], 'debug');
            $this->log_message('Request Timeout: ' . $args['timeout'], 'debug');
            $this->log_message('SSL Verify: ' . ($args['sslverify'] ? 'true' : 'false'), 'debug');
            
            // Make the API request with retry logic for better handling of temporary failures
            $response = $this->make_api_request_with_retry($api_endpoint, $args, 3, 1000);
            
            // Check for errors
            if (is_wp_error($response)) {
                $error_message = $response->get_error_message();
                $this->log_message('API request error: ' . $error_message, 'error');
                return array(
                    'success' => false,
                    /* translators: %s: Underlying network or HTTP error message from WordPress. */
                    'message' => sprintf(__('Error communicating with ElevenLabs API: %s', 'text-to-speech-tts'), $error_message),
                    'debug_info' => array(
                        'error_type' => 'network_error',
                        'error_message' => $error_message
                    )
                );
            }
            
            // Get the response code
            $status_code = wp_remote_retrieve_response_code($response);
            
            // Check the response status
            if ($status_code !== 200) {
                $body = wp_remote_retrieve_body($response);
                $this->log_message('API responded with code ' . $status_code . ': ' . $body, 'error');
                
                // Get a friendly error message based on status code
                $friendly_message = $this->get_friendly_error_message($status_code, $body);
                
                // Special handling for voice_limit_reached error
                if ($friendly_message === 'voice_limit_reached') {
                    // For this specific error, make sure it's very clear to the frontend by setting
                    // both message and data as the special error code
                    $this->log_message('Voice limit reached error detected, returning special error format', 'error');
                    return array(
                        'success' => false,
                        'message' => 'voice_limit_reached',
                        'data' => 'voice_limit_reached',  // Set data to the same value for easier detection
                        'error_type' => 'voice_limit_reached'
                    );
                }
                
                return array(
                    'success' => false,
                    'message' => $friendly_message,
                    'debug_info' => array(
                        'status_code' => $status_code,
                        'response_body' => $body,
                        'error_type' => 'api_error'
                    )
                );
            }
            
            // Get the response body
            $response_body = wp_remote_retrieve_body($response);
            
            // Get the content type
            $content_type = wp_remote_retrieve_header($response, 'content-type');

            // Get the actual character cost from ElevenLabs response header
            $character_cost = wp_remote_retrieve_header($response, 'character-cost');
            $character_cost = !empty($character_cost) ? intval($character_cost) : 0;

            if ($this->debug_mode) {
                $this->debug_log('Text length: ' . strlen($text) . ', ElevenLabs character-cost: ' . $character_cost);
            }

            // Audio file size
            $file_size = strlen($response_body);
            
            // Track credits if using shared API key
            $used_shared_key = false;
            // Check if this is the shared API key (check both old and new prefixes)
            $shared_key = $this->get_decrypted_shared_key();
            if ($api_key_to_use && $shared_key && $api_key_to_use === $shared_key) {
                $used_shared_key = true;
                
                // Use actual credit cost from ElevenLabs header, fall back to text length
                $credits_used = ($character_cost > 0) ? $character_cost : strlen($text);
                
                // Include credits class if not already loaded
                if (!class_exists('Mementor_TTS_User_Credits')) {
                    require_once plugin_dir_path(__FILE__) . 'class-mementor-tts-user-credits.php';
                }
                
                $user_credits = Mementor_TTS_User_Credits::get_instance();
                $credits_deducted = $user_credits->use_credits($credits_used);
                
                if (!$credits_deducted) {
                    // This shouldn't happen as we check before, but just in case
                    $this->log_message('Failed to deduct credits for user', 'error');
                }
                
                // Add credit info to response
                $remaining_credits = $user_credits->get_remaining_credits();
                
            }
            
            // Return the audio data
            $result = array(
                'success' => true,
                'audio_data' => $response_body,
                'content_type' => $content_type,
                'file_size' => $file_size,
                'character_cost' => $character_cost,
            );
            
            // Add credit info if using shared key
            if ($used_shared_key) {
                $result['credits_info'] = array(
                    'used' => $credits_used,
                    'remaining' => $remaining_credits,
                    'using_shared_key' => true
                );
            }
            
            return $result;
            
        } catch (Exception $e) {
            $this->log_message('Exception in text_to_speech: ' . $e->getMessage(), 'error');
            
            return array(
                'success' => false,
                'message' => $e->getMessage()
            );
        }
    }

    /**
     * Get User Info
     */
    public function get_user_info() {
        // Check cache first
        $cached_info = get_transient('mementor_tts_user_info');
        if ($cached_info !== false) {
            return $cached_info;
        }

        $response = $this->make_request('/user/subscription');
        if (is_wp_error($response)) {
            return $response;
        }

        // Cache user info for 1 hour
        set_transient('mementor_tts_user_info', $response, HOUR_IN_SECONDS);
        return $response;
    }
    
    /**
     * Delete a voice from ElevenLabs
     *
     * @param string $voice_id The ID of the voice to delete
     * @return array|WP_Error Result of the API call
     */
    public function delete_voice($voice_id) {
        if (empty($voice_id)) {
            return new WP_Error('invalid_voice_id', __('Voice ID is required', 'text-to-speech-tts'));
        }
        
        $endpoint = '/voices/' . $voice_id;
        
        $response = $this->make_request($endpoint, array(
            'method' => 'DELETE'
        ));
        
        if (is_wp_error($response)) {
            $this->log_message('Failed to delete voice: ' . $response->get_error_message(), 'error');
            return $response;
        }
        
        // Clear voice cache after deletion
        delete_transient('mementor_tts_voices');
        
        return $response;
    }

    /**
     * Clear all caches (both transients and static request cache)
     */
    public function clear_cache() {
        global $wpdb;

        // Clear all voice-related transients with multiple patterns to catch all variations
        $patterns = array(
            '_transient_%mementor_tts%voices%',           // Old pattern
            '_transient_timeout_%mementor_tts%voices%',   // Timeout options
            '_transient_%elevenlabs_voices%',             // ElevenLabs specific
            '_transient_timeout_%elevenlabs_voices%'      // ElevenLabs timeout
        );

        $deleted_count = 0;
        foreach ($patterns as $pattern) {
            $sql = $wpdb->prepare("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", $pattern);
            $results = $wpdb->get_col($sql);
            if (!empty($results)) {
                foreach ($results as $option_name) {
                    delete_option($option_name);
                    $deleted_count++;
                    $this->log_message('Deleted transient: ' . $option_name, 'debug');
                }
            }
        }

        // Also clear user info
        delete_transient('mementor_tts_user_info');

        // Clear static request cache
        self::$request_cache = array();

        $this->log_message('Cache cleared - ' . $deleted_count . ' voice transients and request cache cleared');
    }

    /**
     * Get available models with caching
     */
    public function get_models() {
        // Try to get from cache first
        $cached_models = get_transient('mementor_tts_models');
        if ($cached_models !== false) {
            return array(
                'models' => $cached_models,
                'error' => false
            );
        }

        $response = $this->make_request('/models', array(
            'method' => 'GET'
        ));

        if ($response['error']) {
            return array(
                'models' => array(),
                'error' => $response['error']
            );
        }

        $models_data = json_decode($response['data'], true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            $this->log_message('TTS Plugin: Failed to parse models response - ' . esc_html(json_last_error_msg()), 'error');
            return array(
                'models' => array(),
                'error' => 'Invalid response format from API'
            );
        }

        // Cache the models
        set_transient('mementor_tts_models', $models_data, HOUR_IN_SECONDS);

        return array(
            'models' => $models_data,
            'error' => false
        );
    }

    /**
     * Get API Key
     *
     * @return string The API key or empty string if not set
     */
    public function get_api_key() {
        return $this->api_key ?? '';
    }
    
    /**
     * Set API Key
     *
     * @param string $api_key The API key to set
     * @return void
     */
    public function set_api_key($api_key) {
        // Sanitize the API key before processing
        $api_key = $this->sanitize_api_key($api_key);

        // Debug log what key we're setting
        $debug_mode = get_option('mementor_tts_debug_mode', '0') === '1';
        if ($debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
            $key_prefix = substr($api_key, 0, 5);
            $key_suffix = substr($api_key, -4);
            error_log('[TTS API] set_api_key called with: ' . $key_prefix . '...' . $key_suffix);
        }

        // ElevenLabs API keys can be in different formats including the newer sk_ format
        // Check for proper API key format - either 32 chars or starts with sk_
        if (strlen($api_key) === 32 || strpos($api_key, 'sk_') === 0) {
            $this->api_key = $api_key;
            if ($debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
                error_log('[TTS API] API key SET successfully (format valid)');
            }
            return;
        }

        // Try to decrypt the key if it's encrypted
        try {
            if (!class_exists('Mementor_TTS_Encryption')) {
                require_once plugin_dir_path(__FILE__) . 'class-mementor-tts-encryption.php';
            }
            $encryption = new Mementor_TTS_Encryption();
            $decrypted_key = $encryption->decrypt($api_key);
            
            if ($decrypted_key !== false) {
                $this->api_key = $this->sanitize_api_key($decrypted_key);
            } else {
                // If decryption fails, assume it's a raw key
                $this->api_key = $api_key;
            }
        } catch (Exception $e) {
            $this->log_message('Exception while decrypting API key: ' . esc_html($e->getMessage()), 'error');
            // Try using the raw key as a fallback
            if (strlen($api_key) >= 32) {
                $this->api_key = $api_key;
            } else {
                $this->log_message('Raw key too short to be valid', 'warning');
                $this->api_key = '';
            }
        }
    }

    /**
     * Prevent cloning of the singleton instance
     */
    private function __clone() {
        // Prevent cloning of the instance
    }

    /**
     * Prevent unserializing the singleton instance
     */
    public function __wakeup() {
        throw new Exception("Cannot unserialize a singleton.");
    }

    /**
     * Get a user-friendly error message based on HTTP status code and error data
     *
     * @param int $status_code HTTP status code
     * @param mixed|null $error_data Optional error data
     * @return string User-friendly error message
     */
    private function get_friendly_error_message($status_code, $error_data = null) {
        // First check for special error codes in error_data if it exists
        if (!empty($error_data)) {
            $json_data = json_decode($error_data, true);
            if (json_last_error() === JSON_ERROR_NONE && !empty($json_data)) {
                // Check for voice limit reached in detail structure
                if (isset($json_data['detail']) && is_array($json_data['detail']) && 
                    isset($json_data['detail']['status']) && $json_data['detail']['status'] === 'voice_limit_reached') {
                    // Return our special code for this specific error
                    return 'voice_limit_reached';
                }
                
                // Check for PRO-plan required error (free_users_not_allowed)
                if (isset($json_data['detail']) && is_array($json_data['detail']) &&
                    isset($json_data['detail']['status']) && $json_data['detail']['status'] === 'free_users_not_allowed') {
                    // Return our special code for this specific error
                    return 'pro_plan_required';
                }

                // Check for payment_required error (library voices on free tier)
                if (isset($json_data['detail']) && is_array($json_data['detail']) &&
                    isset($json_data['detail']['status']) && $json_data['detail']['status'] === 'payment_required') {
                    return 'pro_plan_required';
                }

                // Check for quota_exceeded error (PRO shared account out of credits)
                if (isset($json_data['detail']) && is_array($json_data['detail']) &&
                    isset($json_data['detail']['status']) && $json_data['detail']['status'] === 'quota_exceeded') {
                    return __('PRO credits are temporarily exhausted. Please try again later or contact support.', 'text-to-speech-tts');
                }

                // Check for sign_in_required error (ElevenLabs requires website login)
                if (isset($json_data['detail']) && is_array($json_data['detail']) &&
                    isset($json_data['detail']['status']) && $json_data['detail']['status'] === 'sign_in_required') {
                    return __('Your ElevenLabs account requires verification. Please sign in at elevenlabs.io first, then try again.', 'text-to-speech-tts');
                }

                // Pass through ElevenLabs detail message if available
                if (isset($json_data['detail']) && is_array($json_data['detail']) &&
                    isset($json_data['detail']['message'])) {
                    return sanitize_text_field($json_data['detail']['message']);
                }
            }
        }

        // Standard error messages based on HTTP status codes
        switch ($status_code) {
            case 400:
                return __('Bad Request - There is an issue with your request parameters.', 'text-to-speech-tts');
            case 401:
                return __('Unauthorized - Invalid API key.', 'text-to-speech-tts');
            case 403:
                return __('Forbidden - You don\'t have permission to access this resource.', 'text-to-speech-tts');
            case 404:
                return __('Not Found - The requested resource could not be found.', 'text-to-speech-tts');
            case 429:
                return __('Too Many Requests - You have exceeded the API rate limits.', 'text-to-speech-tts');
            case 500:
                return __('Server Error - ElevenLabs is experiencing internal issues.', 'text-to-speech-tts');
            case 502:
                return __('Bad Gateway - ElevenLabs API is temporarily unavailable.', 'text-to-speech-tts');
            case 503:
                return __('Service Unavailable - ElevenLabs servers are currently overloaded.', 'text-to-speech-tts');
            case 504:
                return __('Gateway Timeout - Request to ElevenLabs timed out.', 'text-to-speech-tts');
            default:
                if ($status_code >= 400 && $status_code < 500) {
                    /* translators: %d: HTTP 4xx status code returned by ElevenLabs. */
                    return sprintf(__('Client Error (%d) - There is a problem with your request.', 'text-to-speech-tts'), intval($status_code));
                } else if ($status_code >= 500) {
                    /* translators: %d: HTTP 5xx status code returned by ElevenLabs. */
                    return sprintf(__('Server Error (%d) - The ElevenLabs API is experiencing issues.', 'text-to-speech-tts'), intval($status_code));
                }
                /* translators: %d: HTTP status code returned by ElevenLabs. */
                return sprintf(__('Error (%d) - An unknown error occurred.', 'text-to-speech-tts'), intval($status_code));
        }
    }

    /**
     * Format API error response
     *
     * @param string $error_message Error message
     * @return array Formatted error response
     */
    private function format_api_error_response($error_message) {
        return array(
            'success' => false,
            'error' => $error_message,
            'voices' => array()
        );
    }

    /**
     * Send request to ElevenLabs API
     *
     * @param string $endpoint API endpoint (without leading slash)
     * @param string $method HTTP method (GET, POST, etc.)
     * @param array $args Request arguments
     * @return mixed Response or WP_Error
     */
    private function send_request($endpoint, $method = 'GET', $args = array()) {
        // Get the API key using the same method as text_to_speech for consistency
        $api_key = $this->get_api_key_to_use('', null, $args);
        
        if (empty($api_key)) {
            $this->log_message('No API key available for request', 'error');
            return new WP_Error('no_api_key', esc_html__('No API key available', 'text-to-speech-tts'));
        }

        try {
            // Build the full URL (ensure no double slashes between base and endpoint)
            $url = rtrim($this->api_base_url, '/') . '/' . ltrim($endpoint, '/');
            $url = esc_url_raw($url);

            // Set default headers
            $default_headers = array(
                'xi-api-key' => $api_key,
                'Accept' => 'application/json'
            );

            $args['headers'] = array_merge(
                isset($args['headers']) ? $args['headers'] : array(),
                $default_headers
            );

            $response = wp_remote_request($url, $args);
            $status_code = wp_remote_retrieve_response_code($response);
            $body = wp_remote_retrieve_body($response);

            if (is_wp_error($response)) {
                throw new Exception($response->get_error_message());
            }
            if ($status_code >= 400) {
                $error_data = json_decode($body, true);
                $error_message = $this->parse_error_response($error_data, $status_code);
                throw new Exception($error_message);
            }

            $result = array(
                'status' => $status_code,
                'data' => $body,
                'error' => false
            );
        
            return $result;

        } catch (Exception $e) {
            $this->log_message('Exception in send_request: ' . $e->getMessage(), 'error');
            return new WP_Error('api_error', $e->getMessage());
        }
    }

    /**
     * Sanitize API key to ensure proper format
     * 
     * @param string $api_key The potentially unsanitized API key
     * @return string The sanitized API key
     */
    private function sanitize_api_key($api_key) {
        if (empty($api_key)) {
            return '';
        }
        
        // If it's already in the new format (starts with "sk-" or "sk_"), just do basic whitespace trimming
        if (strpos($api_key, 'sk-') === 0 || strpos($api_key, 'sk_') === 0) {
            return trim($api_key);
        }
        
        // For classic ElevenLabs API keys (should be 32 alphanumeric characters)
        
        // First approach: Try pattern matching for alphanumeric 32-character sequence
        // This is more likely to succeed for partially corrupted data
        $matches = array();
        if (preg_match('/([a-zA-Z0-9]{32})/', $api_key, $matches)) {
            return $matches[1];
        }
        
        // Second approach: Try stripping non-alphanumeric characters and see if we get a valid key
        $alphanumeric_only = preg_replace('/[^a-zA-Z0-9]/', '', $api_key);
        if (strlen($alphanumeric_only) >= 32) {
            return substr($alphanumeric_only, 0, 32);
        }
        
        // Third approach: Try trimming whitespace and limiting to 32 chars
        // Only as a fallback if the other methods don't work
        $trimmed = trim($api_key);
        if (strlen($trimmed) >= 32) {
            return substr($trimmed, 0, 32);
        }
        
        // If all else fails, just return the trimmed input
        return trim($api_key);
    }

    /**
     * Get available voices from ElevenLabs API with caching
     *
     * @param string|null $language Optional language filter
     * @param mixed|null $cache_buster Optional parameter to bypass cache when provided
     * @return array Response with success status, voices array, and error message if any
     */
    public function get_voices($language = null, $cache_buster = null) {
        // Get the user's API key
        $api_key_to_use = $this->get_api_key_to_use('', null, array());
        
        if (empty($api_key_to_use)) {
            $error_message = 'ElevenLabs API Error: No valid API key found. Please add your ElevenLabs API key in the plugin settings.';
            $this->log_message($error_message, 'error');
            return $this->format_api_error_response($error_message);
        }
        
        // Check for cached voices first (unless cache_buster is provided)
        $transient_key = 'mementor_tts_elevenlabs_voices_' . md5($api_key_to_use);
        $cached_voices = false;
        
        if ($cache_buster === null) {
            $cached_voices = get_transient($transient_key);
        }
        
        if ($cached_voices !== false) {
            $this->log_message('ElevenLabs API: Using cached voices data');
            return $cached_voices;
        }
        
        if ($cache_buster !== null) {
            $this->log_message('ElevenLabs API: Cache bypassed with buster: ' . $cache_buster);
        } else {
            $this->log_message('ElevenLabs API: No cache found, fetching voices from API');
        }
        
        // Build the API URL
        $api_url = $this->api_base_url . '/voices';
        
        // Add language filter if provided
        if (!empty($language)) {
            $api_url = add_query_arg(array('language' => sanitize_text_field($language)), $api_url);
        }
        
        // Make API request
        $headers = array(
            'xi-api-key' => $api_key_to_use,
            'Accept'     => 'application/json',
        );
        
        // Make the request
        $response = wp_remote_request(
            $api_url,
            array(
                'method'  => 'GET',
                'headers' => $headers,
                'timeout' => 60,
                'sslverify' => apply_filters('mementor_tts_ssl_verify', true)
            )
        );
        
        // Check for errors
        if (is_wp_error($response)) {
            $error_message = 'ElevenLabs API Request Error: ' . $response->get_error_message();
            $this->log_message($error_message, 'error');
            return $this->format_api_error_response($error_message);
        }

        // Get the response code
        $response_code = apply_filters('mementor_tts_custom_http_response_code', wp_remote_retrieve_response_code($response));
        
        // Process the response
        if ($response_code >= 200 && $response_code < 300) {
            // Get the body
            $body = wp_remote_retrieve_body($response);
            
            // Decode the JSON response
            $json_body = json_decode($body, true);
            
            if (empty($json_body)) {
                $error_message = 'ElevenLabs API Error: Empty or invalid JSON response';
                $this->log_message($error_message, 'error');
                return $this->format_api_error_response($error_message);
            }

            // Check if response contains voices array
            if (!isset($json_body['voices']) || !is_array($json_body['voices'])) {
                $error_message = 'No valid voices array in API response';
                $this->log_message($error_message, 'error');
                return $this->format_api_error_response($error_message);
            }
            
            // Format the response data
            $voices_array = array_values($json_body['voices']);
            $result = array(
                'success' => true,
                'voices' => $voices_array,
                'error' => null,
            );
            
            // Cache the successful response
            set_transient($transient_key, $result, self::$cache_expiration);
            
            return $result;
        } else {
            // Handle error response
            $body = wp_remote_retrieve_body($response);
            $json_body = json_decode($body, true);
            
            $error_message = 'ElevenLabs API Error ' . $response_code . ': ';
            if (!empty($json_body['detail'])) {
                // Handle detail as either string or array
                if (is_array($json_body['detail'])) {
                    $error_message .= esc_html(wp_json_encode($json_body['detail']));
                } else {
                    $error_message .= esc_html($json_body['detail']);
                }
            } else {
                $error_message .= 'Unknown error';
            }
            $this->log_message($error_message, 'error');
            
            return $this->format_api_error_response($error_message);
        }
    }

    /**
     * Format API error response
     *
     * @param string $error_message Error message
     * @return array Formatted error response
     */
    // The format_api_error_response method is already defined earlier in the class
    
    /**
     * Show admin notice for encryption errors
     */
    public function show_encryption_error_notice() {
        if (current_user_can('manage_options')) {
            ?>
            <div class="notice notice-error is-dismissible">
                <p><?php esc_html_e('Mementor Text to Speech: API key decryption failed. Please re-enter your API key in the plugin settings.', 'text-to-speech-tts'); ?></p>
            </div>
            <?php
        }
    }
    
    /**
     * Get decrypted shared API key
     *
     * @return string|null Decrypted API key or null
     */
    private function get_decrypted_shared_key() {
        return null;
    }
    
    /**
     * Check if user can use shared API key
     * 
     * @return bool True if user has credits remaining
     */
    private function can_use_shared_api_key() {
        // Include credits class if not already loaded
        if (!class_exists('Mementor_TTS_User_Credits')) {
            require_once plugin_dir_path(__FILE__) . 'class-mementor-tts-user-credits.php';
        }
        
        $user_credits = Mementor_TTS_User_Credits::get_instance();
        
        // Check if user has credits remaining
        $has_credits = $user_credits->has_credits_remaining();
        $remaining = $user_credits->get_remaining_credits();
        
        $debug_mode = get_option('mementor_tts_debug_mode', '0') === '1';
        if ($debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
            error_log('[TTS API] Checking shared key eligibility: has_credits=' . ($has_credits ? 'YES' : 'NO') . ', remaining=' . $remaining);
        }
        
        // Also check if this might be a reset attempt
        if ($has_credits) {
            $is_reset_attempt = $user_credits->detect_reset_attempt();
            if ($is_reset_attempt) {
                if ($debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
                    error_log('[TTS API] Potential reset attempt detected, blocking shared key usage');
                }
                return false;
            }
        }
        
        return $has_credits;
    }

    /**
     * Speech to Text - Transcribe audio/video file
     *
     * @param string $file_path Path to the audio/video file
     * @param array $options Transcription options (language, tag_events, include_timestamps)
     * @return array|WP_Error Transcription result or error
     */
    public function speech_to_text($file_path, $options = array()) {
        // Get the API key using the same method as text_to_speech for consistency
        $api_key_to_use = $this->get_api_key_to_use('', null, $options);

        // Validate API key
        if (empty($api_key_to_use)) {
            return new WP_Error('no_api_key', __('No API key found', 'text-to-speech-tts'));
        }

        if ($this->debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
            error_log('[TTS STT API] API Key length: ' . strlen($api_key_to_use));
            error_log('[TTS STT API] API Key ends with: ...' . substr($api_key_to_use, -4));
        }

        // Validate file exists
        if (!file_exists($file_path)) {
            return new WP_Error('file_not_found', __('File not found', 'text-to-speech-tts'));
        }

        // Default options
        $defaults = array(
            'language' => 'auto',
            'tag_events' => false,
            'include_timestamps' => false
        );
        $options = wp_parse_args($options, $defaults);

        // Prepare the file for upload
        $boundary = wp_generate_password(24, false);
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Raw bytes required for multipart/form-data upload to ElevenLabs; WP_Filesystem returns a wrapped read that isn't suitable here.
        $file_content = file_get_contents($file_path);
        $filename = basename($file_path);

        // Build multipart form data
        $body = '';

        // Add file
        $body .= "--{$boundary}\r\n";
        $body .= "Content-Disposition: form-data; name=\"file\"; filename=\"{$filename}\"\r\n";
        $body .= "Content-Type: application/octet-stream\r\n\r\n";
        $body .= $file_content . "\r\n";

        // Add model
        $body .= "--{$boundary}\r\n";
        $body .= "Content-Disposition: form-data; name=\"model_id\"\r\n\r\n";
        $body .= "scribe_v1\r\n";

        // Add language_code if not auto
        if ($options['language'] !== 'auto') {
            $body .= "--{$boundary}\r\n";
            $body .= "Content-Disposition: form-data; name=\"language_code\"\r\n\r\n";
            $body .= $options['language'] . "\r\n";
        }

        // Add tag_audio_events
        if ($options['tag_events']) {
            $body .= "--{$boundary}\r\n";
            $body .= "Content-Disposition: form-data; name=\"tag_audio_events\"\r\n\r\n";
            $body .= "true\r\n";
        }

        $body .= "--{$boundary}--\r\n";

        // Make API request
        $endpoint = 'speech-to-text';
        $url = $this->api_base_url . '/' . $endpoint;

        if ($this->debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
            error_log('[TTS STT API] Request URL: ' . $url);
            error_log('[TTS STT API] Boundary: ' . $boundary);
            error_log('[TTS STT API] Body length: ' . strlen($body));
        }

        $response = wp_remote_post($url, array(
            'timeout' => 300, // 5 minutes for file upload
            'headers' => array(
                'xi-api-key' => $api_key_to_use,
                'Content-Type' => 'multipart/form-data; boundary=' . $boundary
            ),
            'body' => $body
        ));

        // Check for errors
        if (is_wp_error($response)) {
            if ($this->debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
                error_log('[TTS STT API] Request error: ' . $response->get_error_message());
            }
            return $response;
        }

        $response_code = wp_remote_retrieve_response_code($response);
        $response_body = wp_remote_retrieve_body($response);

        if ($this->debug_mode && defined('WP_DEBUG') && WP_DEBUG) {
            error_log('[TTS STT API] Response code: ' . $response_code);
            error_log('[TTS STT API] Response body: ' . $response_body);
        }

        // Parse response
        if ($response_code === 200) {
            $data = json_decode($response_body, true);

            if (json_last_error() !== JSON_ERROR_NONE) {
                return new WP_Error('invalid_response', __('Invalid API response', 'text-to-speech-tts'));
            }

            return array(
                'success' => true,
                'text' => isset($data['text']) ? $data['text'] : '',
                'data' => $data
            );
        } else {
            $error_data = json_decode($response_body, true);
            $error_message = isset($error_data['detail']['message'])
                ? $error_data['detail']['message']
                : __('Transcription failed', 'text-to-speech-tts');

            return new WP_Error('api_error', $error_message, array('status' => $response_code));
        }
    }
}
