<?php
/**
 * The admin-specific functionality of the plugin.
 *
 * @link       https://mementor.no
 * @since      1.0.2
 *
 * @package    TTSWP
 * @subpackage Mementor_TTS/admin
 */

// Prevent direct access to this file
if (!defined('ABSPATH')) {
    exit;
}

require_once __DIR__ . '/class-mementor-tts-admin-columns.php';
require_once __DIR__ . '/class-mementor-tts-admin-bar.php';
require_once __DIR__ . '/class-mementor-tts-weglot-columns.php';

/**
 * The admin-specific functionality of the plugin.
 *
 * Defines the plugin name, version, and hooks for
 * enqueuing the admin-specific stylesheet and JavaScript.
 *
 * @package    TTSWP
 * @subpackage Mementor_TTS/admin
 * @author     Mementor <support@mementor.no>
 */
class Mementor_TTS_Admin {

    /**
     * The ID of this plugin.
     */
    private $plugin_name;

    /**
     * The version of this plugin.
     */
    private $version;

    /**
     * Post-list column handler (extracted).
     *
     * @var Mementor_TTS_Admin_Columns
     */
    private $columns;

    /**
     * WP admin-bar menu handler (extracted).
     *
     * @var Mementor_TTS_Admin_Bar
     */
    private $admin_bar;

    /**
     * Weglot per-language column handler (restored from retired PRO).
     *
     * @var Mementor_TTS_Weglot_Columns
     */
    private $weglot_columns;

    /**
     * Initialize the class and set its properties.
     */
    public function __construct($plugin_name, $version) {
        $this->plugin_name = $plugin_name;
        $this->version = $version;

        // Add filter to modify the Support Forum menu item
        add_filter('admin_menu', function() {
            global $submenu;
            if (isset($submenu['text-to-speech-tts-settings']) && is_array($submenu['text-to-speech-tts-settings'])) {
                foreach ($submenu['text-to-speech-tts-settings'] as $key => $item) {
                    // Add null safety checks to prevent PHP 8+ deprecation warnings
                    if (is_array($item) && isset($item[2]) && is_string($item[2]) && $item[2] === 'https://wordpress.org/support/plugin/text-to-speech-tts/') {
                        $submenu['text-to-speech-tts-settings'][$key][0] = __('Support Forum', 'text-to-speech-tts') . 
                            ' <span class="dashicons dashicons-external" style="font-size: 14px; width: 14px; height: 14px; vertical-align: middle; opacity: 0.7;"></span>';
                        $submenu['text-to-speech-tts-settings'][$key][3] = 'target="_blank"';
                    }
                }
            }
        });

        // Add settings registration hook
        add_action('admin_init', array($this, 'register_settings'));
        add_action('admin_init', array($this, 'register_settings_sections'));
        add_action('admin_init', array($this, 'handle_player_reset'));

        // Clear dynamic CSS cache when any player setting changes
        add_action('update_option_mementor_tts_player_bg_color', function() { delete_transient('mementor_tts_dynamic_css'); });
        add_action('update_option_mementor_tts_player_border_color', function() { delete_transient('mementor_tts_dynamic_css'); });
        add_action('update_option_mementor_tts_player_accent_color', function() { delete_transient('mementor_tts_dynamic_css'); });
        add_action('update_option_mementor_tts_player_text_color', function() { delete_transient('mementor_tts_dynamic_css'); });
        add_action('update_option_mementor_tts_play_button_bg', function() { delete_transient('mementor_tts_dynamic_css'); });
        add_action('update_option_mementor_tts_timer_color', function() { delete_transient('mementor_tts_dynamic_css'); });
        add_action('update_option_mementor_tts_progress_bar_default', function() { delete_transient('mementor_tts_dynamic_css'); });
        add_action('update_option_mementor_tts_progress_bar_playing', function() { delete_transient('mementor_tts_dynamic_css'); });
        add_action('update_option_mementor_tts_player_padding', function() { delete_transient('mementor_tts_dynamic_css'); });
        add_action('update_option_mementor_tts_player_opacity', function() { delete_transient('mementor_tts_dynamic_css'); });
        add_action('update_option_mementor_tts_play_icon_size', function() { delete_transient('mementor_tts_dynamic_css'); });
        add_action('update_option_mementor_tts_font_size', function() { delete_transient('mementor_tts_dynamic_css'); });

        // Register the plugin settings page
        add_action('admin_menu', array($this, 'add_plugin_admin_menu'));

        // Add late white label menu check - register this SEPARATELY from inside admin_menu
        add_action('admin_menu', array($this, 'add_white_label_menu_late'), 999);

        
        // Add very early filter to fix null query strings
        add_filter('query_vars', function($query_vars) {
            // Ensure query vars are never null
            if (!is_array($query_vars)) {
                $query_vars = array();
            }
            return array_map(function($var) {
                return $var ?? '';
            }, $query_vars);
        }, 1);
        
        // Add filter to fix request parsing
        add_filter('request', function($query_vars) {
            // Ensure all values in request are never null
            if (is_array($query_vars)) {
                foreach ($query_vars as $key => $value) {
                    if ($value === null) {
                        $query_vars[$key] = '';
                    }
                }
            }
            return $query_vars;
        }, 1);
        
        // Register AJAX action for audio generation
        // DISABLED: Using the handler in class-mementor-tts-ajax.php instead which properly handles Fusion Builder
        // add_action('wp_ajax_mementor_tts_generate_audio', array($this, 'generate_audio_ajax'));
        
        // Register AJAX action for refreshing statistics
        add_action('wp_ajax_mementor_tts_refresh_stats', array($this, 'refresh_stats_ajax'));

        // Post-list TTS column — extracted to Mementor_TTS_Admin_Columns.
        $this->columns = new Mementor_TTS_Admin_Columns($plugin_name, $version);
        $this->columns->register_hooks();

        // WP admin-bar quick-access menu — extracted to Mementor_TTS_Admin_Bar.
        $this->admin_bar = new Mementor_TTS_Admin_Bar();
        $this->admin_bar->register_hooks();

        // Weglot per-language audio rows in the post-list column (paid plan).
        $this->weglot_columns = new Mementor_TTS_Weglot_Columns();
        $this->weglot_columns->register_hooks();

        // Add bulk action for generating audio (PRO feature)
        add_action('admin_init', array($this, 'add_bulk_generate_audio_action'));

        // Register all AJAX handlers
        $this->register_ajax_handlers();

        // Global low-credit admin notice
        add_action( 'admin_notices', array( $this, 'display_low_credit_banner' ) );

        // One-time notice when PRO plugin was auto-deactivated
        add_action( 'admin_notices', array( $this, 'display_pro_deactivated_notice' ) );

        // Fix double-encrypted API keys (runs once with high priority)
        
        // Add high priority filter to ensure custom voice ID is used
        add_filter('mementor_tts_voice_for_generation', array($this, 'filter_voice_for_generation'), 1, 2);
        add_filter('mementor_tts_generate_voice_id', array($this, 'filter_voice_for_generation'), 1, 2);
        
        // Also hook into WordPress init to catch early filters
        add_action('init', array($this, 'setup_voice_filters'), 1);
        
        // Add auto-generation on post publish
        add_action('transition_post_status', array($this, 'auto_generate_on_publish'), 10, 3);
        
        // Add cron action for asynchronous audio generation
        add_action('mementor_tts_generate_audio_cron', array($this, 'generate_audio_for_post'));

        // Enqueue styles and scripts
        add_action('admin_enqueue_scripts', array($this, 'enqueue_styles'));
        
        // Add filter to ensure admin page titles are never null (prevents PHP 8+ deprecation warnings)
        add_filter('admin_title', function($admin_title, $title) {
            // Ensure both parameters are never null
            $admin_title = $admin_title ?? '';
            $title = $title ?? '';
            
            // If admin_title is empty but we have a title, use it
            if (empty($admin_title) && !empty($title)) {
                $admin_title = $title . ' &lsaquo; ' . get_bloginfo('name');
            }
            
            return $admin_title;
        }, 10, 2);
        
        // Add early action to ensure globals are initialized for our hidden submenu pages
        add_action('admin_init', function() {
            global $title, $hook_suffix, $current_screen, $pagenow, $parent_file;
            
            // Check if we're on the PRO help page
            if (isset($_GET['page']) && $_GET['page'] === 'text-to-speech-tts-pro-help') {
                // Ensure title is set and never null
                if (empty($title) || $title === null) {
                    $title = __('PRO Help', 'text-to-speech-tts') ?: 'PRO Help';
                }
                // Also set parent file to prevent null issues
                if (empty($parent_file)) {
                    $parent_file = 'text-to-speech-tts-settings';
                }
            }
        }, 1); // Run very early
        
        // Add even earlier hook specifically for our pages to set title
        add_action('load-admin_page_text-to-speech-tts-pro-help', function() {
            global $title;
            // Force set the title for the PRO help page
            $title = __('PRO Help', 'text-to-speech-tts') ?: 'PRO Help';
        });
        
        // Note: Removed global filters that were interfering with WordPress core
        // The issue needs to be fixed at the source rather than filtering core functions
        
        // Add filter specifically for wp_parse_str which is causing the str_replace error
        add_filter('pre_get_option_rewrite_rules', function($pre_option) {
            // This runs before WordPress parses rewrite rules
            // Ensure $_SERVER variables that WordPress uses are never null
            if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === null) {
                $_SERVER['REQUEST_URI'] = '';
            }
            if (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] === null) {
                $_SERVER['QUERY_STRING'] = '';
            }
            return $pre_option;
        }, 1);
        
        // Fix potential null values in URL parsing
        add_action('plugins_loaded', function() {
            // Ensure server variables used by WordPress are never null
            $server_vars = ['REQUEST_URI', 'QUERY_STRING', 'REQUEST_METHOD', 'SERVER_NAME', 'HTTP_HOST'];
            foreach ($server_vars as $var) {
                if (isset($_SERVER[$var]) && $_SERVER[$var] === null) {
                    $_SERVER[$var] = '';
                }
            }
        }, 1);
        
    }

    /**
     * Register AJAX handlers
     */
    private function register_ajax_handlers() {
        // AJAX for generating audio
        // DISABLED: Using the handler in class-mementor-tts-ajax.php instead which properly handles Fusion Builder
        // add_action('wp_ajax_mementor_tts_generate_audio', array($this, 'generate_audio_ajax'));
        
        // AJAX for generating audio from shortcode text — handled by class-mementor-tts-ajax.php

        // AJAX for checking audio generation status
        add_action('wp_ajax_mementor_tts_check_audio_status', array($this, 'check_audio_status_ajax'));
        
        // AJAX for loading voices
        add_action('wp_ajax_mementor_tts_get_voices', array($this, 'load_voices_ajax'));
        
        // AJAX for getting ElevenLabs voices
        add_action('wp_ajax_mementor_tts_get_elevenlabs_voices', array($this, 'get_elevenlabs_voices_ajax'));
        
        // AJAX for deleting ElevenLabs voice
        add_action('wp_ajax_mementor_tts_delete_elevenlabs_voice', array($this, 'delete_elevenlabs_voice_ajax'));
        
        // AJAX for getting ElevenLabs stats
        add_action('wp_ajax_mementor_tts_get_elevenlabs_stats', array($this, 'get_elevenlabs_stats_ajax'));
        
        // AJAX for saving voice
        add_action('wp_ajax_mementor_tts_save_voice', array($this, 'save_voice_ajax'));
        
        // AJAX for testing API key
        add_action('wp_ajax_mementor_tts_test_api_key', array($this, 'test_api_key_ajax'));
        
        // AJAX for deleting audio
        add_action('wp_ajax_mementor_tts_delete_audio', array($this, 'delete_audio_ajax'));

        // AJAX proxy for downloading audio — forces Content-Disposition: attachment
        // even when the file lives on a cross-origin CDN where the HTML `download`
        // attribute is ignored by the browser.
        add_action('wp_ajax_mementor_tts_download_audio', array($this, 'download_audio_ajax'));

        // AJAX for validating API key
        add_action('wp_ajax_mementor_tts_validate_api_key', array($this, 'validate_api_key_ajax'));
        
        // AJAX for canceling processing
        add_action('wp_ajax_mementor_tts_cancel_processing', array($this, 'cancel_processing_ajax'));
        
        // AJAX for resetting API settings
        add_action('wp_ajax_mementor_tts_reset_api_settings', array($this, 'reset_api_settings_ajax'));
        
        // AJAX for clear cache
        add_action('wp_ajax_mementor_tts_clear_cache', array($this, 'clear_cache_ajax'));
        
        // AJAX for import settings
        add_action('wp_ajax_mementor_tts_import_settings', array($this, 'import_settings_ajax'));
        
        // AJAX for export settings 
        add_action('wp_ajax_mementor_tts_export_settings', array($this, 'export_settings_ajax'));
        
        // AJAX for resetting player settings
        add_action('wp_ajax_mementor_tts_reset_player_settings', array($this, 'reset_player_settings_ajax'));

        // AJAX for dismissing the Getting Started cards on the Overview page (per-user meta)
        add_action('wp_ajax_mementor_tts_dismiss_getting_started', array($this, 'dismiss_getting_started_ajax'));
        
        // AJAX for getting API key for toggle button
        add_action('wp_ajax_mementor_tts_get_api_key', array($this, 'get_api_key_ajax'));

        // AJAX for getting decrypted API key
        add_action('wp_ajax_mementor_tts_get_decrypted_api_key', array($this, 'get_decrypted_api_key_ajax'));

        // AJAX for special license key activation/deactivation
        add_action('wp_ajax_mementor_tts_activate_special_license', array($this, 'activate_special_license_ajax'));
        add_action('wp_ajax_mementor_tts_deactivate_special_license', array($this, 'deactivate_special_license_ajax'));

        // AJAX for validating API key
        add_action('wp_ajax_mementor_tts_verify_api_key', array($this, 'validate_api_key_ajax'));

        // AJAX for re-validating permissions on an existing stored key
        add_action('wp_ajax_mementor_tts_revalidate_permissions', array($this, 'revalidate_permissions_ajax'));

        // AJAX for getting decrypted API key (alias)
        add_action('wp_ajax_mementor_tts_get_decrypted_key', array($this, 'get_decrypted_api_key_ajax'));
        
        // AJAX for word replacements (PRO feature)
        add_action('wp_ajax_mementor_tts_add_word_replacement', array($this, 'add_word_replacement_ajax'));
        add_action('wp_ajax_mementor_tts_update_word_replacement', array($this, 'update_word_replacement_ajax'));
        add_action('wp_ajax_mementor_tts_delete_word_replacement', array($this, 'delete_word_replacement_ajax'));

        // AJAX for Account page billing (checkout proxy + banner dismiss)
        add_action( 'wp_ajax_tts_checkout_subscription', array( $this, 'checkout_subscription_ajax' ) );
        add_action( 'wp_ajax_tts_checkout_credits', array( $this, 'checkout_credits_ajax' ) );
        add_action( 'wp_ajax_tts_dismiss_credit_banner', array( $this, 'dismiss_credit_banner_ajax' ) );
    }

    /**
     * Sanitize text option
     * 
     * @param string $input The input value to sanitize
     * @return string Sanitized text value
     * @since 1.0.0
     */
    public function sanitize_option_text($input) {
        return sanitize_text_field($input);
    }

    /**
     * Sanitize a multi-line setting that needs to preserve raw characters
     * (including HTML tags) because it will be used as a plain-substring
     * needle against page HTML, never rendered. Admin-only (manage_options).
     */
    public function sanitize_option_match_patterns($input) {
        if (!is_string($input)) {
            return '';
        }
        // Normalize line endings, strip NULs, trim each line, drop empty lines.
        $input = str_replace(array("\r\n", "\r"), "\n", $input);
        $input = str_replace("\0", '', $input);
        $lines = array_filter(array_map('trim', explode("\n", $input)), 'strlen');
        return implode("\n", $lines);
    }
    
    /**
     * Sanitize API key field with encryption
     * 
     * @param string $input The input value to sanitize
     * @return string Encrypted API key or existing key if input is empty
     * @since 1.7.5
     */
    public function sanitize_api_key_field($input) {
        // If input is empty but we have an existing key, keep the existing key
        if (empty($input) && isset($_POST['mementor_tts_api_key_exists']) && $_POST['mementor_tts_api_key_exists'] === '1') {
            // Return the current encrypted value
            return get_option('mementor_tts_api_key', '');
        }
        
        // If input is empty and no existing key, return empty
        if (empty($input)) {
            return '';
        }
        
        // Sanitize the input
        $sanitized = sanitize_text_field($input);
        
        // If it's the same as the placeholder or error message, don't save it
        if (strpos($sanitized, 'Failed to decrypt') !== false || 
            strpos($sanitized, '•••') !== false) {
            // Return the current encrypted value
            return get_option('mementor_tts_api_key', '');
        }
        
        // Check if the input is already an encrypted value (prevent double encryption)
        $current_encrypted = get_option('mementor_tts_api_key', '');
        if (!empty($current_encrypted) && $sanitized === $current_encrypted) {
            // Input is the same as the stored encrypted value, don't re-encrypt
            return $current_encrypted;
        }
        
        // Initialize encryption
        require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-encryption.php';
        $encryption = new Mementor_TTS_Encryption();
        
        // Check if input might be an encrypted value by trying to decrypt it
        $test_decrypt = $encryption->decrypt($sanitized);
        if ($test_decrypt !== false && (strlen($test_decrypt) == 32 || strpos($test_decrypt, 'sk_') === 0)) {
            // Input appears to be an encrypted API key, return it as is
            return $sanitized;
        }
        
        // Encrypt the new API key
        $encrypted = $encryption->encrypt($sanitized);

        // Clear any cached permission checks since we have a new API key
        if ($encrypted !== false) {
            // Delete transients for old API key
            global $wpdb;
            $wpdb->query($wpdb->prepare(
                "DELETE FROM {$wpdb->options}
                WHERE option_name LIKE %s
                OR option_name LIKE %s",
                '_transient_mementor_tts_api_permissions_check_%',
                '_transient_timeout_mementor_tts_api_permissions_check_%'
            ));
        }

        // Return encrypted key or empty string if encryption failed
        return $encrypted !== false ? $encrypted : '';
    }
    
    /**
     * Sanitize S3 Secret Access Key field with encryption
     *
     * @param string $input The input value to sanitize
     * @return string Encrypted S3 secret key or existing key if input is empty
     * @since 1.0.0
     */
    public function sanitize_s3_secret_key_field($input) {
        // Initialize encryption
        require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-encryption.php';
        $encryption = new Mementor_TTS_Encryption();

        // If input is empty but we have an existing key, check if it needs encryption
        if (empty($input) && isset($_POST['mementor_tts_s3_secret_key_exists']) && $_POST['mementor_tts_s3_secret_key_exists'] === '1') {
            $current_key = get_option('mementor_tts_s3_secret_key', '');

            // If current key exists, check if it's encrypted
            if (!empty($current_key)) {
                $test_decrypt = $encryption->decrypt($current_key);

                // If decryption fails, it's an old unencrypted key - encrypt it now
                if ($test_decrypt === false) {
                    $encrypted = $encryption->encrypt($current_key);
                    return $encrypted !== false ? $encrypted : $current_key;
                }
            }

            // Return the current value (already encrypted or empty)
            return $current_key;
        }

        // If input is empty and no existing key, return empty
        if (empty($input)) {
            return '';
        }

        // Sanitize the input
        $sanitized = sanitize_text_field($input);

        // If it's the same as the placeholder or error message, don't save it
        if (strpos($sanitized, 'Failed to decrypt') !== false ||
            strpos($sanitized, '•••') !== false) {
            // Return the current encrypted value
            return get_option('mementor_tts_s3_secret_key', '');
        }

        // Check if the input is already an encrypted value (prevent double encryption)
        $current_encrypted = get_option('mementor_tts_s3_secret_key', '');
        if (!empty($current_encrypted) && $sanitized === $current_encrypted) {
            // Input is the same as the stored encrypted value, don't re-encrypt
            return $current_encrypted;
        }

        // Check if input might be an encrypted value by trying to decrypt it
        $test_decrypt = $encryption->decrypt($sanitized);
        if ($test_decrypt !== false) {
            // Input appears to be an encrypted value, return it as is
            return $sanitized;
        }

        // Encrypt the new secret key
        $encrypted = $encryption->encrypt($sanitized);

        // Return encrypted key or empty string if encryption failed
        return $encrypted !== false ? $encrypted : '';
    }

    /**
     * Sanitize boolean option
     *
     * @param mixed $input The input value to sanitize
     * @return bool Sanitized boolean value
     * @since 1.0.0
     */
    public function sanitize_player_placement($input) {
        $allowed = array('disabled', 'before', 'after');
        return in_array($input, $allowed, true) ? $input : 'disabled';
    }

    public function sanitize_product_player_position($input) {
        $map = array(
            'short_before' => array('short' => 'before', 'long' => 'disabled'),
            'short_after'  => array('short' => 'after',  'long' => 'disabled'),
            'long_before'  => array('short' => 'disabled', 'long' => 'before'),
            'long_after'   => array('short' => 'disabled', 'long' => 'after'),
        );
        $values = isset($map[$input]) ? $map[$input] : $map['short_before'];
        update_option('mementor_tts_product_player_short_desc', $values['short']);
        update_option('mementor_tts_product_player_long_desc', $values['long']);
        return $input;
    }

    public function sanitize_option_boolean($input) {
        // Handle string values properly for checkboxes
        if (is_string($input)) {
            return ($input === '1' || $input === 'true' || $input === 'on');
        }
        return (bool) $input;
    }
    
    /**
     * Sanitize integer option
     * 
     * @param mixed $input The input value to sanitize
     * @return int Sanitized integer value
     * @since 1.0.0
     */
    public function sanitize_option_integer($input) {
        return absint($input);
    }
    
    /**
     * Sanitize signed integer (allows negative values)
     * 
     * @param mixed $input The input value to sanitize
     * @return int Sanitized integer value (can be negative)
     * @since 1.4.8
     */
    public function sanitize_signed_integer($input) {
        return intval($input);
    }
    
    /**
     * Sanitize hex color
     * 
     * @param string $color The color value to sanitize
     * @return string Sanitized color value
     * @since 1.0.0
     */
    public function sanitize_color($color) {
        if (empty($color) || 'transparent' === $color) {
            return 'transparent';
        }

        // 3 or 6 hex digits, or the empty string.
        if (preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color)) {
            return $color;
        }

        return '';
    }
    
    /**
     * Sanitize array
     * 
     * @param mixed $input The input value to sanitize
     * @return array Sanitized array
     * @since 1.0.0
     */
    public function sanitize_array($input) {
        // If input is not an array, make it an array with the input as a single element
        if (!is_array($input)) {
            return array(sanitize_text_field($input));
        }
        
        // Sanitize each element of the array
        $sanitized_input = array();
        foreach ($input as $key => $value) {
            if (is_array($value)) {
                // For nested arrays, recursively sanitize
                $sanitized_value = array();
                foreach ($value as $inner_key => $inner_value) {
                    $sanitized_value[$inner_key] = sanitize_text_field($inner_value);
                }
                $sanitized_input[$key] = $sanitized_value;
            } else {
                $sanitized_input[$key] = sanitize_text_field($value);
            }
        }
        
        return $sanitized_input;
    }
    
    /**
     * Register plugin settings
     */
    public function register_settings() {
        // API Settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_settings', 'mementor_tts_api_key', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_api_key_field'),
            'default' => ''
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_settings', 'mementor_tts_service', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'elevenlabs'
        ));
        // mementor_tts_voice and mementor_tts_voice_name are saved via AJAX only (save_voice handler)
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_settings', 'mementor_tts_language', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'en'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_settings', 'mementor_tts_model', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'eleven_multilingual_v2'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_settings', 'mementor_tts_quality', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => '128'
        ));
        
        // Custom Filename Pattern Setting
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_settings', 'mementor_tts_filename_pattern', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'mementor-[id]-[language]'
        ));
        
        // Media Library Setting - Use separate group to prevent resetting other settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_media_settings', 'mementor_tts_media_library', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'no'
        ));
        
        // Media Offload Settings - Use separate group to prevent resetting other settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_offload_settings', 'mementor_tts_offload_enabled', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'no'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_offload_settings', 'mementor_tts_offload_provider', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'local'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_offload_settings', 'mementor_tts_s3_access_key', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => ''
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_offload_settings', 'mementor_tts_s3_secret_key', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_s3_secret_key_field'),
            'default' => ''
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_offload_settings', 'mementor_tts_s3_region', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'us-east-1'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_offload_settings', 'mementor_tts_s3_bucket', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => ''
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_offload_settings', 'mementor_tts_s3_path', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'tts-audio'
        ));

        // Statistics Setting - Use separate group to prevent resetting other settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_statistics_settings', 'mementor_tts_enable_statistics', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'no'
        ));
        
        // SEO Schema Setting - Use separate group to prevent resetting other settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_seo_settings', 'mementor_tts_enable_schema', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'no'
        ));
        
        // Voice Settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_voices_settings', 'mementor_tts_model', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'eleven_multilingual_v2'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_voices_settings', 'mementor_tts_language', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'en'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_voices_settings', 'mementor_tts_quality', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'standard'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        // mementor_tts_voice and mementor_tts_voice_name are saved via AJAX (save_voice handler)
        // Do NOT register them here — options.php would overwrite the AJAX-saved value with stale form data
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_voices_settings', 'mementor_tts_speaker_boost', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_voices_settings', 'mementor_tts_stability_preset', array(
            'type' => 'integer',
            'sanitize_callback' => array($this, 'sanitize_option_integer'),
            'default' => 50
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_voices_settings', 'mementor_tts_speed_preset', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => '1.0'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_voices_settings', 'mementor_tts_similarity_preset', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => '75'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_voices_settings', 'mementor_tts_style_preset', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => '0'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_voices_settings', 'mementor_tts_privacy_mode', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_checkbox'),
            'default' => false
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_voices_settings', 'mementor_tts_custom_voice_id', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_custom_voice_id'),
            'default' => ''
        ));
        // Language-voice mappings (multi-language support)
        register_setting('mementor_tts_voices_settings', 'mementor_tts_language_voice_mappings', array(
            'type' => 'array',
            'sanitize_callback' => array($this, 'sanitize_language_voice_mappings'),
            'default' => array()
        ));

        // Post Types Settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_post_types_settings', 'mementor_tts_post_types', array(
            'type'              => 'array',
            'default'           => array('post'),
            'sanitize_callback' => array($this, 'sanitize_post_types_array'),
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_post_types_settings', 'mementor_tts_exclude_ids', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => ''
        ));
        
        // Content Settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_include_title', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_pause_after_title', array(
            'type' => 'number',
            'sanitize_callback' => array($this, 'sanitize_pause_duration'),
            'default' => 0.6
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_pause_between_paragraphs', array(
            'type' => 'number',
            'sanitize_callback' => array($this, 'sanitize_pause_duration'),
            'default' => 0.6
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_pause_after_headings', array(
            'type' => 'number',
            'sanitize_callback' => array($this, 'sanitize_pause_duration'),
            'default' => 0.6
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_include_author', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_include_date', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_include_excerpt', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_include_image_captions', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_text_before', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => ''
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_text_after', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => ''
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_include_custom_fields', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_custom_fields', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => ''
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_include_tags', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));

        // Product Audio Settings (WooCommerce)
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_include_title', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_include_price', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_include_stock', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_include_category', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_include_image', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_image_alt', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_image_title', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_image_caption', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_image_description', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_include_short_desc', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_include_long_desc', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_player_short_desc', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_player_placement'),
            'default' => 'disabled'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_player_long_desc', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_player_placement'),
            'default' => 'before'
        ));
        // Combined product player position — splits into short_desc/long_desc on save
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_player_position', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_product_player_position'),
            'default' => 'short_before'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_player_label', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => ''
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_content_settings', 'mementor_tts_product_hide_label', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        // Product text templates
        $text_template_options = array(
            'mementor_tts_product_text_price',
            'mementor_tts_product_text_sale',
            'mementor_tts_product_text_in_stock',
            'mementor_tts_product_text_out_of_stock',
            'mementor_tts_product_text_stock_qty',
            'mementor_tts_product_text_category',
            'mementor_tts_product_text_categories',
        );
        foreach ($text_template_options as $opt) {
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
            register_setting('mementor_tts_content_settings', $opt, array(
                'type' => 'string',
                'sanitize_callback' => array($this, 'sanitize_option_text'),
                'default' => ''
            ));
        }

        // CSS Selectors Settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_css_selectors_settings', 'mementor_tts_include_selectors', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => '.post-content, .wp-block-post-content, .page-content'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_css_selectors_settings', 'mementor_tts_exclude_selectors', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => '#ad-wrapper, .sidebar, .comments, .advertisement, .mementor-tts-player-container, .mementor-tts-player, .mementor-tts-player-wrapper'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_css_selectors_settings', 'mementor_tts_truncate_after', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_match_patterns'),
            'default' => ''
        ));

        // Automation Settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_automation_settings', 'mementor_tts_cron_enabled', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_automation_settings', 'mementor_tts_cron_interval', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'daily'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_automation_settings', 'mementor_tts_cron_limit', array(
            'type' => 'integer',
            'sanitize_callback' => array($this, 'sanitize_option_integer'),
            'default' => 5
        ));

        // Auto Generation Settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_auto_generation_settings', 'mementor_tts_auto_generate_on_publish', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));

        // Player Settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_position', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'top'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_style', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'standard'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_background_color', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_color'),
            'default' => '#f6f7fb'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_text_color', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_color'),
            'default' => '#50575e'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_button_color', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_color'),
            'default' => '#0d6efd'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_button_text', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'Listen'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_show_time', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        
        // Add show_info setting registration
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_show_info', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        
        // Add speed_control setting registration
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_speed_control', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));

        // Add show_download setting registration
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_show_download', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));

        // Add show_volume setting registration
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_show_volume', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));

        // Add show_play_bg setting registration
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_show_play_bg', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        
        // Add autoplay setting registration
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_autoplay', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        
        // Add loop setting registration
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_loop', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));

        // Shortcode Settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_shortcode_settings', 'mementor_tts_shortcode_enabled', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_shortcode_settings', 'mementor_tts_shortcode_tag', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'tts'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_shortcode_settings', 'mementor_tts_shortcode_storage', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'uploads'
        ));
        
        // Advanced Settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_advanced_settings', 'mementor_tts_cache_enabled', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_advanced_settings', 'mementor_tts_preload_audio', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_advanced_settings', 'mementor_tts_debug_mode', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false
        ));

        register_setting('mementor_tts_advanced_settings', 'mementor_tts_remote_debug_enabled', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false,
        ));

        register_setting('mementor_tts_advanced_settings', 'mementor_tts_remote_debug_include_content', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => false,
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_advanced_settings', 'mementor_tts_delete_data', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_advanced_settings', 'mementor_tts_storage_location', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'uploads'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_type', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'default'
        ));

        // Width Settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_max_width', array(
            'type' => 'integer',
            'sanitize_callback' => array($this, 'sanitize_max_width'),
            'default' => 100
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_max_width_unit', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_width_unit'),
            'default' => '%'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_alignment', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_alignment'),
            'default' => 'left'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_width', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'full_width'
        ));

        // Outer Spacing Settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_margin_top', array(
            'type' => 'integer',
            'sanitize_callback' => array($this, 'sanitize_signed_integer'),
            'default' => 0
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_margin_bottom', array(
            'type' => 'integer',
            'sanitize_callback' => array($this, 'sanitize_signed_integer'),
            'default' => 0
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_advanced_settings', 'mementor_tts_api_timeout', array(
            'type' => 'integer',
            'sanitize_callback' => array($this, 'sanitize_api_timeout'),
            'default' => 30
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_advanced_settings', 'mementor_tts_api_retry_attempts', array(
            'type' => 'integer',
            'sanitize_callback' => array($this, 'sanitize_retry_attempts'),
            'default' => 2
        ));

        // Player Label Settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_show_player_label', array(
            'type' => 'boolean',
            'sanitize_callback' => array($this, 'sanitize_option_boolean'),
            'default' => true
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_label', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'Listen to this article:'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_label_position', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => 'left'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_label_size', array(
            'type' => 'integer',
            'sanitize_callback' => array($this, 'sanitize_option_integer'),
            'default' => 14
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_label_weight', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_option_text'),
            'default' => '400'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_label_color', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_color'),
            'default' => '#1d2327'
        ));

        // Colors settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_bg_color', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_color'),
            'default' => '#f8f9fa'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_border_color', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_color'),
            'default' => '#dcdcde'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_play_button_bg', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_color'),
            'default' => '#ffffff'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_accent_color', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_color'),
            'default' => '#2271b1'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_timer_color', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_color'),
            'default' => '#1d2327'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_progress_bar_default', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_color'),
            'default' => '#e0e0e0'
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_progress_bar_playing', array(
            'type' => 'string',
            'sanitize_callback' => array($this, 'sanitize_color'),
            'default' => '#2271b1'
        ));

        // Font & Padding settings
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_play_icon_size', array(
            'type' => 'integer',
            'sanitize_callback' => array($this, 'sanitize_option_integer'),
            'default' => 24
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_font_size', array(
            'type' => 'integer',
            'sanitize_callback' => array($this, 'sanitize_option_integer'),
            'default' => 14
        ));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic -- Using standard WordPress OOP callback pattern with proper sanitization
        register_setting('mementor_tts_player_settings', 'mementor_tts_player_padding', array(
            'type' => 'integer',
            'sanitize_callback' => array($this, 'sanitize_option_integer'),
            'default' => 12
        ));

        register_setting('mementor_tts_player_settings', 'mementor_tts_player_opacity', array(
            'type' => 'number',
            'sanitize_callback' => function( $val ) {
                $val = floatval( $val );
                return max( 0.1, min( 1, $val ) );
            },
            'default' => 1
        ));

        register_setting('mementor_tts_player_settings', 'mementor_tts_progress_type', array(
            'type' => 'string',
            'sanitize_callback' => function( $val ) {
                return in_array( $val, array( 'linear', 'waveform_progress' ), true ) ? $val : 'linear';
            },
            'default' => 'linear'
        ));

    }
    
    /**
     * Register settings sections and fields
     */
    public function register_settings_sections() {
        // This method can be implemented if you need to add settings sections and fields
        // For now, we'll leave it empty since we're just registering the settings
    }

    /**
     * Get only a custom white-label logo URL (empty if none set).
     *
     * @since    2.1.3
     * @return   string    Custom logo URL or empty string
     */
    public static function get_custom_white_label_logo() {
        if (ttswp_has_paid_plan()) {
            $custom_logo = get_option('mementor_tts_white_label_logo', '');
            if (!empty($custom_logo)) {
                return $custom_logo;
            }
        }
        return '';
    }

    /**
     * Get the white label logo URL
     *
     * @since    1.0.0
     * @return   string    The logo URL
     */
    public static function get_white_label_logo() {
        if (ttswp_has_paid_plan()) {
            $custom_logo = get_option('mementor_tts_white_label_logo', '');
            if (!empty($custom_logo)) {
                return $custom_logo;
            }
        }
        return MEMENTOR_TTS_PLUGIN_URL . 'admin/images/Mementor-Logo-Header.png';
    }

    /**
     * Get the white label plugin name
     *
     * @since    1.0.0
     * @return   string    The plugin name
     */
    public static function get_white_label_name() {
        if (ttswp_has_paid_plan()) {
            $custom_name = get_option('mementor_tts_white_label_name', '');
            if (!empty($custom_name)) {
                return $custom_name;
            }
        }
        return __('Text to Speech (TTS)', 'text-to-speech-tts');
    }

    /**
     * Register and enqueue the stylesheets for the admin area.
     *
     * @since    1.0.0
     * @param    string    $hook    The current admin page.
     */
    public function enqueue_styles($hook) {
        // Ensure $hook is never null to prevent PHP 8+ deprecation warnings
        $hook = $hook ?? '';
        
        // Check if we're on a plugin page or a post-related page with enabled post type
        $is_plugin_page = strpos($hook, 'text-to-speech-tts') !== false;
        $is_post_editor = in_array($hook, array('post.php', 'post-new.php'));
        $is_posts_list = $hook === 'edit.php';
        
        $current_post_type = '';
        if ($is_post_editor || $is_posts_list) {
             global $typenow;
             $post = get_post();
             
             // First try to get post type from current post
             $current_post_type = $post ? $post->post_type : '';
             
             // If not available, try global $typenow
             if (empty($current_post_type) && $typenow) {
                 $current_post_type = $typenow;
             }
             // As a last resort, check $_GET['post_type'] with proper nonce verification
             elseif (empty($current_post_type) && isset($_GET['post_type'])) {
                 // Verify nonce for post type operations
                 $post_type_nonce_verified = isset($_GET['_wpnonce']) && wp_verify_nonce(sanitize_key($_GET['_wpnonce']), 'mementor_tts_post_type_nonce');
                 
                 // Only use the post_type parameter if nonce is verified or we're just viewing (not submitting data)
                 if ($post_type_nonce_verified || !isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'POST') {
                     $current_post_type = sanitize_key($_GET['post_type']);
                 }
             }
             
             // Default to 'post' if no post type was determined
             if (empty($current_post_type) && ($hook === 'post-new.php' || $hook === 'edit.php')) {
                 $current_post_type = 'post'; // Default assumption
             }
         }

        $enabled_post_types = get_option('mementor_tts_post_types', array('post'));
        $is_enabled_post_type = ($is_post_editor || $is_posts_list) && in_array($current_post_type, $enabled_post_types);

        // Only load base styles on relevant pages
        if (!$is_plugin_page && !$is_enabled_post_type) {
            return;
        }
        
        // Hide third-party admin notices on our plugin pages
        if ($is_plugin_page) {
            // Remove all admin notices from other plugins - use priority 0 to run first
            add_action('in_admin_header', array($this, 'remove_third_party_notices'), 0);
            add_action('admin_notices', array($this, 'remove_third_party_notices'), 0);
            add_action('all_admin_notices', array($this, 'remove_third_party_notices'), 0);
            add_action('network_admin_notices', array($this, 'remove_third_party_notices'), 0);
            add_action('user_admin_notices', array($this, 'remove_third_party_notices'), 0);
            
            // Add CSS to hide any notices that slip through
            add_action('admin_print_styles', function() {
                echo '<style>
                /* Hide ALL admin notices on our plugin pages except our own */
                .notice:not(.mementor-tts-notice),
                .error:not(.mementor-tts-notice),
                .updated:not(.mementor-tts-notice),
                .update-nag:not(.mementor-tts-notice),
                .notice-error:not(.mementor-tts-notice),
                .notice-warning:not(.mementor-tts-notice),
                .notice-success:not(.mementor-tts-notice),
                .notice-info:not(.mementor-tts-notice),
                div[class*="notice"]:not(.mementor-tts-notice):not(.mementor-tts-welcome-card):not(.mementor-tts-settings-card):not(.mementor-tts-api-settings-card),
                div[id*="message"]:not(.mementor-tts-notice),
                /* Specifically target Error Log Monitor notices */
                #elm-sw-setup-wizard-notice,
                .elm-notice,
                .fs-notice,
                .fs-notice.fs-slug-error-log-monitor,
                /* Target common plugin notice patterns */
                [class*="admin-notice"]:not(.mementor-tts-notice),
                [class*="admin_notice"]:not(.mementor-tts-notice),
                [class*="-notice"]:not(.mementor-tts-notice),
                [id*="-notice"]:not(.mementor-tts-notice),
                /* Hide update nags */
                .update-nag,
                .update-message,
                /* Hide common notification containers */
                #wpbody-content > .wrap > .notice,
                #wpbody-content > .wrap > .error,
                #wpbody-content > .wrap > .updated,
                #wpbody-content > .notice,
                #wpbody-content > .error,
                #wpbody-content > .updated {
                    display: none !important;
                }
                
                /* Prevent PRO plugin CSS conflicts on main settings page */
                body.toplevel_page_text-to-speech-tts .mementor-tts-main {
                    display: block !important;
                    flex: unset !important;
                    width: 100% !important;
                }
                
                body.toplevel_page_text-to-speech-tts .mementor-tts-welcome-grid {
                    display: grid !important;
                    grid-template-columns: 1fr 300px !important;
                    gap: 20px !important;
                    align-items: start !important;
                }
                
                body.toplevel_page_text-to-speech-tts .mementor-tts-section {
                    margin-bottom: 20px !important;
                    width: 100% !important;
                    display: block !important;
                }
                </style>';
            });
        } else {
            add_action('admin_print_styles', function() {
                echo '<style>
                /* Prevent PRO plugin CSS conflicts on main settings page */
                body.toplevel_page_text-to-speech-tts .mementor-tts-main {
                    display: block !important;
                    flex: unset !important;
                    width: 100% !important;
                }
                
                body.toplevel_page_text-to-speech-tts .mementor-tts-welcome-grid {
                    display: grid !important;
                    grid-template-columns: 1fr 300px !important;
                    gap: 20px !important;
                    align-items: start !important;
                }
                
                body.toplevel_page_text-to-speech-tts .mementor-tts-section {
                    margin-bottom: 20px !important;
                    width: 100% !important;
                    display: block !important;
                }
                </style>';
            });
        }

        // Enqueue base admin styles.
        // Use filemtime() suffix so CSS edits bust the browser cache immediately,
        // even when the plugin version hasn't bumped. The version still prefixes
        // so major releases still invalidate all caches.
        $admin_css_path = MEMENTOR_TTS_PLUGIN_DIR . 'admin/css/mementor-tts-admin.css';
        $admin_css_ver  = $this->version . ( file_exists( $admin_css_path ) ? '.' . filemtime( $admin_css_path ) : '' );
        wp_enqueue_style(
            $this->plugin_name . '-admin',
            MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-admin.css',
            array('wp-color-picker'),
            $admin_css_ver
        );

        // Enqueue redesign CSS (sidebar layout, design tokens, component styles)
        wp_enqueue_style(
            'tts-admin-redesign',
            MEMENTOR_TTS_PLUGIN_URL . 'admin/css/tts-admin-redesign.css',
            array($this->plugin_name . '-admin'),
            $this->version
        );

        // Enqueue Notifications CSS (used by various JS files)
        wp_enqueue_style(
            $this->plugin_name . '-notifications',
            MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-notifications.css',
            array(),
            $this->version
        );
        
        // Enqueue generator page styles if we're on the generator page
        if (strpos($hook, 'text-to-speech-tts-generator') !== false) {
            wp_enqueue_style(
                $this->plugin_name . '-generator',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-generator.css',
                array($this->plugin_name . '-admin', 'tts-admin-redesign'),
                $this->version
            );
        }

        // Enqueue Speech to Text styles
        if (strpos($hook, 'text-to-speech-tts-speech-to-text') !== false) {
            wp_enqueue_style(
                $this->plugin_name . '-speech-to-text',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-speech-to-text.css',
                array($this->plugin_name . '-admin'),
                $this->version
            );
        }
        
        // Enqueue statistics page styles if we're on the statistics or my-stats-voices page
        if (strpos($hook, 'text-to-speech-tts-statistics') !== false || strpos($hook, 'text-to-speech-tts-my-stats-voices') !== false) {
            wp_enqueue_style(
                $this->plugin_name . '-statistics',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-statistics.css',
                array($this->plugin_name . '-admin'),
                $this->version
            );
        }
        
        // Enqueue shared styles and player preview styles (on plugin pages or post editor)
        if ($is_plugin_page || $is_post_editor) {
            wp_enqueue_style(
                 $this->plugin_name . '-shared',
                 MEMENTOR_TTS_PLUGIN_URL . 'public/css/mementor-tts-shared.css', // Corrected path
                 array($this->plugin_name . '-admin'), // Depends on base admin styles
                 $this->version
             );
            wp_enqueue_style(
                $this->plugin_name . '-player-preview',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-player-preview.css',
                array($this->plugin_name . '-shared'), // Depends on shared styles
                $this->version
            );
            
            // Add inline styles for player preview initialization
            // Get settings needed for the preview variables
            $options = array(
                'bg_color'     => get_option('mementor_tts_player_bg_color', '#f5eee9'),
                'text_color'   => get_option('mementor_tts_player_text_color', '#0e0e0f'),
                'accent_color' => get_option('mementor_tts_player_accent_color', '#2271b1'),
                'border_radius'=> get_option('mementor_tts_player_border_radius', '5'),
                'padding'      => get_option('mementor_tts_player_padding', '15'),
                'max_width'    => get_option('mementor_tts_player_max_width', '100'),
                'width_unit'   => get_option('mementor_tts_player_width_unit', '%'),
                'width'        => get_option('mementor_tts_player_width', '100'),
                'alignment'    => get_option('mementor_tts_player_alignment', 'none'), // none, left, center, right
                'margin_top'   => get_option('mementor_tts_player_margin_top', '0'),
                'margin_bottom'=> get_option('mementor_tts_player_margin_bottom', '10'),
                'border_color' => get_option('mementor_tts_player_border_color', '#cccccc'),
                'shadow'       => get_option('mementor_tts_player_shadow', 'none'), // none, small, medium, large
                'font_family'  => get_option('mementor_tts_player_font_family', 'inherit'),
                'font_size'    => absint(get_option('mementor_tts_player_font_size', '16')), // Ensure integer
                'label_display'=> get_option('mementor_tts_show_player_label', '1') ? 'block' : 'none',
                'label_color'  => get_option('mementor_tts_player_label_color', '#0e0e0f'),
                'label_weight' => get_option('mementor_tts_player_label_weight', 'bold'),
                'play_button_bg' => get_option('mementor_tts_player_play_button_bg', '#ffffff'),
                'play_button_text' => get_option('mementor_tts_player_play_button_text_color', '#0e0e0f'),
                'progress_bg'  => get_option('mementor_tts_player_progress_default', '#e0e0e0'),
                'progress_fg'  => get_option('mementor_tts_player_progress_playing', '#2271b1'),
                'time_color'   => get_option('mementor_tts_player_timer_color', '#50575e'),
                'play_icon_size' => absint(get_option('mementor_tts_player_play_icon_size', 24)) // Add play icon size
                // Add more options as needed for the preview
            );
            
            $margin_value = '0 auto'; // Default for center/none
            if ($options['alignment'] === 'left') {
                $margin_value = $options['margin_top'] . 'px auto ' . $options['margin_bottom'] . 'px 0';
            } elseif ($options['alignment'] === 'right') {
                 $margin_value = $options['margin_top'] . 'px 0 ' . $options['margin_bottom'] . 'px auto';
            } else { // center or none
                $margin_value = $options['margin_top'] . 'px auto ' . $options['margin_bottom'] . 'px auto';
            }
            
            $shadow_value = 'none';
            if ($options['shadow'] === 'small') $shadow_value = '0 2px 5px rgba(0,0,0,0.1)';
            if ($options['shadow'] === 'medium') $shadow_value = '0 5px 15px rgba(0,0,0,0.15)';
            if ($options['shadow'] === 'large') $shadow_value = '0 10px 25px rgba(0,0,0,0.2)';

            $dynamic_css = sprintf(
                ".mementor-tts-preview-player-wrapper {\n                    --preview-player-bg-color: %s;\n                    --preview-player-text-color: %s;\n                    --preview-player-accent-color: %s;\n                    --preview-player-border-radius: %spx;\n                    --preview-player-padding: %spx;\n                    --preview-player-max-width: %s%s;\n                    --preview-player-width: %s%s;\n                    --preview-player-margin: %s;\n                    --preview-player-border-color: %s;\n                    --preview-player-shadow: %s;\n                    --preview-player-font-family: %s;\n                    --preview-player-font-size: %spx;\n                    --preview-player-label-display: %s;\n                    --preview-player-label-color: %s;\n                    --preview-player-label-weight: %s;\n                    --preview-player-play-button-bg: %s;\n                    --preview-player-play-button-text: %s;\n                    --preview-player-progress-bg: %s;\n                    --preview-player-progress-fg: %s;\n                    --preview-player-time-color: %s;\n                    --preview-player-play-icon-size: %spx;\n                }",
                esc_attr($options['bg_color']),
                esc_attr($options['text_color']),
                esc_attr($options['accent_color']),
                esc_attr($options['border_radius']),
                esc_attr($options['padding']),
                esc_attr($options['max_width']),
                esc_attr($options['width_unit']),
                esc_attr($options['width']),
                esc_attr($options['width_unit']),
                esc_attr($margin_value),
                esc_attr($options['border_color']),
                esc_attr($shadow_value),
                esc_attr($options['font_family']),
                esc_attr($options['font_size']),
                esc_attr($options['label_display']),
                esc_attr($options['label_color']),
                esc_attr($options['label_weight']),
                esc_attr($options['play_button_bg']),
                esc_attr($options['play_button_text']),
                esc_attr($options['progress_bg']),
                esc_attr($options['progress_fg']),
                esc_attr($options['time_color']),
                esc_attr($options['play_icon_size']) // Add play icon size arg
                // Add more variables here if needed
            );
            // Use the CORRECT registered handle for wp_add_inline_style
            wp_add_inline_style('mementor-tts-player-preview', $dynamic_css);

            // Waveform JS for admin preview
            if ( get_option('mementor_tts_progress_type', 'linear') === 'waveform_progress' ) {
                wp_enqueue_script(
                    'mementor-tts-waveform',
                    MEMENTOR_TTS_PLUGIN_URL . 'public/js/mementor-tts-waveform.js',
                    array(),
                    $this->version,
                    true
                );
            }
        }
        
        // --- Register Page-Specific Styles --- (Keep registration as is)
        wp_register_style($this->plugin_name . '-voices', MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-voices.css', array($this->plugin_name . '-admin'), $this->version);
        wp_register_style($this->plugin_name . '-affiliate', MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-affiliate.css', array($this->plugin_name . '-admin'), $this->version);
        wp_register_style($this->plugin_name . '-statistics', MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-statistics.css', array($this->plugin_name . '-admin'), $this->version);
        wp_register_style($this->plugin_name . '-shortcodes', MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-shortcodes.css', array($this->plugin_name . '-admin'), $this->version);
        wp_register_style($this->plugin_name . '-advanced', MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-advanced.css', array($this->plugin_name . '-admin'), $this->version);
        wp_register_style($this->plugin_name . '-license', MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-license.css', array($this->plugin_name . '-admin'), $this->version);

        // --- Enqueue styles for specific admin pages based on $_GET['page'] ---
        if ($is_plugin_page && isset($_GET['page'])) {
            // Verify the nonce for page navigation actions
            $nonce_verified = isset($_GET['_wpnonce']) && wp_verify_nonce(sanitize_key($_GET['_wpnonce']), 'mementor_tts_page_nonce');
            
            // Only proceed if nonce is verified or we're just viewing (not submitting data)
            if ($nonce_verified || !isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'POST') {
                $current_page_slug = sanitize_key($_GET['page']);

                switch ($current_page_slug) {
                    case 'text-to-speech-tts-settings': // Main settings page
                    case 'text-to-speech-tts-voices':   // Voices page
                        wp_enqueue_style($this->plugin_name . '-voices');
                        break;
                    case 'text-to-speech-tts-affiliate':
                        wp_enqueue_style($this->plugin_name . '-affiliate');
                        break;
                    case 'text-to-speech-tts-statistics':
                        wp_enqueue_style($this->plugin_name . '-statistics');
                        break;
                    case 'text-to-speech-tts-shortcodes':
                        wp_enqueue_style($this->plugin_name . '-shortcodes');
                        break;
                    case 'text-to-speech-tts-advanced':
                        wp_enqueue_style($this->plugin_name . '-advanced');
                        break;
                    case 'text-to-speech-tts-upgrade':
                        wp_enqueue_style($this->plugin_name . '-license');
                        // tts-account.css enqueued by the consolidated block below (filemtime-busted)
                        break;
                    // Add other cases if needed for other pages
                }
            }
        }
        
        // Enqueue styles needed for the admin columns on the posts list screen
        if ($is_posts_list && $is_enabled_post_type) {
             wp_enqueue_style(
                 $this->plugin_name . '-admin-columns',
                 MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-admin-columns.css',
                 array(), // No dependencies for this specific file
                 $this->version
             );
        }

        // Account-card styles. Loaded on:
        //  - Account page (full layout)
        //  - Overview/Settings page (account-card partial above Quick Stats)
        //  - Upgrade/license page (shares the .tts-license-banner styles)
        if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'text-to-speech-tts-account', 'text-to-speech-tts-settings', 'text-to-speech-tts-upgrade' ), true ) ) {
            $tts_account_css_path = MEMENTOR_TTS_PLUGIN_DIR . 'admin/css/tts-account.css';
            $tts_account_css_ver  = $this->version . ( file_exists( $tts_account_css_path ) ? '.' . filemtime( $tts_account_css_path ) : '' );
            wp_enqueue_style(
                'tts-account',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/css/tts-account.css',
                array( 'tts-admin-redesign' ),
                $tts_account_css_ver
            );
        }

        // This adds the global theme variables, keep it
        $this->add_dynamic_css_variables();
    }

    /**
     * Add dynamic CSS variables based on settings (Global Theme)
     *
     * @since    1.0.0
     */
    private function add_dynamic_css_variables() {
        // Get customized colors from options with proper escaping
        $bg_color = sanitize_hex_color(get_option('mementor_tts_player_bg_color', '#f6f7fb'));
        $border_color = sanitize_hex_color(get_option('mementor_tts_player_border_color', '#dcdcde'));
        $text_color = sanitize_hex_color(get_option('mementor_tts_player_text_color', '#50575e'));
        $accent_color = sanitize_hex_color(get_option('mementor_tts_player_accent_color', '#2271b1'));
        $play_button_bg = sanitize_hex_color(get_option('mementor_tts_player_play_button_bg', '#ffffff'));
        $timer_color = sanitize_hex_color(get_option('mementor_tts_player_timer_color', '#50575e'));
        $progress_default = sanitize_hex_color(get_option('mementor_tts_player_progress_default', '#e0e0e0'));
        $progress_playing = sanitize_hex_color(get_option('mementor_tts_player_progress_playing', '#2271b1'));
        
        // Get customized sizes from options with sanitization
        $play_icon_size = absint(get_option('mementor_tts_player_play_icon_size', 24));
        $icon_size = absint(get_option('mementor_tts_player_icon_size', 20));
        $font_size = absint(get_option('mementor_tts_player_font_size', 14));
        $player_padding = absint(get_option('mementor_tts_player_padding', 15));
        
        $dynamic_css = "
            :root {
                /* Theme Colors */
                --mementor-tts-bg-color: " . esc_attr($bg_color) . " !important;
                --mementor-tts-border-color: " . esc_attr($border_color) . " !important;
                --mementor-tts-text-color: " . esc_attr($text_color) . " !important;
                --mementor-tts-accent-color: " . esc_attr($accent_color) . " !important;
                --mementor-tts-accent-color-hover: " . esc_attr($this->adjust_brightness($accent_color, -10)) . " !important;
                
                /* Component Colors */
                --mementor-tts-play-button-bg: " . esc_attr($play_button_bg) . " !important;
                --mementor-tts-play-button-bg-hover: " . esc_attr($this->adjust_brightness($play_button_bg, -5)) . " !important;
                --mementor-tts-timer-color: " . esc_attr($timer_color) . " !important;
                --mementor-tts-progress-default: " . esc_attr($progress_default) . " !important;
                --mementor-tts-progress-playing: " . esc_attr($progress_playing) . " !important;
                
                /* Sizes */
                --mementor-tts-play-icon-size: " . esc_attr($play_icon_size) . "px !important;
                --mementor-tts-icon-size: " . esc_attr($icon_size) . "px !important;
                --mementor-tts-font-size: " . esc_attr($font_size) . "px !important;
                --mementor-tts-player-padding: " . esc_attr($player_padding) . "px !important;
            }
        ";
        
        // Add the dynamic CSS as inline styles to the admin stylesheet
        wp_add_inline_style('mementor-tts-shared', $dynamic_css);
    }
    
    /**
     * Adjust color brightness
     *
     * @since    1.0.0
     * @param    string    $hex      Hex color code
     * @param    int       $steps    Steps to adjust brightness (negative for darker, positive for lighter)
     * @return   string              Adjusted hex color
     */
    private function adjust_brightness($hex, $steps) {
        // Remove # if present
        $hex = ltrim($hex, '#');
        
        // Convert to RGB
        $r = hexdec(substr($hex, 0, 2));
        $g = hexdec(substr($hex, 2, 2));
        $b = hexdec(substr($hex, 4, 2));
        
        // Adjust brightness
        $r = max(0, min(255, $r + $steps * 8));
        $g = max(0, min(255, $g + $steps * 8));
        $b = max(0, min(255, $b + $steps * 8));
        
        // Convert back to hex
        return '#' . sprintf('%02x%02x%02x', $r, $g, $b);
    }

    /**
     * Get voice ID for generation, checking for custom voice first
     * 
     * @return string The voice ID to use for generation
     */
    private function get_voice_id_for_generation() {
        // Check if paid plan user has a custom voice ID
        if (ttswp_has_paid_plan()) {
            $custom_voice_id = get_option('mementor_tts_custom_voice_id', '');
            if (!empty($custom_voice_id)) {
                return $custom_voice_id;
            }
        }

        // Return the selected voice or default
        return get_option('mementor_tts_voice', 'EXAVITQu4vr4xnSDxMaL');
    }
    
    /**
     * Filter voice ID for generation to use custom voice if available
     * 
     * @param string $voice_id Current voice ID
     * @param mixed $context Additional context (post_id, etc)
     * @return string Modified voice ID
     */
    public function filter_voice_for_generation($voice_id, $context = null) {
        // Check if paid plan user has a custom voice ID
        if (ttswp_has_paid_plan()) {
            $custom_voice_id = get_option('mementor_tts_custom_voice_id', '');
            if (!empty($custom_voice_id)) {
                error_log('[TTS Admin Filter] Overriding voice ID from ' . $voice_id . ' to custom: ' . $custom_voice_id);
                return $custom_voice_id;
            }
        }

        return $voice_id;
    }
    
    /**
     * Setup voice filters early in the WordPress init process
     */
    public function setup_voice_filters() {
        // Add filters with very high priority to override voice selection
        add_filter('option_mementor_tts_voice', array($this, 'filter_voice_option'), 1);
        add_filter('default_option_mementor_tts_voice', array($this, 'filter_voice_option'), 1);
    }
    
    /**
     * Filter the voice option to return custom voice if available
     * 
     * @param string $voice_id Current voice ID
     * @return string Modified voice ID
     */
    public function filter_voice_option($voice_id) {
        // Only filter if we're in an AJAX context and generating audio
        if (!wp_doing_ajax() || !isset($_POST['action']) || 
            ($_POST['action'] !== 'mementor_tts_generate_audio' && 
             $_POST['action'] !== 'mementor_tts_regenerate_audio')) {
            return $voice_id;
        }
        
        // Check if paid plan user has a custom voice ID
        if (ttswp_has_paid_plan()) {
            // Need to get the raw option to avoid infinite loop
            global $wpdb;
            $custom_voice_id = $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
                    'mementor_tts_custom_voice_id'
                )
            );
            if (!empty($custom_voice_id)) {
                error_log('[TTS Option Filter] Overriding voice option from ' . $voice_id . ' to custom: ' . $custom_voice_id);
                return $custom_voice_id;
            }
        }

        return $voice_id;
    }


    /**
     * Register and enqueue the JavaScript for the admin area.
     *
     * @since    1.0.0
     * @param    string    $hook    The current admin page.
     */
    public function enqueue_scripts($hook) {
        // Ensure $hook is never null to prevent PHP 8+ deprecation warnings
        $hook = $hook ?? '';
        
        // Check if we're on a plugin page or a post-related page with enabled post type
        $is_plugin_page = strpos($hook, 'text-to-speech-tts') !== false;
        $is_post_page = in_array($hook, array('post.php', 'post-new.php', 'edit.php'));
        
        // Determine current screen's post type if possible
        $current_post_type = '';
        if ($is_post_page) {
            if ($hook === 'post.php' || $hook === 'post-new.php') {
                $current_post_type = get_post_type();
                if (!$current_post_type && isset($_GET['post_type'])) {
                    // Verify nonce for post type actions
                    $post_type_nonce_verified = isset($_GET['_wpnonce']) && wp_verify_nonce(sanitize_key($_GET['_wpnonce']), 'mementor_tts_post_type_nonce');
                    
                    // Only use the post_type parameter if nonce is verified or we're just viewing (not submitting data)
                    if ($post_type_nonce_verified || !isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'POST') {
                        $current_post_type = sanitize_key($_GET['post_type']);
                    }
                }
                if (!$current_post_type && $hook === 'post-new.php') {
                    $current_post_type = 'post'; // Default for new posts if type not specified
                }
            } elseif ($hook === 'edit.php') {
                // For edit.php, check the global $typenow or default to 'post'
                global $typenow;
                $current_post_type = !empty($typenow) ? $typenow : '';
                
                // If typenow is empty, check $_GET['post_type'] with nonce verification
                if (empty($current_post_type) && isset($_GET['post_type'])) {
                    // Verify nonce for post type operations in edit.php
                    $edit_nonce_verified = isset($_GET['_wpnonce']) && wp_verify_nonce(sanitize_key($_GET['_wpnonce']), 'mementor_tts_post_type_nonce');
                    
                    // Only use the post_type parameter if nonce is verified or we're just viewing (not submitting data)
                    if ($edit_nonce_verified || !isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'POST') {
                        $current_post_type = sanitize_key($_GET['post_type']);
                    }
                }
                
                // Default to 'post' if we still don't have a post type
                if (empty($current_post_type)) {
                    $current_post_type = 'post';
                }
            }
        }

        $enabled_post_types = get_option('mementor_tts_post_types', array('post'));
        $is_enabled_post_type = ($is_post_page && $current_post_type && in_array($current_post_type, $enabled_post_types));

        // Only load on plugin pages or enabled post type pages
        if (!$is_plugin_page && !$is_enabled_post_type) {
            return;
        }

        // jQuery is automatically included as a dependency in script registration
        
        // Enqueue notifications script first since others depend on it
        wp_enqueue_script(
            $this->plugin_name . '-notifications',
            MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-notifications.js',
            array('jquery'),
            $this->version,
            false
        );
        
        // Enqueue debug utility script early so other scripts can use it
        wp_enqueue_script(
            $this->plugin_name . '-debug-utils',
            MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-debug-utils.js',
            array('jquery'),
            $this->version,
            false
        );
        
        // Set debug mode based on WordPress debug settings
        $debug_mode = defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG;
        wp_localize_script(
            $this->plugin_name . '-debug-utils',
            'mementorTTSDebugConfig',
            array(
                'debugMode' => $debug_mode
            )
        );
        
        // Add inline script to initialize debug mode
        wp_add_inline_script(
            $this->plugin_name . '-debug-utils',
            'if (window.mementorTTSDebug && typeof mementorTTSDebugConfig !== "undefined") {
                window.mementorTTSDebug.setDebugMode(mementorTTSDebugConfig.debugMode);
                window.mementorTTSDebugMode = mementorTTSDebugConfig.debugMode;
            }'
        );
        
        // Enqueue generator page script if we're on the generator page
        if (strpos($hook, 'text-to-speech-tts-generator') !== false) {
            // Enqueue modal utils script first
            wp_enqueue_script(
                $this->plugin_name . '-modal-utils',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-modal-utils.js',
                array('jquery'),
                $this->version,
                true
            );
            
            // Enqueue tooltip script
            wp_enqueue_script(
                $this->plugin_name . '-tooltip',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-tooltip.js',
                array('jquery'),
                $this->version,
                true
            );
            
            // Localize the tooltip script with license page URL
            wp_localize_script(
                $this->plugin_name . '-tooltip',
                'mementorTTS',
                array(
                    'licensePageUrl' => admin_url('admin.php?page=text-to-speech-tts-upgrade')
                )
            );
            
            // Enqueue generator script with tooltip and modal utils scripts as dependencies
            $generator_js_path = MEMENTOR_TTS_PLUGIN_DIR . 'admin/js/mementor-tts-generator.js';
            $generator_js_ver = $this->version . '.' . (file_exists($generator_js_path) ? filemtime($generator_js_path) : time());
            wp_enqueue_script(
                $this->plugin_name . '-generator',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-generator.js',
                array('jquery', $this->plugin_name . '-tooltip', $this->plugin_name . '-modal-utils'),
                $generator_js_ver,
                true
            );
            
            // Localize the generator script with necessary data
            // Get the currently selected voice
            $selected_voice_id = get_option('mementor_tts_voice_id', '');
            
            wp_localize_script(
                $this->plugin_name . '-generator',
                'mementorTtsGenerator',
                array(
                    'ajaxUrl' => admin_url('admin-ajax.php'),
                    'nonce' => wp_create_nonce('mementor_tts_nonce'),
                    'pluginUrl' => MEMENTOR_TTS_PLUGIN_URL,
                    'currentVoiceId' => $selected_voice_id, // Add the current voice ID
                    'emptyTextError' => __('Please enter some text to generate audio.', 'text-to-speech-tts'),
                    'generatingText' => __('Generating...', 'text-to-speech-tts'),
                    'generateButtonText' => __('Generate & Play', 'text-to-speech-tts'),
                    'copiedText' => __('Copied!', 'text-to-speech-tts'),
                    'generalError' => __('An error occurred while generating audio. Please try again.', 'text-to-speech-tts'),
                    'audioBaseUrl' => wp_upload_dir()['baseurl'] . '/text-to-speech-tts/'
                )
            );
        }

        // --- Enqueue styles and scripts for Admin Columns on edit.php ---
        if ($hook === 'edit.php' && $is_enabled_post_type) {
            // Enqueue Admin Columns CSS
             wp_enqueue_style(
                 $this->plugin_name . '-admin-columns',
                 MEMENTOR_TTS_PLUGIN_URL . 'admin/css/mementor-tts-admin-columns.css',
                 array(), // No dependencies for this specific file
                 $this->version
             );
            
            // Enqueue Modal Utilities JS first (needed by admin-columns.js)
            wp_enqueue_script(
                $this->plugin_name . '-modal-utils',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-modal-utils.js',
                array('jquery'), // Depends on jQuery
                $this->version,
                true // Load in footer
            );

            // Enqueue Admin Columns JS
             wp_enqueue_script(
                 $this->plugin_name . '-admin-columns',
                 MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-admin-columns.js',
                 array('jquery', $this->plugin_name . '-modal-utils'), // Depends on jQuery and modal utils
                 $this->version,
                 true // Load in footer
             );
             
             // Enqueue Admin Footer Fix JS
             wp_enqueue_script(
                 $this->plugin_name . '-admin-footer-fix',
                 MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-admin-footer-fix.js',
                 array(), // No JS dependencies, relies on DOMContentLoaded
                 $this->version,
                 true // Load in footer
             );

            // Define translation strings first to avoid dynamic argument warnings
            $translations = array(
                'deleteConfirm' => __('Are you sure you want to delete this audio file? This action cannot be undone.', 'text-to-speech-tts'),
                'regenerateConfirm' => __('Are you sure you want to regenerate this audio file? This will replace the existing audio.', 'text-to-speech-tts'),
                'generatingText' => __('Generating...', 'text-to-speech-tts'),
                'regeneratingText' => __('Regenerating...', 'text-to-speech-tts'),
                'errorGeneric' => __('Error generating audio', 'text-to-speech-tts'),
                'errorAjax' => __('Error generating audio:', 'text-to-speech-tts'),
                'errorGenericDelete' => __('Error deleting audio', 'text-to-speech-tts'),
                'errorAjaxDelete' => __('Error deleting audio:', 'text-to-speech-tts')
            );
            
            // Localize Admin Columns script
            wp_localize_script(
                $this->plugin_name . '-admin-columns',
                'mementorTTSAudio', // Reuse the same object name as the new JS expects it
                array(
                    'ajaxUrl' => admin_url('admin-ajax.php'),
                    'nonce' => wp_create_nonce('mementor_tts_nonce'),
                    'deleteConfirm' => $translations['deleteConfirm'],
                    'regenerateConfirm' => $translations['regenerateConfirm'],
                    'generatingText' => $translations['generatingText'],
                    'regeneratingText' => $translations['regeneratingText'],
                    'errorGeneric' => $translations['errorGeneric'],
                    'errorAjax' => $translations['errorAjax'],
                    'errorGenericDelete' => $translations['errorGenericDelete'],
                    'errorAjaxDelete' => $translations['errorAjaxDelete'],
                    'voice' => $this->get_voice_id_for_generation(), // Get voice ID with custom voice support
                    'isDebug' => (get_option('mementor_tts_debug_mode', '0') === '1'), // Pass debug status
                    'iconPlay'   => MEMENTOR_TTS_PLUGIN_URL . 'admin/images/icons/list-play2.svg',
                    'iconRedo'   => MEMENTOR_TTS_PLUGIN_URL . 'admin/images/icons/list-redo.svg',
                    'iconDelete' => MEMENTOR_TTS_PLUGIN_URL . 'admin/images/icons/list-delete.svg',
                    'iconCreate' => MEMENTOR_TTS_PLUGIN_URL . 'admin/images/icons/list-create.svg',
                )
            );

            // Weglot per-language rows — only when the integration is actually active.
            $weglot_ml_active = function_exists('weglot_get_current_language')
                && get_option('mementor_tts_weglot_enabled', '0') === '1'
                && function_exists('ttswp_has_paid_plan')
                && ttswp_has_paid_plan();
            if ($weglot_ml_active) {
                wp_enqueue_script(
                    $this->plugin_name . '-ml-columns',
                    MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-ml-columns.js',
                    array('jquery', $this->plugin_name . '-admin-columns'),
                    $this->version,
                    true
                );
                wp_localize_script(
                    $this->plugin_name . '-ml-columns',
                    'mementorTTSML',
                    array(
                        'ajaxUrl'              => admin_url('admin-ajax.php'),
                        'nonce'                => wp_create_nonce('mementor_tts_ml_audio'),
                        // Nonce for the shared download proxy (different action).
                        'downloadNonce'        => wp_create_nonce('mementor_tts_nonce'),
                        'playIconUrl'          => MEMENTOR_TTS_PLUGIN_URL . 'admin/images/icons/list-play2.svg',
                        'downloadIconUrl'      => MEMENTOR_TTS_PLUGIN_URL . 'admin/images/icons/list-download.svg',
                        'redoIconUrl'          => MEMENTOR_TTS_PLUGIN_URL . 'admin/images/icons/list-redo.svg',
                        'deleteIconUrl'        => MEMENTOR_TTS_PLUGIN_URL . 'admin/images/icons/list-delete.svg',
                        'createIconUrl'        => MEMENTOR_TTS_PLUGIN_URL . 'admin/images/icons/list-create.svg',
                        'successMessage'       => __('Audio generated!', 'text-to-speech-tts'),
                        'deleteConfirm'        => __('Are you sure you want to delete this audio file?', 'text-to-speech-tts'),
                        'deleteSuccessMessage' => __('Audio deleted!', 'text-to-speech-tts'),
                    )
                );
            }
        }
        // --- End Admin Columns ---

        // Determine if we're on the posts listing page
        $is_posts_list = ($hook === 'edit.php' && $is_enabled_post_type);

        // Enqueue audio generation script only if NOT on the posts listing page
        // This prevents duplicate handlers since admin-columns.js already handles this on the edit.php page
        if (!$is_posts_list) {
            // First enqueue modal utilities
            wp_enqueue_script(
                'mementor-tts-modal-utils',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-modal-utils.js',
                array('jquery'),
                $this->version,
                true
            );
            
            wp_enqueue_script(
                'mementor-tts-audio-generation',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-audio-generation.js',
                array('jquery', $this->plugin_name . '-notifications', 'mementor-tts-modal-utils'),
                $this->version,
                false
            );
        }

        // Enqueue audio player script if on the player or settings page
        if (strpos($hook, 'text-to-speech-tts-player') !== false || 
            strpos($hook, 'text-to-speech-tts-settings') !== false) {
            wp_enqueue_script(
                'mementor-tts-audio-player',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/js/audio-player.js',
                array('jquery'),
                $this->version,
                false
            );
            
            // Also load the settings validation script specifically for the settings page
            if (strpos($hook, 'text-to-speech-tts-settings') !== false) {
                wp_enqueue_script(
                    'mementor-tts-settings-validation',
                    MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-settings-validation.js',
                    array('jquery'),
                    $this->version,
                    true
                );
                
                // Localize the settings validation script
                wp_localize_script(
                    'mementor-tts-settings-validation',
                    'mementorTTSValidation',
                    array(
                        'ajaxUrl' => admin_url('admin-ajax.php'),
                        'nonce' => wp_create_nonce('mementor_tts_nonce'),
                        'isDebug' => (get_option('mementor_tts_debug_mode', '0') === '1'),
                        'validateButtonText' => __('Save & Validate API Key', 'text-to-speech-tts'),
                        'validatingText' => __('Validating...', 'text-to-speech-tts')
                    )
                );
                
                // Enqueue Dropbox functionality script
                wp_enqueue_script(
                    'mementor-tts-dropbox',
                    MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-dropbox.js',
                    array('jquery'),
                    $this->version,
                    true
                );
            }
        }

        // Plugin-specific scripts for all plugin pages
        if ($is_plugin_page) {
            // Enqueue the main admin script
            wp_enqueue_script(
                $this->plugin_name . '-admin',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-admin.js',
                array('jquery', $this->plugin_name . '-notifications'),
                $this->version,
                false
            );
            
            // Load page-specific scripts
            if (strpos($hook, 'text-to-speech-tts-content') !== false) {
                // Enqueue content page specific JS
                wp_enqueue_script(
                    $this->plugin_name . '-content-page',
                    MEMENTOR_TTS_PLUGIN_URL . 'admin/js/mementor-tts-content-page.js',
                    array('jquery'),
                    $this->version,
                    true
                );
            }

            // Localize the admin script with necessary data
            wp_localize_script(
                $this->plugin_name . '-admin',
                'mementorTTS',
                array(
                    'nonce' => wp_create_nonce('mementor_tts_nonce'),
                    'ajaxurl' => admin_url('admin-ajax.php'),
                    'pluginUrl' => MEMENTOR_TTS_PLUGIN_URL,
                    'currentVoiceId' => $this->get_voice_id_for_generation(), // Get voice ID with custom voice support
                    'strings' => array(
                        'validating' => __('Validating...', 'text-to-speech-tts'),
                        'connected' => __('Connected', 'text-to-speech-tts'),
                        'notConnected' => __('Not Connected', 'text-to-speech-tts'),
                        'confirmReset' => __('Are you sure you want to reset the API settings?', 'text-to-speech-tts'),
                        'generating' => __('Generating audio...', 'text-to-speech-tts'),
                        'error' => __('Error:', 'text-to-speech-tts'),
                        'voicesLoaded' => __('Voices loaded successfully', 'text-to-speech-tts'),
                        'loadingVoices' => __('Loading voices...', 'text-to-speech-tts'),
                        'failedToLoadVoices' => __('Failed to load voices. Please try again.', 'text-to-speech-tts'),
                        'errorLoadingVoices' => __('Error loading voices', 'text-to-speech-tts'),
                        'noVoicesFound' => __('No voices found matching your filters.', 'text-to-speech-tts'),
                        'savingVoice' => __('Saving voice selection...', 'text-to-speech-tts'),
                        'failedToSaveVoice' => __('Failed to save voice selection.', 'text-to-speech-tts'),
                        'errorSavingVoice' => __('Error saving voice selection', 'text-to-speech-tts'),
                        'resetConfirm' => __('Are you sure you want to reset all player settings to default values?', 'text-to-speech-tts'),
                        'resetSuccess' => __('Player settings reset successfully.', 'text-to-speech-tts'),
                        'resetError' => __('Failed to reset player settings. Please try again.', 'text-to-speech-tts')
                    )
                )
            );

            // Localize the admin script with admin-specific data
            wp_localize_script(
                $this->plugin_name . '-admin',
                'mementorTTSAdmin',
                array(
                    'ajaxUrl' => admin_url('admin-ajax.php'),
                    'nonce' => wp_create_nonce('mementor_tts_nonce'),
                    'loadingVoices' => __('Loading voices...', 'text-to-speech-tts'),
                    'errorLoadingVoices' => __('Error loading voices. Please try again.', 'text-to-speech-tts')
                )
            );
        }

        // Account & Upgrade page scripts (both need Stripe checkout)
        if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'text-to-speech-tts-account', 'text-to-speech-tts-upgrade' ), true ) ) {
            // Stripe.js
            wp_enqueue_script( 'stripe-js', 'https://js.stripe.com/v3/', array(), null, true );

            // Account page JS
            wp_enqueue_script(
                'tts-account',
                MEMENTOR_TTS_PLUGIN_URL . 'admin/js/tts-account.js',
                array( 'jquery', 'stripe-js' ),
                $this->version,
                true
            );

            // Pass data to JS
            $billing_data = $this->get_billing_data();
            wp_localize_script( 'tts-account', 'ttsAccount', array(
                'ajaxurl'    => admin_url( 'admin-ajax.php' ),
                'nonce'      => wp_create_nonce( 'mementor_tts_nonce' ),
                'stripeKey'  => $billing_data['stripe_key'] ?? '',
                'accountUrl' => admin_url( 'admin.php?page=text-to-speech-tts-account' ),
            ) );
        }

        // Add JavaScript for Support Forum link
        add_action('admin_footer', function() {
            ?>
            <script>
            jQuery(document).ready(function($) {
                // Find the Support Forum link and add target="_blank"
                $('#adminmenu a').each(function() {
                    if ($(this).attr('href') === 'https://wordpress.org/support/plugin/text-to-speech-tts/') {
                        $(this).attr('target', '_blank');
                    }
                });
            });
            </script>
            <?php
        });
    }

    /**
     * Display admin notices
     */
    public function display_admin_notices() {
        // Admin notices are now handled by the JavaScript file
        // This method remains for backward compatibility but doesn't output anything
    }
    
    /**
     * Remove third-party admin notices on our plugin pages
     */
    public function remove_third_party_notices() {
        // Check if we're on a TTS plugin page
        if (!isset($_GET['page']) || strpos($_GET['page'], 'text-to-speech-tts') === false) {
            return;
        }
        
        // Remove all actions from various notice hooks
        global $wp_filter;
        
        // List of hooks to clean
        $notice_hooks = array(
            'admin_notices',
            'all_admin_notices', 
            'network_admin_notices',
            'user_admin_notices'
        );
        
        foreach ($notice_hooks as $hook) {
            if (isset($wp_filter[$hook])) {
                // Get all priorities for this hook
                $hook_ref = &$wp_filter[$hook];
                
                if (is_object($hook_ref) && isset($hook_ref->callbacks)) {
                    // WordPress 4.7+ uses WP_Hook object
                    foreach ($hook_ref->callbacks as $priority => &$callbacks) {
                        foreach ($callbacks as $id => $callback) {
                            // Check if this is NOT one of our callbacks
                            $is_our_callback = false;
                            
                            if (is_array($callback['function']) && isset($callback['function'][0])) {
                                if (is_object($callback['function'][0])) {
                                    $class_name = get_class($callback['function'][0]);
                                    // Check if it's our class or the TTS plugin class
                                    if ($class_name === get_class($this) || 
                                        strpos($class_name, 'Mementor_TTS') !== false) {
                                        $is_our_callback = true;
                                    }
                                }
                            }
                            
                            // Remove if it's not our callback
                            if (!$is_our_callback) {
                                unset($callbacks[$id]);
                            }
                        }
                        
                        // Clean up empty priority arrays
                        if (empty($callbacks)) {
                            unset($hook_ref->callbacks[$priority]);
                        }
                    }
                } else {
                    // Older WordPress versions - clear the entire hook except our callbacks
                    $wp_filter[$hook] = array();
                }
            }
        }
        
        // Also prevent notices from being added later
        add_action('admin_notices', '__return_false', 999);
        add_action('all_admin_notices', '__return_false', 999);
    }

    /**
     * Add bulk action for generating audio
     *
     * @since    1.0.0
     */
    public function add_bulk_generate_audio_action() {
        // Get enabled post types from options
        $enabled_post_types = get_option('mementor_tts_post_types', array('post'));
        
        // If no post types are enabled, return early
        if (empty($enabled_post_types)) {
            return;
        }
        
        // Add bulk action to each enabled post type
        foreach ($enabled_post_types as $post_type) {
            // Add bulk action dropdown option
            add_filter('bulk_actions-edit-' . $post_type, array($this, 'register_bulk_generate_audio_action'));
            
            // Handle the bulk action
            add_filter('handle_bulk_actions-edit-' . $post_type, array($this, 'handle_bulk_generate_audio_action'), 10, 3);
        }
        
        // Add admin notices for bulk action results
        add_action('admin_notices', array($this, 'bulk_generate_audio_admin_notice'));
        
        // Add AJAX handler for bulk audio generation
        add_action('wp_ajax_mementor_tts_bulk_generate_audio', array($this, 'bulk_generate_audio_ajax'));
    }
    
    /**
     * Register bulk generate audio action in dropdown
     * 
     * @param array $bulk_actions Existing bulk actions
     * @return array Modified bulk actions
     */
    public function register_bulk_generate_audio_action($bulk_actions) {
        $bulk_actions['mementor_tts_generate_audio'] = __('Generate Audio', 'text-to-speech-tts');
        return $bulk_actions;
    }
    
    /**
     * Handle bulk generate audio action
     * 
     * @param string $redirect_to The redirect URL
     * @param string $doaction The action being taken
     * @param array $post_ids The post IDs to act on
     * @return string The redirect URL
     */
    public function handle_bulk_generate_audio_action($redirect_to, $doaction, $post_ids) {
        if ($doaction !== 'mementor_tts_generate_audio') {
            return $redirect_to;
        }
        
        // Check paid plan
        if (!ttswp_has_paid_plan()) {
            return add_query_arg('bulk_audio_error', 'pro_required', $redirect_to);
        }
        
        // No limit needed since we process sequentially
        
        // Store post IDs in transient for AJAX processing
        $bulk_id = 'bulk_' . wp_generate_password(12, false);
        set_transient('mementor_tts_bulk_' . $bulk_id, $post_ids, HOUR_IN_SECONDS);
        
        // Redirect to bulk processing page or show inline processing
        return add_query_arg(array(
            'bulk_audio_processing' => $bulk_id,
            'post_count' => count($post_ids)
        ), $redirect_to);
    }
    
    /**
     * Display admin notices for bulk audio generation
     */
    public function bulk_generate_audio_admin_notice() {
        // Error notices
        if (isset($_GET['bulk_audio_error'])) {
            $error = sanitize_key($_GET['bulk_audio_error']);
            
            if ($error === 'pro_required') {
                ?>
                <div class="notice notice-error is-dismissible">
                    <p>
                        <strong><?php esc_html_e('Bulk Audio Generation is a PRO feature', 'text-to-speech-tts'); ?></strong><br>
                        <?php esc_html_e('This feature is only available for active PRO licenses.', 'text-to-speech-tts'); ?>
                        <a href="<?php echo esc_url(admin_url('admin.php?page=text-to-speech-tts-upgrade')); ?>" class="button button-primary" style="margin-left: 10px;">
                            <?php esc_html_e('Upgrade to PRO', 'text-to-speech-tts'); ?>
                        </a>
                    </p>
                </div>
                <?php
            }
        }
        
        // Processing notice
        if (isset($_GET['bulk_audio_processing']) && isset($_GET['post_count'])) {
            $bulk_id = sanitize_key($_GET['bulk_audio_processing']);
            $post_count = absint($_GET['post_count']);
            
            // Check if there are actually posts to process
            $post_ids = get_transient('mementor_tts_bulk_' . $bulk_id);
            if (empty($post_ids)) {
                // No posts to process, redirect to clean URL
                wp_safe_redirect(remove_query_arg(array('bulk_audio_processing', 'post_count')));
                exit;
            }
            ?>
            <div class="notice notice-info" id="mementor-tts-bulk-notice">
                <h3><?php esc_html_e('Cooking Up Some Fresh Audio...', 'text-to-speech-tts'); ?></h3>
                <p><?php esc_html_e('Grab a coffee — your audio will be ready in seconds.', 'text-to-speech-tts'); ?></p>
                <div class="mementor-tts-bulk-progress">
                    <div class="progress-bar">
                        <div class="progress-fill" style="width: 0%"></div>
                    </div>
                    <p class="progress-text">
                        <span class="current">0</span> / <span class="total"><?php echo esc_html($post_count); ?></span>
                    </p>
                </div>
                <div class="mementor-tts-bulk-results" style="display: none;">
                    <h4><?php esc_html_e('Results:', 'text-to-speech-tts'); ?></h4>
                    <ul class="results-list"></ul>
                </div>
            </div>
            <script>
            jQuery(document).ready(function($) {
                var bulkId = '<?php echo esc_js($bulk_id); ?>';
                var totalPosts = <?php echo absint($post_count); ?>;
                var processed = 0;
                
                function processNextPost() {
                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'mementor_tts_bulk_generate_audio',
                            bulk_id: bulkId,
                            nonce: '<?php echo esc_js(wp_create_nonce('mementor_tts_bulk_nonce')); ?>'
                        },
                        success: function(response) {
                            if (response.success) {
                                processed++;
                                updateProgress(processed, totalPosts);
                                
                                // Add result to list
                                var resultClass = response.data.status === 'success' ? 'success' : 'error';
                                var resultIcon = response.data.status === 'success' ? '✓' : '✗';
                                var resultText = response.data.message;
                                
                                $('.results-list').append(
                                    '<li class="' + resultClass + '">' + 
                                    '<span class="icon">' + resultIcon + '</span> ' + 
                                    resultText + 
                                    '</li>'
                                );
                                
                                if (response.data.has_more) {
                                    // Process next post
                                    setTimeout(processNextPost, 1000);
                                } else {
                                    // All done
                                    $('.mementor-tts-bulk-results').show();
                                    $('.progress-text').html('<strong>Complete!</strong>');
                                    
                                    // Add refresh button
                                    $('#mementor-tts-bulk-notice').append(
                                        '<div style="margin-top: 20px; margin-bottom: 20px;">' +
                                        '<p><?php esc_html_e('Mission Accomplished — Audio Activated ✅', 'text-to-speech-tts'); ?></p>' +
                                        '<button class="button button-primary" onclick="window.location.href=window.location.pathname + \'?post_type=<?php echo esc_js(get_current_screen()->post_type); ?>\';"><?php esc_html_e('Refresh Page', 'text-to-speech-tts'); ?></button>' +
                                        '</div>'
                                    );
                                }
                            } else {
                                // Error occurred
                                $('.results-list').append(
                                    '<li class="error">' + 
                                    '<span class="icon">✗</span> ' + 
                                    response.data.message + 
                                    '</li>'
                                );
                                $('.mementor-tts-bulk-results').show();
                            }
                        },
                        error: function() {
                            $('.results-list').append(
                                '<li class="error">' + 
                                '<span class="icon">✗</span> ' + 
                                '<?php esc_html_e('An error occurred during processing.', 'text-to-speech-tts'); ?>' + 
                                '</li>'
                            );
                            $('.mementor-tts-bulk-results').show();
                        }
                    });
                }
                
                function updateProgress(current, total) {
                    var percentage = (current / total) * 100;
                    $('.progress-fill').css('width', percentage + '%');
                    $('.progress-text .current').text(current);
                }
                
                // Start processing
                processNextPost();
            });
            </script>
            <style>
            .mementor-tts-bulk-progress {
                margin: 20px 0;
            }
            .progress-bar {
                background: #f0f0f0;
                height: 20px;
                border-radius: 10px;
                overflow: hidden;
                margin-bottom: 10px;
            }
            .progress-fill {
                background: #2271b1;
                height: 100%;
                transition: width 0.3s ease;
            }
            .progress-text {
                font-size: 14px;
                color: #666;
            }
            .mementor-tts-bulk-results .results-list {
                margin: 10px 0;
                padding-left: 20px;
            }
            .mementor-tts-bulk-results .results-list li {
                margin: 5px 0;
                list-style: none;
            }
            .mementor-tts-bulk-results .results-list li.success {
                color: #008a20;
            }
            .mementor-tts-bulk-results .results-list li.error {
                color: #d63638;
            }
            .mementor-tts-bulk-results .icon {
                font-weight: bold;
                margin-right: 5px;
            }
            #mementor-tts-bulk-notice .button {
                margin-right: 10px;
            }
            </style>
            <?php
        }
    }
    
    /**
     * AJAX handler for bulk audio generation
     */
    public function bulk_generate_audio_ajax() {
        // Check nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'mementor_tts_bulk_nonce')) {
            wp_send_json_error(array(
                'message' => __('Security check failed.', 'text-to-speech-tts')
            ));
            return;
        }
        
        // Check user capabilities
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(array(
                'message' => __('You do not have permission to perform this action.', 'text-to-speech-tts')
            ));
            return;
        }
        
        // Get bulk ID
        $bulk_id = isset($_POST['bulk_id']) ? sanitize_key($_POST['bulk_id']) : '';
        if (empty($bulk_id)) {
            wp_send_json_error(array(
                'message' => __('Invalid bulk operation ID.', 'text-to-speech-tts')
            ));
            return;
        }
        
        // Get post IDs from transient
        $post_ids = get_transient('mementor_tts_bulk_' . $bulk_id);
        if ($post_ids === false || !is_array($post_ids) || empty($post_ids)) {
            wp_send_json_error(array(
                'message' => __('No posts found for processing.', 'text-to-speech-tts')
            ));
            return;
        }
        
        // Process the first post
        $post_id = array_shift($post_ids);
        
        // Get post title for response
        $post = get_post($post_id);
        if (!$post) {
            // Skip invalid post
            if (!empty($post_ids)) {
                set_transient('mementor_tts_bulk_' . $bulk_id, $post_ids, HOUR_IN_SECONDS);
                wp_send_json_success(array(
                    'status' => 'error',
                    /* translators: %d: Post ID that could not be loaded during bulk generation. */
                    'message' => sprintf(__('Post #%d not found, skipping...', 'text-to-speech-tts'), $post_id),
                    'has_more' => true
                ));
            } else {
                delete_transient('mementor_tts_bulk_' . $bulk_id);
                wp_send_json_success(array(
                    'status' => 'error',
                    /* translators: %d: Post ID that could not be loaded. */
                    'message' => sprintf(__('Post #%d not found.', 'text-to-speech-tts'), $post_id),
                    'has_more' => false
                ));
            }
            return;
        }
        
        // Include required classes
        require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-ajax.php';
        
        // Get AJAX instance
        $ajax_handler = Mementor_TTS_Ajax::get_instance();
        
        // Simulate POST data for the generate_audio method
        $_POST['post_id'] = $post_id;
        $_POST['nonce'] = wp_create_nonce('mementor_tts_nonce');
        
        // Set up a custom filter to capture the response
        $audio_generated = false;
        $generation_error = '';
        
        add_filter('wp_doing_ajax', '__return_true');
        
        // Override wp_send_json_success/error temporarily
        add_filter('wp_die_ajax_handler', function() use (&$audio_generated, &$generation_error) {
            return function($message, $title, $args) use (&$audio_generated, &$generation_error) {
                if (is_array($message) || is_object($message)) {
                    $data = json_decode(wp_json_encode($message), true);
                    if (isset($data['success']) && $data['success']) {
                        $audio_generated = true;
                    } else {
                        $generation_error = isset($data['data']['message']) ? $data['data']['message'] : __('Unknown error', 'text-to-speech-tts');
                    }
                } else {
                    $generation_error = $message;
                }
                // Don't actually die
            };
        }, 999);
        
        // Try direct generation using the processor
        require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-processor.php';
        require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-speech-builder.php';
        
        $processor = Mementor_TTS_Processor::get_instance();
        
        // Get post content
        $content = '';
        
        // Include title if enabled
        if (get_option('mementor_tts_include_title', 1)) {
            $content .= '<h1>' . get_the_title($post_id) . '</h1>';
        }
        
        // Include author if enabled
        if (get_option('mementor_tts_include_author', 0)) {
            $author_id = get_post_field('post_author', $post_id);
            $author_name = get_the_author_meta('display_name', $author_id);
            /* translators: %s: Author display name. Prepended to spoken content, e.g. "By Jane Doe". */
            $content .= '<p>' . sprintf(__('By %s', 'text-to-speech-tts'), $author_name) . '</p>';
        }

        // Include date if enabled
        if (get_option('mementor_tts_include_date', 0)) {
            $post_date = get_the_date(get_option('date_format'), $post_id);
            /* translators: %s: Publication date formatted per the site's date format. */
            $content .= '<p>' . sprintf(__('Published on %s', 'text-to-speech-tts'), $post_date) . '</p>';
        }
        
        // Include excerpt if enabled
        if (get_option('mementor_tts_include_excerpt', 0)) {
            $excerpt = get_the_excerpt($post_id);
            if (!empty($excerpt)) {
                $content .= '<p>' . $excerpt . '</p>';
            }
        }
        
        // Get the main post content
        $post = get_post($post_id);
        $raw_content = $post->post_content;

        // Render via helper so Fusion Builder / Avada emit the full post body,
        // not just the teaser above <!--more-->.
        $content .= ttswp_render_content_full($raw_content, $post_id);

        $ttswp_adm_rendered_len = strlen( $content );
        $ttswp_adm_raw_len      = strlen( $raw_content );
        ttswp_send_remote_debug_event(
            'content_extracted',
            sprintf( 'admin content-assembly length: %d (raw %d)', $ttswp_adm_rendered_len, $ttswp_adm_raw_len ),
            array(
                'path'         => 'admin.build_speech_content',
                'raw_post_len' => $ttswp_adm_raw_len,
                'rendered_len' => $ttswp_adm_rendered_len,
            ),
            $ttswp_adm_rendered_len < 500 && $ttswp_adm_raw_len > 1500 ? 'warning' : 'debug',
            $post_id
        );

        // Add custom intro/outro
        $text_before = get_option('mementor_tts_text_before', '');
        $text_after = get_option('mementor_tts_text_after', '');
        
        if (!empty($text_before)) {
            $content = '<p>' . $text_before . '</p>' . $content;
        }
        
        if (!empty($text_after)) {
            $content .= '<p>' . $text_after . '</p>';
        }
        
        // Process the content through filters to clean it up
        // This simulates what would happen when content is displayed
        $text = wp_strip_all_tags($content);
        $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, get_bloginfo('charset'));
        $text = trim($text);
        
        // Apply word replacements if paid plan is active
        if (ttswp_has_paid_plan()) {
            $replacements = get_option('mementor_tts_word_replacements', array());
            if (!empty($replacements) && is_array($replacements)) {
                foreach ($replacements as $replacement) {
                    if (isset($replacement['original']) && isset($replacement['replacement'])) {
                        $pattern = '/\b' . preg_quote($replacement['original'], '/') . '\b/i';
                        $text = preg_replace($pattern, $replacement['replacement'], $text);
                    }
                }
            }
        }
        
        if (empty($text)) {
            $generation_error = __('No content to convert to speech.', 'text-to-speech-tts');
        } else {
            // Get voice ID (check for custom voice ID if paid plan is active)
            $voice_id = get_option('mementor_tts_voice_id', '');

            if (ttswp_has_paid_plan()) {
                $custom_voice_id = get_option('mementor_tts_custom_voice_id', '');
                if (!empty($custom_voice_id)) {
                    $voice_id = $custom_voice_id;
                }
            }
            
            // Generate audio
            $result = $processor->generate_audio($text, $post_id, $voice_id);
            
            if ($result && is_string($result)) {
                $audio_generated = true;
            } else if (is_array($result) && isset($result['success']) && $result['success'] === false) {
                $generation_error = isset($result['message']) ? $result['message'] : __('Failed to generate audio', 'text-to-speech-tts');
            } else {
                $generation_error = __('Failed to generate audio', 'text-to-speech-tts');
            }
        }
        
        // Determine success/error and prepare message
        if ($audio_generated) {
            $status = 'success';
            /* translators: %s: Post title. Shown as a line in the bulk-generation progress list. */
            $message = sprintf(__('%s - Audio generated successfully', 'text-to-speech-tts'), esc_html($post->post_title));
        } else {
            $status = 'error';
            /* translators: 1: Post title. 2: Error message returned by the generator. */
            $message = sprintf(__('%1$s - %2$s', 'text-to-speech-tts'), esc_html($post->post_title), $generation_error);
        }
        
        // Update transient with remaining posts
        if (!empty($post_ids)) {
            set_transient('mementor_tts_bulk_' . $bulk_id, $post_ids, HOUR_IN_SECONDS);
            $has_more = true;
        } else {
            delete_transient('mementor_tts_bulk_' . $bulk_id);
            $has_more = false;
        }
        
        wp_send_json_success(array(
            'status' => $status,
            'message' => $message,
            'has_more' => $has_more
        ));
    }

    /**
     * Handle AJAX request for generating audio
     */
    public function generate_audio_ajax() {
        try {
            // Check nonce for security
            if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'mementor_tts_nonce')) {
                wp_send_json_error(array('message' => __('Security check failed.', 'text-to-speech-tts')));
                return;
            }

            // Check if post ID is provided
            if (!isset($_POST['post_id']) || empty($_POST['post_id'])) {
                wp_send_json_error(array('message' => __('No post ID provided.', 'text-to-speech-tts')));
                return;
            }

            // Sanitize and validate post_id
            $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
            if (!$post_id) {
                wp_send_json_error(array('message' => __('Invalid post ID.', 'text-to-speech-tts')));
                return;
            }

            // Check if post exists
            $post = get_post($post_id);
            if (!$post) {
                wp_send_json_error(array('message' => __('Post not found.', 'text-to-speech-tts')));
                return;
            }
            
            // Check user permissions for the specific post
            if (!current_user_can('edit_post', $post_id)) {
                wp_send_json_error(array('message' => __('You do not have permission to edit this post.', 'text-to-speech-tts')));
                return;
            }

            // Add flag to indicate that we're processing this post
            update_post_meta($post_id, '_mementor_tts_processing', true);

            // Prepare text for TTS using our helper function
            $text = $this->prepare_text_with_title_and_excerpt($post);

            // Check if text is empty
            if (empty($text)) {
                update_post_meta($post_id, '_mementor_tts_processing', false);
                wp_send_json_error(array('message' => __('No content to convert to speech.', 'text-to-speech-tts')));
                return;
            }

            // Include required files
            require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-encryption.php';
            require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-elevenlabs-api.php';
            require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-processor.php';

            // Get TTS processor
            $processor = Mementor_TTS_Processor::get_instance();

            // Check if a specific voice ID was provided
            $voice_id = '';
            if (isset($_POST['voice_id']) && !empty($_POST['voice_id'])) {
                $voice_id = sanitize_text_field(wp_unslash($_POST['voice_id']));
            }

            // Generate audio using the processor
            $result = $processor->generate_audio($text, $post_id, $voice_id);

            // Check if we have a valid response
            if ($result && is_string($result)) {
                // Remove processing flag
                delete_post_meta($post_id, '_mementor_tts_processing');

                // Clear any output buffer
                ob_clean();

                // Send success response
                wp_send_json_success(array(
                    'message' => __('Audio created successfully.', 'text-to-speech-tts'),
                    'file_url' => $result
                ));
            } else {
                // Remove processing flag
                delete_post_meta($post_id, '_mementor_tts_processing');

                // Clear any output buffer
                ob_clean();

                // Handle different error types
                if (is_array($result) && isset($result['error'])) {
                    $error_type = $result['error'];
                    
                    // Check for credits exhausted error - send complete data for modal
                    if ($error_type === 'credits_exhausted') {
                        wp_send_json_error(array(
                            'error' => 'credits_exhausted',
                            'message' => isset($result['message']) ? $result['message'] : __('You have exhausted your free credits.', 'text-to-speech-tts'),
                            'credits_info' => isset($result['credits_info']) ? $result['credits_info'] : array()
                        ));
                        return;
                    }
                    // Check for quota exceeded error
                    elseif (strpos($error_type, 'quota_exceeded') !== false) {
                        // Don't modify or extract from the error message
                        // Keep the full error message intact, including details about credits remaining
                        $error_message = str_replace('quota_exceeded: ', '', $error_type);
                        wp_send_json_error(array('message' => $error_message));
                        return;
                    }
                    // Check for no API key error
                    elseif (strpos($error_type, 'No API key provided') !== false) {
                        $error_message = __('No API key? Upgrade to PRO and get one instantly, plus 30,000 free audio credits per month, and enjoy other premium features! Alternatively, you can get one here.', 'text-to-speech-tts');
                        wp_send_json_error(array('message' => $error_message));
                        return;
                    }
                    else {
                        // Generic error from result
                        wp_send_json_error(array('message' => $error_type));
                        return;
                    }
                } else {
                    // Fallback error message
                    wp_send_json_error(array('message' => __('Failed to generate audio.', 'text-to-speech-tts')));
                }
            }
        } catch (Exception $e) {
            // Remove processing flag if post_id is set
            if (isset($post_id)) {
                delete_post_meta($post_id, '_mementor_tts_processing');
            }

            // Clear any output buffer
            ob_clean();

            // Send error response
            wp_send_json_error(array('message' => $e->getMessage()));
        }

        // End execution to prevent any additional output
        wp_die();
    }

    /**
     * Get ElevenLabs API usage statistics
     * 
     * @since    1.0.0
     * @return   array    The usage statistics or error message
     */
    public function get_elevenlabs_usage_stats() {
        try {
            // Include required files
            require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-encryption.php';
            require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-elevenlabs-api.php';
            
            // Get API instance directly
            $api = Mementor_TTS_ElevenLabs_API::get_instance();
            
            // If no API instance, return error
            if (!$api) {
                return array(
                    'success' => false,
                    'message' => 'Failed to initialize API'
                );
            }
            
            // For user stats, we should NOT use the PRO API key
            // We want to show the user's own ElevenLabs account information
            // Get the user's API key and set it directly
            $user_api_key = get_option('mementor_tts_api_key', '');
            
            // If no user API key, return error
            if (empty($user_api_key)) {
                return array(
                    'success' => false,
                    'message' => 'No user API key configured',
                    'error' => true
                );
            }
            
            // Decrypt and set the user's API key
            $encryption = new Mementor_TTS_Encryption();
            $decrypted_key = $encryption->decrypt($user_api_key);
            
            if (!$decrypted_key) {
                return array(
                    'success' => false,
                    'message' => 'Failed to decrypt API key',
                    'error' => true
                );
            }
            
            // Log the key being used (just first/last few chars for security)
            error_log('TTS Stats: Using user API key: ' . substr($decrypted_key, 0, 4) . '...' . substr($decrypted_key, -4));
            
            // Temporarily set the user's API key for this request
            $original_key = $api->get_api_key();
            $api->set_api_key($decrypted_key);

            // Check if we have cached stats (cache for 5 minutes to avoid rate limiting)
            $cache_key = 'mementor_tts_user_stats_' . md5($user_api_key);
            $cached_stats = get_transient($cache_key);

            if ($cached_stats !== false) {
                error_log('TTS Stats: Using cached stats (5 min cache)');
                return $cached_stats;
            }

            // Make a direct API request bypassing the PRO filter
            // We need to call the API directly with the user's key
            $url = 'https://api.elevenlabs.io/v1/user/subscription';

            error_log('TTS Stats: Making direct API call to: ' . $url);
            
            $response = wp_remote_get($url, array(
                'timeout' => 15,
                'headers' => array(
                    'xi-api-key' => $decrypted_key,
                    'Accept' => 'application/json'
                )
            ));
            
            if (is_wp_error($response)) {
                return array(
                    'success' => false,
                    'error' => true,
                    'message' => $response->get_error_message()
                );
            }
            
            $response_code = wp_remote_retrieve_response_code($response);
            $response_body = wp_remote_retrieve_body($response);
            
            // Format response to match what the API class returns
            $response = array(
                'status' => $response_code,
                'data' => $response_body
            );
            
            // Get response code
            $response_code = isset($response['status']) ? $response['status'] : 0;
            
            // Log the response status
            error_log('TTS Stats: API response code: ' . $response_code);
            
            // Check if response is successful
            if ($response_code !== 200) {
                // Log the error details
                if (isset($response['data'])) {
                    error_log('TTS Stats: API error response: ' . $response['data']);
                }
                return array(
                    'success' => false,
                    'error' => true,
                    'message' => 'API request failed with code ' . $response_code,
                    'code' => $response_code
                );
            }
            
            // Get response body
            $body = isset($response['data']) ? $response['data'] : '';
            
            error_log('TTS Stats: Raw response body: ' . substr($body, 0, 200) . '...');
            
            $data = json_decode($body, true);
            
            // Check if data is valid
            if (empty($data)) {
                // Restore original API key if we changed it
                if (isset($original_key)) {
                    $api->set_api_key($original_key);
                }
                return array(
                    'success' => false,
                    'error' => true,
                    'message' => 'Invalid response from API'
                );
            }
            
            // Initialize the result array with defaults
            $result = array(
                'success' => true,
                'tier' => isset($data['tier']) ? $data['tier'] : 'free',
                'character_count' => 0,
                'character_limit' => 0,
                'next_character_count_reset_unix' => 0
            );
            
            // Check which endpoint we received data from and extract relevant data
            if (isset($data['keys']) && is_array($data['keys']) && count($data['keys']) > 0) {
                // We're using the key-info endpoint (preferred)
                $key_info = $data['keys'][0]; // Get the first key (current key)
                
                $result['character_count'] = isset($key_info['characters_generated']) ? $key_info['characters_generated'] : 0;
                $result['character_limit'] = isset($key_info['character_limit']) ? $key_info['character_limit'] : 0;
                
                // Get next reset date if available
                if (isset($key_info['next_character_reset_unix'])) {
                    $result['next_character_count_reset_unix'] = $key_info['next_character_reset_unix'];
                } elseif (isset($data['next_character_count_reset_unix'])) {
                    $result['next_character_count_reset_unix'] = $data['next_character_count_reset_unix'];
                }
            } else {
                // We're using the fallback general subscription endpoint
                $result['character_count'] = isset($data['character_count']) ? $data['character_count'] : 0;
                $result['character_limit'] = isset($data['character_limit']) ? $data['character_limit'] : 0;
                
                // Get next reset date if available
                if (isset($data['next_character_count_reset_unix'])) {
                    $result['next_character_count_reset_unix'] = $data['next_character_count_reset_unix'];
                }
            }
            
            // Restore original API key if we changed it
            if (isset($original_key)) {
                $api->set_api_key($original_key);
            }

            // Cache the stats for 5 minutes to avoid rate limiting
            set_transient($cache_key, $result, 5 * MINUTE_IN_SECONDS);
            error_log('TTS Stats: Successfully cached stats for 5 minutes');

            return $result;
        } catch (Exception $e) {
            // Restore original API key if we changed it
            if (isset($original_key)) {
                $api->set_api_key($original_key);
            }
            
            // Log the exception and return error
            if (function_exists('error_log')) {
                error_log('ElevenLabs stats exception: ' . $e->getMessage());
            }
            
            return array(
                'success' => false,
                'error' => true,
                'message' => $e->getMessage()
            );
        }
    }

    /**
     * AJAX handler for refreshing ElevenLabs usage statistics
     * 
     * @since    1.0.0
     * @return   void
     */
    public function refresh_stats_ajax() {
        // Check nonce for security
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'mementor_tts_nonce')) {
            wp_send_json_error(array(
                'message' => 'Security check failed'
            ));
            return;
        }
        
        // Check if user has permission
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array(
                'message' => 'You do not have permission to perform this action'
            ));
            return;
        }
        
        // Get fresh statistics from the API
        $stats = $this->get_elevenlabs_usage_stats();
        
        // Check for errors
        if (isset($stats['error']) && $stats['error']) {
            $error_message = $stats['message'];
            $error_code = isset($stats['code']) ? $stats['code'] : 0;
            
            // Provide more specific error messages for common error codes
            if ($error_code === 401) {
                $error_message = 'Authentication Error or Missing Permissions (401): Your API key is either missing the user_read permission, invalid, or expired. Please check your API key in the Settings tab and the ElevenLabs API section.';
            }
            
            wp_send_json_error(array(
                'message' => $error_message,
                'code' => $error_code
            ));
            return;
        }
        
        // Return the statistics
        wp_send_json_success(array(
            'stats' => $stats
        ));
    }

    /**
     * Check if API key is configured and redirect if needed
     */
    private function check_api_key_access() {
        // Check if we're on the settings page
        $screen = get_current_screen();
        if ($screen->id === 'text-to-speech_page_text-to-speech-tts-settings') {
            return true;
        }

        // Get API key
        $api_key = get_option('mementor_tts_api_key', '');
        if (empty($api_key) && strpos($screen->id, 'text-to-speech-tts') !== false) {
            // Redirect to settings page if no API key is set
            add_action('admin_notices', function() {
                echo '<div class="notice notice-error is-dismissible"><p>' . esc_html__('Please add your API key in the settings page to use Text to Speech.', 'text-to-speech-tts') . '</p></div>';
            });
            wp_safe_redirect(admin_url('admin.php?page=text-to-speech-tts-settings'));
            exit;
        }
    }

    /**
     * Get user info
     *
     * @return array The user info or error message
     */
    public function get_user_info() {
        // Get encrypted API key
        $encrypted_api_key = get_option('mementor_tts_api_key', '');
        
        // If no API key is set, return an error
        if (empty($encrypted_api_key)) {
            return array(
                'error' => true,
                'message' => 'No API key set'
            );
        }
        
        // Decrypt the API key
        try {
            require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-encryption.php';
            $encryption = new Mementor_TTS_Encryption();
            $api_key = $encryption->decrypt($encrypted_api_key);
            
            // If decryption failed, return an error
            if ($api_key === false) {
                return array(
                    'error' => true,
                    'message' => 'Failed to decrypt API key'
                );
            }
        } catch (Exception $e) {
            return array(
                'error' => true,
                'message' => 'Exception during API key decryption: ' . $e->getMessage()
            );
        }
        
        // Make the API request to ElevenLabs
        $response = wp_remote_get('https://api.elevenlabs.io/v1/user/subscription', array(
            'headers' => array(
                'Accept' => 'application/json',
                'xi-api-key' => $api_key
            ),
            'timeout' => 15
        ));
        
        // Check for errors
        if (is_wp_error($response)) {
            return array(
                'error' => true,
                'message' => $response->get_error_message()
            );
        }
        
        // Check the response code
        $response_code = wp_remote_retrieve_response_code($response);
        if ($response_code !== 200) {
            $error_message = 'API Error: ' . $response_code;
            
            // Provide more specific information for common error codes
            if ($response_code === 401) {
                $error_message = 'Authentication Error or Missing Permissions (401): Your API key is either missing the user_read permission, invalid, or expired. Please check your API key in the Settings tab and the ElevenLabs API section.';
            } elseif ($response_code === 403) {
                $error_message = 'Authorization Error (403): Your API key does not have permission to access this resource.';
            } elseif ($response_code === 429) {
                $error_message = 'Rate Limit Error (429): You have exceeded the API rate limit. Please try again later.';
            } elseif ($response_code >= 500) {
                $error_message = 'Server Error (' . $response_code . '): The ElevenLabs API is currently experiencing issues. Please try again later.';
            }
            
            return array(
                'error' => true,
                'message' => $error_message,
                'code' => $response_code
            );
        }
        
        // Get the response body and decode it
        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        
        if (empty($data)) {
            return array(
                'error' => true,
                'message' => 'Invalid response from API'
            );
        }
        
        // Return the user information
        return array(
            'error' => false,
            'status' => isset($data['status']) ? $data['status'] : 'unknown',
            'tier' => isset($data['tier']) ? $data['tier'] : 'unknown',
            'character_count' => isset($data['character_count']) ? $data['character_count'] : 0,
            'character_limit' => isset($data['character_limit']) ? $data['character_limit'] : 0,
            'can_extend_character_limit' => isset($data['can_extend_character_limit']) ? $data['can_extend_character_limit'] : false,
            'allowed_to_extend_character_limit' => isset($data['allowed_to_extend_character_limit']) ? $data['allowed_to_extend_character_limit'] : false,
            'next_character_count_reset_unix' => isset($data['next_character_count_reset_unix']) ? $data['next_character_count_reset_unix'] : 0,
            'voice_limit' => isset($data['voice_limit']) ? $data['voice_limit'] : 0,
            'professional_voice_limit' => isset($data['professional_voice_limit']) ? $data['professional_voice_limit'] : 0,
            'can_extend_voice_limit' => isset($data['can_extend_voice_limit']) ? $data['can_extend_voice_limit'] : false,
            'can_use_instant_voice_cloning' => isset($data['can_use_instant_voice_cloning']) ? $data['can_use_instant_voice_cloning'] : false,
            'can_use_professional_voice_cloning' => isset($data['can_use_professional_voice_cloning']) ? $data['can_use_professional_voice_cloning'] : false,
            'currency' => isset($data['currency']) ? $data['currency'] : 'USD'
        );
    }

    /**
     * Display the Mementor logo header image
     * 
     * @return void
     */
    public function display_header_logo() {
        // Check if we have the image attachment ID
        $logo_id = get_option('mementor_tts_header_logo_id');
        
        if ($logo_id) {
            // Use wp_get_attachment_image if we have an attachment ID
            echo wp_get_attachment_image($logo_id, 'full', false, array(
                'class' => 'mementor-tts-header-logo',
                'alt' => esc_attr__('Mementor Logo', 'text-to-speech-tts')
            ));
        } else {
            // Fallback to the enqueued background image
            echo '<div class="mementor-tts-header-logo"></div>';
        }
    }
    
    /**
     * Display the Mementor logo icon
     * 
     * @return void
     */
    public function display_logo_icon() {
        // Check if we have the image attachment ID
        $icon_id = get_option('mementor_tts_icon_logo_id');
        
        if ($icon_id) {
            // Use wp_get_attachment_image if we have an attachment ID
            echo wp_get_attachment_image($icon_id, 'full', false, array(
                'class' => 'mementor-tts-logo-icon',
                'alt' => esc_attr__('Mementor Icon', 'text-to-speech-tts')
            ));
        } else {
            // Fallback to the direct image tag instead of an empty div
            $icon_url = MEMENTOR_TTS_PLUGIN_URL . 'admin/images/Mementor-Logo-Icon.png';
            echo '<img src="' . esc_url($icon_url) . '" class="mementor-tts-logo-icon" alt="' . esc_attr__('Mementor Icon', 'text-to-speech-tts') . '">';
        }
    }

    /**
     * Initialize logo attachment IDs
     * 
     * This method should be called during plugin activation to properly register
     * the logo images as media attachments in WordPress.
     *
     * @return void
     */
    public function initialize_logo_attachments() {
        // Only run this if we don't already have the attachment IDs
        if (get_option('mementor_tts_header_logo_id') && get_option('mementor_tts_icon_logo_id')) {
            return;
        }
        
        // Get the upload directory
        $upload_dir = wp_upload_dir();
        
        // Header logo
        $header_logo_path = MEMENTOR_TTS_PLUGIN_DIR . 'admin/images/Mementor-Logo-Header.png';
        $header_logo_filename = basename($header_logo_path);
        
        // Check if the file exists in the upload directory
        if (!file_exists($upload_dir['path'] . '/' . $header_logo_filename)) {
            // Copy the file to the upload directory
            copy($header_logo_path, $upload_dir['path'] . '/' . $header_logo_filename);
            
            // Prepare attachment data
            $attachment = array(
                'guid'           => $upload_dir['url'] . '/' . $header_logo_filename,
                'post_mime_type' => 'image/png',
                'post_title'     => 'Mementor Logo Header',
                'post_content'   => '',
                'post_status'    => 'inherit'
            );
            
            // Insert the attachment
            $header_logo_id = wp_insert_attachment($attachment, $upload_dir['path'] . '/' . $header_logo_filename);
            
            // Generate metadata for the attachment
            $attachment_data = wp_generate_attachment_metadata($header_logo_id, $upload_dir['path'] . '/' . $header_logo_filename);
            wp_update_attachment_metadata($header_logo_id, $attachment_data);
            
            // Save the attachment ID
            update_option('mementor_tts_header_logo_id', $header_logo_id);
        }
        
        // Icon logo
        $icon_logo_path = MEMENTOR_TTS_PLUGIN_DIR . 'admin/images/Mementor-Logo-Icon.png';
        $icon_logo_filename = basename($icon_logo_path);
        
        // Check if the file exists in the upload directory
        if (!file_exists($upload_dir['path'] . '/' . $icon_logo_filename)) {
            // Copy the file to the upload directory
            copy($icon_logo_path, $upload_dir['path'] . '/' . $icon_logo_filename);
            
            // Prepare attachment data
            $attachment = array(
                'guid'           => $upload_dir['url'] . '/' . $icon_logo_filename,
                'post_mime_type' => 'image/png',
                'post_title'     => 'Mementor Logo Icon',
                'post_content'   => '',
                'post_status'    => 'inherit'
            );
            
            // Insert the attachment
            $icon_logo_id = wp_insert_attachment($attachment, $upload_dir['path'] . '/' . $icon_logo_filename);
            
            // Generate metadata for the attachment
            $attachment_data = wp_generate_attachment_metadata($icon_logo_id, $upload_dir['path'] . '/' . $icon_logo_filename);
            wp_update_attachment_metadata($icon_logo_id, $attachment_data);
            
            // Save the attachment ID
            update_option('mementor_tts_icon_logo_id', $icon_logo_id);
        }
    }

    /**
     * Process text for TTS by adding appropriate pauses and formatting
     * 
     * @param string $text The text to process
     * @return string The processed text
     */
    /**
     * Sanitize text while preserving safe SSML tags
     */
    private function sanitize_text_with_ssml($text) {
        // Define allowed SSML tags and their attributes
        $allowed_tags = array(
            'break' => array(
                'time' => array(),
                'strength' => array()
            ),
            'emphasis' => array(
                'level' => array()
            ),
            'prosody' => array(
                'rate' => array(),
                'pitch' => array(),
                'volume' => array()
            ),
            'phoneme' => array(
                'alphabet' => array(),
                'ph' => array()
            ),
            'sub' => array(
                'alias' => array()
            ),
            'say-as' => array(
                'interpret-as' => array(),
                'format' => array()
            ),
            'p' => array(),
            's' => array()
        );

        // Allow only safe SSML tags
        $text = wp_kses($text, $allowed_tags);
        
        // Sanitize text content without removing the SSML tags we just allowed
        // Use only the safe parts of sanitize_textarea_field() without tag stripping
        $text = wp_check_invalid_utf8($text);
        $text = wp_kses_no_null($text);
        $text = trim($text);
        
        return $text;
    }

    private function process_text_for_ssml($text) {
        // Remove any existing speak tags since they'll be added by the processor
        $text = preg_replace('#</?speak>#', '', $text);
        
        // Remove any peak tags that might exist (these are not standard SSML)
        $text = preg_replace('#</?peak[^>]*>#', '', $text);
        
        // Preserve SSML tags but remove other HTML tags
        $allowed_ssml_pattern = '/<(\/?(?:break|emphasis|prosody|phoneme|sub|say-as|p|s)(?:\s[^>]*)?\s*)>/';
        
        // Extract SSML tags temporarily
        $ssml_tags = array();
        $text = preg_replace_callback($allowed_ssml_pattern, function($matches) use (&$ssml_tags) {
            $placeholder = '___SSML_TAG_' . count($ssml_tags) . '___';
            $ssml_tags[$placeholder] = $matches[0];
            return $placeholder;
        }, $text);
        
        // Remove remaining HTML tags (non-SSML)
        $text = preg_replace('/<[^>]*>/', '', $text);
        
        // Restore SSML tags
        foreach ($ssml_tags as $placeholder => $tag) {
            $text = str_replace($placeholder, $tag, $text);
        }
        
        // Replace hyphens with spaces to improve speech quality (e.g., "SEO-dude" becomes "SEO dude")
        // But avoid affecting SSML attributes
        $text = preg_replace_callback('/([^<>]*?)/', function($matches) {
            return preg_replace('/(\w)-(\w)/', '$1 $2', $matches[0]);
        }, $text);
        
        // Add initial pause before title (only if no SSML break exists at start or in text)
        if (!preg_match('/^\s*<break/', $text) && !preg_match('/<break\s+time="[0-9.]+s"\s*\/>/', $text)) {
            $text = '<break time="0.5s"/> ' . $text;
        }
        
        // Replace double newlines with longer pause (preserve existing SSML breaks)
        $text = preg_replace('/\n\n/', ' <break time="1s"/> ', $text);
        
        // Replace single newlines with shorter pause
        $text = preg_replace('/\n/', ' <break time="0.3s"/> ', $text);
        
        // Replace paragraph endings with longer pause
        $text = str_replace('</p>', ' <break time="1s"/> ', $text);
        
        // Replace line breaks with shorter pause
        $text = str_replace('<br>', ' <break time="0.5s"/> ', $text);
        
        // Clean up multiple consecutive default breaks only (preserve user-specified breaks)
        $text = preg_replace('/<break time="0\.3s"\/>\s*<break time="0\.3s"\/>/', '<break time="0.5s"/>', $text);
        $text = preg_replace('/<break time="0\.5s"\/>\s*<break time="0\.5s"\/>/', '<break time="1s"/>', $text);
        $text = preg_replace('/<break time="1s"\/>\s*<break time="1s"\/>/', '<break time="1.5s"/>', $text);
        $text = preg_replace('/\s+/', ' ', $text);
        
        return trim($text);
    }

    /**
     * Generate audio for shortcode via AJAX
     */
    public function generate_shortcode_audio_ajax() {
        try {
            // Start output buffering to prevent any PHP notices/warnings from breaking our JSON response
            ob_start();

            // Verify nonce
            if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'mementor_tts_nonce')) {
                wp_send_json_error(array('message' => __('Security check failed.', 'text-to-speech-tts')));
                return;
            }

            // Check user capabilities
            if (!current_user_can('edit_posts')) {
                wp_send_json_error(array('message' => __('You do not have permission to generate audio.', 'text-to-speech-tts')));
                return;
            }

            // Check if text is provided
            if (!isset($_POST['text']) || empty($_POST['text'])) {
                wp_send_json_error(array('message' => __('No text provided.', 'text-to-speech-tts')));
                return;
            }

            // Sanitize the text while preserving SSML tags
            $text = isset($_POST['text']) ? $this->sanitize_text_with_ssml(wp_unslash($_POST['text'])) : '';
            
            // Check if PRO plugin is active
            $is_pro_active = ttswp_has_paid_plan();
            
            // Character limits based on license
            $char_limit = $is_pro_active ? 10000 : 2000;
            
            // Server-side character limit validation
            if (mb_strlen($text) > $char_limit) {
                /* translators: %d: Maximum allowed characters for a single generation request. */
                wp_send_json_error(array('message' => sprintf(__('Text exceeds the maximum character limit of %d. Please reduce your text.', 'text-to-speech-tts'), $char_limit)));
                return;
            }
            
            // Convert escaped newlines to actual newlines
            $text = str_replace('\n', "\n", $text);
            
            // Process text for SSML
            $text = $this->process_text_for_ssml($text);
            

            // Get or generate shortcode ID
            $shortcode_id = '';
            if (isset($_POST['element_uid']) && !empty($_POST['element_uid'])) {
                $shortcode_id = sanitize_text_field(wp_unslash($_POST['element_uid']));
            } else {
                $shortcode_id = 'tts_' . uniqid();
            }

            // Get current language code
            $language_code = 'en'; // Default to English
            if (defined('ICL_LANGUAGE_CODE')) {
                $language_code = ICL_LANGUAGE_CODE;
            } else {
                $language_code = get_option('mementor_tts_language', 'en');
            }

            // Check if audio file already exists with language code
            $upload_dir = wp_upload_dir();
            $audio_dir = $upload_dir['basedir'] . '/text-to-speech-tts/';
            $audio_file = $audio_dir . $shortcode_id . '-' . $language_code . '.mp3';

            if (file_exists($audio_file)) {
                $audio_url = $upload_dir['baseurl'] . '/text-to-speech-tts/' . $shortcode_id . '-' . $language_code . '.mp3';
                wp_send_json_success(array(
                    'message' => __('Audio file already exists.', 'text-to-speech-tts'),
                    'file_url' => $audio_url,
                    'shortcode_id' => $shortcode_id
                ));
                return;
            }

            // Include required files
            require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-processor.php';

            // Get TTS processor instance
            $processor = Mementor_TTS_Processor::get_instance();

            // Get voice ID
            $voice_id = '';
            if (isset($_POST['voice_id']) && !empty($_POST['voice_id'])) {
                $voice_id = sanitize_text_field(wp_unslash($_POST['voice_id']));
            } else {
                // Check if paid plan user has a custom voice ID
                if (ttswp_has_paid_plan()) {
                    $custom_voice_id = get_option('mementor_tts_custom_voice_id', '');
                    if (!empty($custom_voice_id)) {
                        $voice_id = $custom_voice_id;
                    }
                }
                
                // If still no voice ID, use the selected voice
                if (empty($voice_id)) {
                    $voice_id = get_option('mementor_tts_voice', 'EXAVITQu4vr4xnSDxMaL'); // Always provide a default voice ID (Nicole) if none is selected
                }
            }

            
            // Generate audio
            $result = $processor->generate_shortcode_audio($text, $shortcode_id, $voice_id);

            if (!$result['success']) {
                // Check if this is a voice_limit_reached error
                if (isset($result['error_type']) && $result['error_type'] === 'voice_limit_reached') {
                    wp_send_json_error(array(
                        'message' => $result['message'],
                        'error_type' => 'voice_limit_reached',
                        'data' => 'voice_limit_reached'
                    ));
                } else {
                    wp_send_json_error(array('message' => $result['message']));
                }
                return;
            }

            // Return success response
            wp_send_json_success(array(
                'message' => __('Audio created successfully.', 'text-to-speech-tts'),
                'file_url' => $result['file_url'],
                'shortcode_id' => $shortcode_id
            ));
        } catch (Exception $e) {
            wp_send_json_error(array('message' => $e->getMessage()));
        }
    }

    /**
     * Handle AJAX request for saving voice settings
     */
    public function save_voice_ajax() {
        // Make sure the AJAX class is loaded
        if (!class_exists('Mementor_TTS_Ajax')) {
            require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-ajax.php';
        }
        
        // Get the AJAX handler instance using the singleton pattern
        $ajax_handler = Mementor_TTS_Ajax::get_instance();
        
        // Call the save_voice method
        $ajax_handler->save_voice();
    }

    /**
     * Get OpenAI usage statistics
     */
    public function get_openai_usage_stats() {
        // This method is a placeholder as OpenAI is not used in this plugin
        return array(
            'success' => true,
            'message' => 'OpenAI usage tracking not implemented',
            'tier' => 'N/A',
            'character_count' => 0,
            'character_limit' => 0,
            'percentage_used' => 0
        );
    }

    /**
     * Handle AJAX request for canceling processing state
     */
    public function cancel_processing_ajax() {
        // Check nonce for security
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'mementor_tts_nonce')) {
            wp_send_json_error(array('message' => __('Security check failed.', 'text-to-speech-tts')));
            return;
        }

        // Check if post ID is provided
        if (!isset($_POST['post_id']) || empty($_POST['post_id'])) {
            wp_send_json_error(array('message' => __('No post ID provided.', 'text-to-speech-tts')));
            return;
        }

        // Sanitize and validate post_id
        $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
        if (!$post_id) {
            wp_send_json_error(array('message' => __('Invalid post ID.', 'text-to-speech-tts')));
            return;
        }

        // Check user permissions for the specific post
        if (!current_user_can('edit_post', $post_id)) {
            wp_send_json_error(array('message' => __('You do not have permission to edit this post.', 'text-to-speech-tts')));
            return;
        }

        // Delete the processing flag
        delete_post_meta($post_id, '_mementor_tts_processing');

        // Send success response
        wp_send_json_success(array(
            'message' => __('Processing state cleared successfully.', 'text-to-speech-tts')
        ));
    }

    /**
     * Handle AJAX request for deleting audio
     */
    public function delete_audio_ajax() {
        // Prevent any output before our JSON response
        ob_start();

        try {
            // Check nonce for security
            check_ajax_referer('mementor_tts_nonce', 'nonce');

            // Check if post ID is provided
            if (!isset($_POST['post_id'])) {
                wp_send_json_error(array('message' => __('No post ID provided.', 'text-to-speech-tts')));
                return;
            }

            // Sanitize and validate post_id
            $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
            if (!$post_id) {
                wp_send_json_error(array('message' => __('Invalid post ID.', 'text-to-speech-tts')));
                return;
            }

            // Check if post exists
            $post = get_post($post_id);
            if (!$post) {
                wp_send_json_error(array('message' => __('Post not found.', 'text-to-speech-tts')));
                return;
            }

            // Check user permissions for the specific post
            if (!current_user_can('edit_post', $post_id)) {
                wp_send_json_error(array('message' => __('You do not have permission to edit this post.', 'text-to-speech-tts')));
                return;
            }

            // Get current language code
            $language_code = 'en'; // Default to English
            if (defined('ICL_LANGUAGE_CODE')) {
                $language_code = ICL_LANGUAGE_CODE;
            } else {
                $language_code = get_option('mementor_tts_language', 'en');
            }

            // Always suffix with the language code. The writers (processor.php,
            // api-client.php) store URLs under `_mementor_tts_audio_url_{lang}`
            // including `_en`, so an empty suffix for English collapses every
            // downstream targeted operation onto the base key and leaves the
            // language-specific meta behind — which is why deleted audio
            // re-appears on the next page refresh.
            $key_suffix = '_' . sanitize_key($language_code);

            // Check if audio URL exists in post meta (could be S3 or local)
            // First try language-specific, then fall back to base meta key
            $audio_url = get_post_meta($post_id, '_mementor_tts_audio_url' . $key_suffix, true);
            if (empty($audio_url)) {
                // Fall back to base meta key (no language suffix)
                $audio_url = get_post_meta($post_id, '_mementor_tts_audio_url', true);
            }

            error_log('TTS Delete Admin: Language=' . $language_code . ', Audio URL=' . $audio_url);

            // Check if this is an S3 file
            if (!empty($audio_url) && (strpos($audio_url, 'amazonaws.com') !== false || strpos($audio_url, '.s3.') !== false)) {
                error_log('TTS Delete Admin: Detected S3 URL: ' . $audio_url);

                // Include S3 handler class
                if (!class_exists('Mementor_TTS_S3_Handler')) {
                    require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-mementor-tts-s3-handler.php';
                }

                $s3_handler = new Mementor_TTS_S3_Handler();

                if ($s3_handler->is_configured()) {
                    // Extract S3 key from URL
                    $parsed_url = wp_parse_url($audio_url);
                    if (isset($parsed_url['path'])) {
                        $s3_key = ltrim($parsed_url['path'], '/');
                        error_log('TTS Delete Admin: Deleting S3 file with key: ' . $s3_key);

                        // Delete from S3
                        $deleted = $s3_handler->delete_file($s3_key, true);

                        if (!$deleted) {
                            error_log('TTS Delete Admin: S3 deletion failed');
                            wp_send_json_error(array('message' => __('Failed to delete audio file from S3.', 'text-to-speech-tts')));
                            return;
                        }
                        error_log('TTS Delete Admin: S3 deletion successful');
                    } else {
                        wp_send_json_error(array('message' => __('Invalid S3 URL format.', 'text-to-speech-tts')));
                        return;
                    }
                } else {
                    error_log('TTS Delete Admin: S3 URL detected but S3 not configured');
                    wp_send_json_error(array('message' => __('S3 is not configured.', 'text-to-speech-tts')));
                    return;
                }
            } else {
                // Local file deletion
                // First try via Media Library attachment (which also deletes the physical file)
                $attachment_id = get_post_meta($post_id, '_mementor_tts_attachment_id', true);
                $file_deleted = false;

                if ($attachment_id) {
                    wp_delete_attachment(absint($attachment_id), true);
                    delete_post_meta($post_id, '_mementor_tts_attachment_id');
                    $file_deleted = true;
                }

                // If no attachment handled it, delete the file directly
                if (!$file_deleted && !empty($audio_url)) {
                    $upload_dir = wp_upload_dir();
                    $audio_file = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $audio_url);

                    if (!empty($audio_file) && file_exists($audio_file)) {
                        wp_delete_file($audio_file);
                    }
                }
            }

            // Remove post meta for this language (both language-specific and base keys)
            delete_post_meta($post_id, '_mementor_tts_audio_file' . $key_suffix);
            delete_post_meta($post_id, '_mementor_tts_audio_url' . $key_suffix);
            delete_post_meta($post_id, '_mementor_tts_generated_date' . $key_suffix);

            // Also delete base meta keys (no language suffix)
            delete_post_meta($post_id, '_mementor_tts_audio_file');
            delete_post_meta($post_id, '_mementor_tts_audio_url');
            delete_post_meta($post_id, '_mementor_tts_generated_date');
            delete_post_meta($post_id, '_mementor_tts_has_audio');
            delete_post_meta($post_id, '_mementor_tts_last_generated');

            // Delete from speeches database table
            global $wpdb;
            $table_name = $wpdb->prefix . 'mementor_tts_speeches';
            $deleted_rows = $wpdb->delete(
                $table_name,
                array('post_id' => $post_id),
                array('%d')
            );
            error_log('TTS Delete Admin: Deleted ' . $deleted_rows . ' rows from speeches table');

            // Clear WordPress object cache for this post
            wp_cache_delete($post_id, 'post_meta');
            wp_cache_delete($post_id, 'posts');

            // Clear any TTS-specific caches
            wp_cache_delete('mementor_tts_audio_' . $post_id, 'mementor_tts');

            // Send success response
            wp_send_json_success(array(
                'message' => __('Audio deleted successfully.', 'text-to-speech-tts')
            ));
        } catch (Exception $e) {
            // Send error response
            wp_send_json_error(array('message' => $e->getMessage()));
        }

        // End execution to prevent any additional output
        wp_die();
    }

    /**
     * Stream the post's audio file back with Content-Disposition: attachment
     * so the browser pops a save dialog even when the source lives on a
     * cross-origin CDN (where the HTML `download` attribute is ignored).
     *
     * URL resolution mirrors render_tts_column() exactly so the download
     * proxy and the visible play button always agree on the target file.
     */
    public function download_audio_ajax() {
        $post_id  = isset($_GET['post_id']) ? absint(wp_unslash($_GET['post_id'])) : 0;
        $nonce    = isset($_GET['nonce']) ? sanitize_key(wp_unslash($_GET['nonce'])) : '';
        // Optional language override used by the Weglot per-language column.
        $language = isset($_GET['language']) ? sanitize_key(wp_unslash($_GET['language'])) : '';

        if (!$post_id || !wp_verify_nonce($nonce, 'mementor_tts_nonce')) {
            wp_die(esc_html__('Security check failed.', 'text-to-speech-tts'), '', array('response' => 403));
        }

        if (!current_user_can('edit_post', $post_id)) {
            wp_die(esc_html__('You do not have permission to download this audio.', 'text-to-speech-tts'), '', array('response' => 403));
        }

        // Same language + resolution order as render_tts_column(). An explicit
        // `language` query arg wins over the site's active language.
        if ($language !== '') {
            $language_code = $language;
        } elseif (defined('ICL_LANGUAGE_CODE')) {
            $language_code = ICL_LANGUAGE_CODE;
        } else {
            $language_code = get_option('mementor_tts_language', 'en');
        }

        // When a language is explicitly requested, prefer the language-specific
        // meta so Weglot rows download their own file, not the generic one.
        if ($language !== '') {
            $audio_url = get_post_meta($post_id, '_mementor_tts_audio_url_' . $language_code, true);
        } else {
            $audio_url = get_post_meta($post_id, '_mementor_tts_audio_url', true);
            if (empty($audio_url)) {
                $audio_url = get_post_meta($post_id, '_mementor_tts_audio_url_' . $language_code, true);
            }
        }
        if (empty($audio_url)) {
            $upload_dir = wp_upload_dir();
            $audio_dir  = $upload_dir['basedir'] . '/text-to-speech-tts/';
            $candidates = array(
                'mementor-' . $post_id . '-' . $language_code . '.mp3',
                'mementor-' . $post_id . '-en.mp3',
                'post-' . $post_id . '-' . $language_code . '.mp3',
                'post-' . $post_id . '.mp3',
            );
            foreach ($candidates as $name) {
                if (file_exists($audio_dir . $name)) {
                    $audio_url = $upload_dir['baseurl'] . '/text-to-speech-tts/' . $name;
                    break;
                }
            }
        }

        if (empty($audio_url)) {
            wp_die(esc_html__('No audio file found for this post.', 'text-to-speech-tts'), '', array('response' => 404));
        }

        // Keep the source filename — no renaming scheme introduced here.
        $path     = wp_parse_url($audio_url, PHP_URL_PATH);
        $filename = $path ? sanitize_file_name(basename($path)) : '';
        if ($filename === '' || pathinfo($filename, PATHINFO_EXTENSION) === '') {
            $filename = 'tts-' . $post_id . '.mp3';
        }

        // Local file under uploads? → readfile. Otherwise stream remote.
        $upload_dir = wp_upload_dir();
        $local_path = '';
        if (is_array($upload_dir) && !empty($upload_dir['baseurl']) && strpos($audio_url, $upload_dir['baseurl']) === 0) {
            $candidate = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $audio_url);
            if (is_string($candidate) && file_exists($candidate) && is_readable($candidate)) {
                $local_path = $candidate;
            }
        }

        while (ob_get_level() > 0) {
            ob_end_clean();
        }

        nocache_headers();
        header('Content-Type: audio/mpeg');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('X-Content-Type-Options: nosniff');

        if ($local_path !== '') {
            header('Content-Length: ' . filesize($local_path));
            readfile($local_path);
            exit;
        }

        // Prefer chunked fopen so large CDN files don't sit in memory.
        $stream = @fopen($audio_url, 'rb');
        if ($stream) {
            while (!feof($stream)) {
                $chunk = fread($stream, 8192);
                if ($chunk === false) {
                    break;
                }
                echo $chunk;
                flush();
            }
            fclose($stream);
            exit;
        }

        // Fallback for hosts with allow_url_fopen disabled.
        $response = wp_remote_get($audio_url, array('timeout' => 30));
        if (is_wp_error($response) || 200 !== wp_remote_retrieve_response_code($response)) {
            wp_die(esc_html__('Unable to fetch the audio file.', 'text-to-speech-tts'), '', array('response' => 502));
        }
        $body = wp_remote_retrieve_body($response);
        header('Content-Length: ' . strlen($body));
        echo $body;
        exit;
    }

    /**
     * Validate API key via AJAX
     */
    public function validate_api_key_ajax() {
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'mementor_tts_nonce')) {
            wp_send_json_error(array('message' => __('Security check failed.', 'text-to-speech-tts')));
            wp_die();
        }

        // Check if user has permission
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'text-to-speech-tts')));
            wp_die();
        }

        // Get API key from POST data
        $api_key = isset($_POST['api_key']) ? sanitize_text_field(wp_unslash($_POST['api_key'])) : '';

        // Check if API key has valid format before attempting validation
        $has_valid_format = (strlen($api_key) === 32 && ctype_alnum($api_key)) || (strpos($api_key, 'sk_') === 0 && strlen($api_key) > 5);

        if (!$has_valid_format) {
            wp_send_json_error(array('message' => __('Invalid API key format. Keys should be 32 alphanumeric characters or start with sk_', 'text-to-speech-tts')));
            wp_die();
        }

        // Store the API key first
        $stored = $this->store_api_key($api_key);
        if (!$stored) {
            wp_send_json_error(array('message' => __('Failed to store API key.', 'text-to-speech-tts')));
            wp_die();
        }

        // Validate the key against ElevenLabs API and check permissions
        $check = $this->check_api_key_permissions($api_key);

        // If the key is entirely invalid (401 from ElevenLabs)
        if (!$check['key_valid']) {
            // Remove the invalid key
            delete_option('mementor_tts_api_key');
            update_option('mementor_tts_api_key_status', 'invalid');
            update_option('mementor_tts_api_key_permissions', 'none');
            wp_send_json_error(array(
                'message' => __('API key is invalid or expired. Please check your key and try again.', 'text-to-speech-tts'),
            ));
            wp_die();
        }

        // If there was a network error, save key but note we couldn't verify
        if (!empty($check['network_error'])) {
            update_option('mementor_tts_api_key_status', 'valid');
            update_option('mementor_tts_api_key_permissions', 'unknown');
            wp_send_json_success(array(
                'message' => __('API key saved. Could not verify permissions due to a network error.', 'text-to-speech-tts'),
                'valid' => true,
                'reload' => true,
            ));
            wp_die();
        }

        // Key is valid - save permission details
        $permissions = $check['details'];
        $all_granted = !in_array(false, $permissions, true);

        update_option('mementor_tts_api_key_status', 'valid');
        update_option('mementor_tts_api_key_permissions', $all_granted ? 'full' : 'limited');
        update_option('mementor_tts_api_key_permissions_detail', wp_json_encode($permissions));

        // Build the notice HTML to include in the AJAX response
        $perm_labels = array(
            'tts_access'          => __('Text to Speech: Access', 'text-to-speech-tts'),
            'stt_access'          => __('Speech to Text: Access', 'text-to-speech-tts'),
            'sound_effect_access' => __('Sound Effect: Access (PRO)', 'text-to-speech-tts'),
            'audio_native_read'   => __('Audio Native: Read', 'text-to-speech-tts'),
            'voices_read'         => __('Voices: Read', 'text-to-speech-tts'),
            'history_read'        => __('History: Read', 'text-to-speech-tts'),
            'user_read'           => __('User: Read', 'text-to-speech-tts'),
        );
        $missing = array();
        foreach ($perm_labels as $key => $label) {
            if (empty($permissions[$key])) {
                $missing[] = $label;
            }
        }
        $notice_type = empty($missing) ? 'success' : 'warning';
        $notice_title = empty($missing)
            ? __('API key validated and saved successfully! All permissions are enabled.', 'text-to-speech-tts')
            : __('API key saved, but some permissions are missing.', 'text-to-speech-tts');

        ob_start();
        ?>
        <div class="notice notice-<?php echo esc_attr($notice_type); ?>" style="margin:15px 0;padding:12px 16px;" id="mementor-tts-validation-notice">
            <p><strong><?php echo esc_html($notice_title); ?></strong></p>
            <div style="margin:8px 0 4px;">
            <?php foreach ($perm_labels as $key => $label) :
                $has = !empty($permissions[$key]);
            ?>
                <div style="display:flex;align-items:center;gap:6px;padding:3px 0;">
                    <span class="dashicons <?php echo $has ? 'dashicons-yes-alt' : 'dashicons-dismiss'; ?>" style="color:<?php echo $has ? '#00a32a' : '#d63638'; ?>;"></span>
                    <?php echo esc_html($label); ?>
                </div>
            <?php endforeach; ?>
            </div>
            <?php if (!empty($missing)) : ?>
            <p style="margin-top:10px;">
                <?php echo wp_kses_post(
                    sprintf(
                        /* translators: %s: comma-separated list of missing permissions */
                        __('Your API key is missing: %s.', 'text-to-speech-tts'),
                        '<strong>' . esc_html(implode(', ', $missing)) . '</strong>'
                    )
                ); ?>
                <br>
                <?php echo wp_kses_post(
                    sprintf(
                        /* translators: %s: link to ElevenLabs API keys page */
                        __('You can update your API key permissions at %s', 'text-to-speech-tts'),
                        '<a href="https://elevenlabs.io/app/developers/api-keys" target="_blank" rel="noopener">elevenlabs.io/app/developers/api-keys</a>'
                    )
                ); ?>
            </p>
            <?php endif; ?>
        </div>
        <?php
        $notice_html = ob_get_clean();

        wp_send_json_success(array(
            'message' => $notice_title,
            'valid' => true,
            'permissions' => $permissions,
            'notice_html' => $notice_html,
            'reload' => true,
        ));
        wp_die();
    }

    /**
     * Re-validate permissions on the already-stored API key via AJAX.
     */
    public function revalidate_permissions_ajax() {
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'mementor_tts_nonce')) {
            wp_send_json_error(array('message' => __('Security check failed.', 'text-to-speech-tts')));
            wp_die();
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'text-to-speech-tts')));
            wp_die();
        }

        // Get and decrypt the stored key
        $encrypted_key = get_option('mementor_tts_api_key');
        if (empty($encrypted_key)) {
            wp_send_json_error(array('message' => __('No API key stored. Please add one first.', 'text-to-speech-tts')));
            wp_die();
        }

        require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-encryption.php';
        $encryption = new Mementor_TTS_Encryption();
        $api_key = $encryption->decrypt($encrypted_key);

        if ($api_key === false) {
            wp_send_json_error(array('message' => __('Failed to decrypt stored API key.', 'text-to-speech-tts')));
            wp_die();
        }

        // Re-check permissions
        $check = $this->check_api_key_permissions($api_key);

        if (!$check['key_valid']) {
            update_option('mementor_tts_api_key_status', 'invalid');
            update_option('mementor_tts_api_key_permissions', 'none');
            delete_option('mementor_tts_api_key_permissions_detail');
            wp_send_json_error(array('message' => __('API key is no longer valid.', 'text-to-speech-tts')));
            wp_die();
        }

        if (!empty($check['network_error'])) {
            wp_send_json_success(array(
                'message' => __('Could not verify permissions due to a network error. Please try again.', 'text-to-speech-tts'),
            ));
            wp_die();
        }

        $permissions = $check['details'];
        $all_granted = !in_array(false, $permissions, true);

        update_option('mementor_tts_api_key_status', 'valid');
        update_option('mementor_tts_api_key_permissions', $all_granted ? 'full' : 'limited');
        update_option('mementor_tts_api_key_permissions_detail', wp_json_encode($permissions));

        wp_send_json_success(array(
            'message' => $all_granted
                ? __('All permissions are enabled!', 'text-to-speech-tts')
                : __('Some permissions are still missing.', 'text-to-speech-tts'),
        ));
        wp_die();
    }

    /**
     * Check which permissions an ElevenLabs API key has
     *
     * Tests the key against multiple ElevenLabs endpoints to detect
     * which scopes/permissions are enabled.
     *
     * @param string $api_key The raw API key to check.
     * @return array {
     *     @type bool       $key_valid     Whether the key is recognized by ElevenLabs.
     *     @type bool       $network_error True when a network issue prevented checking.
     *     @type array|null $details       Associative array of permission => bool, or null on network error.
     * }
     */
    private function check_api_key_permissions($api_key) {
        $api_base   = 'https://api.elevenlabs.io/v1';
        $timeout    = 10;
        $ssl_verify = apply_filters('mementor_tts_ssl_verify', true);
        $debug      = mementor_tts_is_debug_enabled();

        $get_headers = array(
            'xi-api-key' => $api_key,
            'Accept'     => 'application/json',
        );
        $post_headers = array(
            'xi-api-key'   => $api_key,
            'Accept'       => 'application/json',
            'Content-Type' => 'application/json',
        );
        $base_args = array(
            'timeout'   => $timeout,
            'sslverify' => $ssl_verify,
        );

        /**
         * Helper: determine permission state from an HTTP response.
         *
         * ElevenLabs returns 401 for BOTH invalid keys and missing permissions.
         * The difference is in the JSON body:
         *   {"detail":{"status":"invalid_api_key",...}}   → key is bad
         *   {"detail":{"status":"missing_permissions",...}} → key OK, perm missing
         *
         * Returns: 'granted' | 'missing' | 'invalid_key' | 'error'
         */
        $check_response = function ($response, $label) use ($debug) {
            if (is_wp_error($response)) {
                if ($debug) {
                    // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
                    error_log("[TTS Permission Check] {$label} network error: " . $response->get_error_message());
                }
                return 'error';
            }

            $code = wp_remote_retrieve_response_code($response);
            $body = wp_remote_retrieve_body($response);

            if ($debug) {
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
                error_log("[TTS Permission Check] {$label} HTTP {$code}");
            }

            // 200-level means permission is present
            if ($code >= 200 && $code < 300) {
                return 'granted';
            }

            // Parse body for 401 responses to distinguish invalid key vs missing permission
            if ($code === 401) {
                $json = json_decode($body, true);
                $status = isset($json['detail']['status']) ? $json['detail']['status'] : '';

                if ($status === 'invalid_api_key') {
                    return 'invalid_key';
                }
                // "missing_permissions" or any other 401 reason = key exists but perm missing
                return 'missing';
            }

            // 400, 404, 422 etc. = got past auth, request was just malformed → has permission
            if ($code >= 400 && $code < 500) {
                return 'granted';
            }

            // 5xx or anything else → can't tell
            return 'error';
        };

        // ── 1. User: Read (GET /user) — also doubles as key-validity check ──
        $response = wp_remote_get(
            $api_base . '/user',
            array_merge($base_args, array('headers' => $get_headers))
        );
        $result = $check_response($response, '/user');

        if ($result === 'error' && is_wp_error($response)) {
            return array('key_valid' => true, 'network_error' => true, 'details' => null);
        }
        if ($result === 'invalid_key') {
            return array('key_valid' => false, 'network_error' => false, 'details' => null);
        }

        $permissions = array();
        $permissions['user_read'] = ($result === 'granted');

        // ── 2. Voices: Read ─────────────────────────────────────────
        $response = wp_remote_get(
            $api_base . '/voices?page_size=1',
            array_merge($base_args, array('headers' => $get_headers))
        );
        $result = $check_response($response, '/voices');
        $permissions['voices_read'] = ($result === 'granted');

        // ── 3. Text to Speech: Access ───────────────────────────────
        $response = wp_remote_post(
            $api_base . '/text-to-speech/perm_check_test',
            array_merge($base_args, array(
                'headers' => $post_headers,
                'body'    => wp_json_encode(array(
                    'text'     => 'test',
                    'model_id' => 'eleven_monolingual_v1',
                )),
            ))
        );
        $result = $check_response($response, '/text-to-speech');
        $permissions['tts_access'] = ($result === 'granted');

        // ── 4. Speech to Text: Access ───────────────────────────────
        // STT requires multipart/form-data with a file; without it the endpoint
        // returns 400 BEFORE checking permissions, so we must send a proper
        // multipart body with a tiny dummy file to trigger the auth check.
        $boundary = wp_generate_password(24, false);
        $stt_body  = "--{$boundary}\r\n";
        $stt_body .= "Content-Disposition: form-data; name=\"model_id\"\r\n\r\n";
        $stt_body .= "scribe_v1\r\n";
        $stt_body .= "--{$boundary}\r\n";
        $stt_body .= "Content-Disposition: form-data; name=\"file\"; filename=\"test.wav\"\r\n";
        $stt_body .= "Content-Type: audio/wav\r\n\r\n";
        $stt_body .= "RIFF\x24\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00\x44\xac\x00\x00\x88\x58\x01\x00\x02\x00\x10\x00data\x00\x00\x00\x00";
        $stt_body .= "\r\n--{$boundary}--\r\n";

        $response = wp_remote_post(
            $api_base . '/speech-to-text',
            array_merge($base_args, array(
                'headers' => array(
                    'xi-api-key'   => $api_key,
                    'Accept'       => 'application/json',
                    'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
                ),
                'body' => $stt_body,
            ))
        );
        $result = $check_response($response, '/speech-to-text');
        $permissions['stt_access'] = ($result === 'granted');

        // ── 5. Sound Effect: Access (PRO feature) ───────────────────
        $response = wp_remote_post(
            $api_base . '/sound-generation',
            array_merge($base_args, array(
                'headers' => $post_headers,
                'body'    => wp_json_encode(array('text' => '', 'duration_seconds' => 0.5)),
            ))
        );
        $result = $check_response($response, '/sound-generation');
        $permissions['sound_effect_access'] = ($result === 'granted');

        // ── 6. Audio Native: Read ───────────────────────────────────
        $response = wp_remote_get(
            $api_base . '/audio-native',
            array_merge($base_args, array('headers' => $get_headers))
        );
        $result = $check_response($response, '/audio-native');
        $permissions['audio_native_read'] = ($result === 'granted');

        // ── 7. History: Read ────────────────────────────────────────
        $response = wp_remote_get(
            $api_base . '/history?page_size=1',
            array_merge($base_args, array('headers' => $get_headers))
        );
        $result = $check_response($response, '/history');
        $permissions['history_read'] = ($result === 'granted');

        return array('key_valid' => true, 'network_error' => false, 'details' => $permissions);
    }
    
    /**
     * Reset API settings via AJAX
     */
    public function reset_api_settings_ajax() {
        try {
            // Check nonce for security
            if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'mementor_tts_nonce')) {
                wp_send_json_error(array('message' => __('Security check failed.', 'text-to-speech-tts')));
                return;
            }

            // Check user capabilities
            if (!current_user_can('manage_options')) {
                wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'text-to-speech-tts')));
                return;
            }
            
            // Reset API key and related settings
            delete_option('mementor_tts_api_key');
            delete_option('mementor_tts_api_key_status');
            delete_option('mementor_tts_voice');
            delete_option('mementor_tts_voice_name');
            
            // Return success with a clear message
            wp_send_json_success(array(
                'message' => 'API settings reset successfully.',
                'html_message' => '<strong>API Settings Reset Successfully</strong><br>Your API key has been removed and all related settings have been reset.'
            ));
        } catch (Exception $e) {
            wp_send_json_error('An error occurred while resetting settings: ' . $e->getMessage());
        }
    }
    
    /**
     * Clear cache via AJAX
     */
    public function clear_cache_ajax() {
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'mementor_tts_clear_cache_nonce')) {
            wp_send_json_error(array('message' => __('Security check failed.', 'text-to-speech-tts')));
            return;
        }

        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'text-to-speech-tts')));
            return;
        }

        // Get API instance
        $api = Mementor_TTS_ElevenLabs_API::get_instance();

        // Clear cache
        $api->clear_cache();

        // Send success response
        wp_send_json_success(array('message' => __('Cache cleared successfully.', 'text-to-speech-tts')));
    }
    
    /**
     * Import settings via AJAX
     */
    public function import_settings_ajax() {
        // Check nonce for security
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'mementor_tts_advanced_nonce')) {
            wp_send_json_error(__('Security check failed.', 'text-to-speech-tts'));
            return;
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'text-to-speech-tts')));
            return;
        }
        
        // Get the settings data
        $settings_data = isset($_POST['settings_data']) ? sanitize_textarea_field(wp_unslash($_POST['settings_data'])) : '';
        
        // Validate the settings data
        if (empty($settings_data)) {
            wp_send_json_error(__('Settings data is empty.', 'text-to-speech-tts'));
            return;
        }
        
        // Decode the settings data
        $settings = json_decode($settings_data, true);
        
        // Check if decoding was successful
        if (json_last_error() !== JSON_ERROR_NONE) {
            wp_send_json_error(__('Invalid JSON data.', 'text-to-speech-tts'));
            return;
        }
        
        // Validate the settings format
        if (!isset($settings['plugin']) || $settings['plugin'] !== 'text-to-speech-tts') {
            wp_send_json_error(__('Invalid settings file. This does not appear to be a Mementor Text to Speech settings export.', 'text-to-speech-tts'));
            return;
        }
        
        // Import the settings
        if (isset($settings['options']) && is_array($settings['options'])) {
            // Sensitive options that must never be imported
            $skip_options = array(
                'mementor_tts_api_key',
                'mementor_tts_s3_secret_key',
                'mementor_tts_s3_access_key',
                'mementor_tts_integrity_secret',
            );

            foreach ($settings['options'] as $option_name => $option_value) {
                // Only allow our own prefixed options
                if (strpos($option_name, 'mementor_tts_') !== 0) {
                    continue;
                }

                // Skip sensitive options
                if (in_array($option_name, $skip_options, true)) {
                    continue;
                }

                update_option($option_name, $option_value);
            }
        }
        
        // Return success
        wp_send_json_success(__('Settings imported successfully.', 'text-to-speech-tts'));
    }
    
    /**
     * Export settings via AJAX
     */
    public function export_settings_ajax() {
        // Check nonce for security
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'mementor_tts_advanced_nonce')) {
            wp_send_json_error(array('message' => __('Security check failed.', 'text-to-speech-tts')));
            return;
        }

        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'text-to-speech-tts')));
            return;
        }
        
        // Check if we have cached settings
        $cache_key = 'mementor_tts_export_settings_cache';
        $options = get_transient($cache_key);
        
        if (false === $options) {
            // No cache exists, so we need to get the settings
            // Instead of directly querying the database, use WordPress core functions
            global $wpdb;
            
            // Get all options from wp_options table
            $all_options = wp_load_alloptions();
            
            // Filter options that start with our prefix
            $options = array();
            $prefix = 'mementor_tts_';
            $prefix_length = strlen($prefix);
            
            foreach ($all_options as $key => $value) {
                // Check if option name starts with our prefix
                if (substr($key, 0, $prefix_length) === $prefix) {
                    // Create an object similar to what get_results would return
                    $option_obj = new stdClass();
                    $option_obj->option_name = $key;
                    $option_obj->option_value = $value;
                    $options[] = $option_obj;
                }
            }
            
            // Cache the results for 1 hour (3600 seconds)
            set_transient($cache_key, $options, 3600);
        }
        
        // Build the settings array
        $settings = array(
            'plugin' => 'text-to-speech-tts',
            'version' => MEMENTOR_TTS_VERSION,
            'exported_at' => current_time('mysql'),
            'options' => array()
        );
        
        // Sensitive options that must never be exported
        $skip_options = array(
            'mementor_tts_api_key',
            'mementor_tts_s3_secret_key',
            'mementor_tts_s3_access_key',
            'mementor_tts_integrity_secret',
        );

        // Add options to the settings array
        foreach ($options as $option) {
            // Skip sensitive options
            if (in_array($option->option_name, $skip_options, true)) {
                continue;
            }
            
            // Add the option to the settings array
            $settings['options'][$option->option_name] = maybe_unserialize($option->option_value);
        }
        
        // Encode the settings as JSON
        $json = wp_json_encode($settings, JSON_PRETTY_PRINT);
        
        // Return the JSON data
        wp_send_json_success(array(
            'message' => __('Settings exported successfully.', 'text-to-speech-tts'),
            'data' => $json,
            'filename' => 'mementor-tts-settings-' . gmdate('Y-m-d') . '.json'
        ));
    }

    /**
     * Handle AJAX request to hide the "Getting Started" block on the Overview page.
     * Dismissal is per-user, stored in user meta so each admin can dismiss independently.
     */
    public function dismiss_getting_started_ajax() {
        if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['nonce'] ) ), 'mementor_tts_dismiss_getting_started' ) ) {
            wp_send_json_error( array( 'message' => __( 'Security check failed.', 'text-to-speech-tts' ) ) );
        }
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'text-to-speech-tts' ) ) );
        }
        update_user_meta( get_current_user_id(), 'mementor_tts_getting_started_dismissed', '1' );
        wp_send_json_success();
    }

    /**
     * Handle AJAX request to reset player settings
     */
    public function reset_player_settings_ajax() {
        // Check nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'mementor_tts_nonce')) {
            wp_send_json_error(array('message' => 'Security check failed.'));
            return;
        }

        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'You do not have permission to perform this action.'));
            return;
        }
        
        // Reset player settings
        $this->reset_player_settings();
        
        // Send success response
        wp_send_json_success(array(
            'message' => 'Player settings reset successfully.'
        ));
    }

    /**
     * Reset player settings to defaults
     */
    private function reset_player_settings() {
        // Get default player label text
        $default_player_label = __('Listen to this article:', 'text-to-speech-tts');

        // Position and Width Settings
        update_option('mementor_tts_player_position', 'after_title_before_excerpt');
        update_option('mementor_tts_player_width', 'full_width');
        
        // Max Width Settings
        update_option('mementor_tts_player_max_width', 100);
        update_option('mementor_tts_player_max_width_unit', '%');
        update_option('mementor_tts_player_alignment', 'center');

        // Label Settings
        update_option('mementor_tts_show_player_label', true);
        update_option('mementor_tts_player_label', $default_player_label);
        update_option('mementor_tts_player_label_position', 'left');
        update_option('mementor_tts_player_label_margin', 10);
        update_option('mementor_tts_player_label_size', 14);
        update_option('mementor_tts_player_label_weight', 400);
        update_option('mementor_tts_player_label_color', '#1d2327');

        // Control Settings
        update_option('mementor_tts_show_info', false);
        update_option('mementor_tts_speed_control', false);
        update_option('mementor_tts_show_download', false);
        update_option('mementor_tts_show_volume', true);
        update_option('mementor_tts_show_play_bg', true);
        update_option('mementor_tts_show_time', true);

        // Color Settings
        update_option('mementor_tts_player_bg_color', '#f6f7fb');
        update_option('mementor_tts_player_border_color', '#dcdcde');
        update_option('mementor_tts_player_text_color', '#0e0e0f');
        update_option('mementor_tts_player_accent_color', '#2271b1');
        update_option('mementor_tts_play_button_bg', '#ffffff');
        update_option('mementor_tts_timer_color', '#1d2327');
        update_option('mementor_tts_progress_bar_default', '#e0e0e0');
        update_option('mementor_tts_progress_bar_playing', '#2271b1');

        // Font & Padding Settings
        update_option('mementor_tts_play_icon_size', 24);
        update_option('mementor_tts_font_size', 14);
        update_option('mementor_tts_player_padding', 12);

        // Margin Settings
        update_option('mementor_tts_player_margin_top', 0);
        update_option('mementor_tts_player_margin_bottom', 0);

        // Clear the dynamic CSS cache
        delete_transient('mementor_tts_dynamic_css');
    }

    /**
     * Handle player reset from POST request
     */
    public function handle_player_reset() {
        // Check if reset was requested
        if (!isset($_POST['mementor_tts_reset_player'])) {
            return;
        }
        
        // Verify nonce
        if (!isset($_POST['mementor_tts_reset_nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['mementor_tts_reset_nonce'])), 'mementor_tts_reset_nonce')) {
            return;
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            return;
        }
        
        // Reset the player settings
        $this->reset_player_settings();
        
        // Add admin notice
        add_settings_error(
            'mementor_tts_player_settings',
            'mementor_tts_player_reset',
            __('Player settings have been reset to defaults.', 'text-to-speech-tts'),
            'updated'
        );
        
        // Redirect to prevent form resubmission
        wp_safe_redirect(admin_url('admin.php?page=text-to-speech-tts-player&reset=success'));
        exit;
    }

    /**
     * Add localization for admin scripts
     */
    public function localize_admin_scripts() {
        wp_localize_script('mementor-tts-admin', 'mementorTTS', array(
            'ajaxurl' => admin_url('admin-ajax.php'),
            'nonce' => wp_create_nonce('mementor_tts_nonce'),
            'isDebug' => (get_option('mementor_tts_debug_mode', '0') === '1'), // Added isDebug flag
            'i18n' => array(
                'resetConfirm' => __('Are you sure you want to reset all player settings to default values?', 'text-to-speech-tts'),
                'resetSuccess' => __('Player settings reset successfully.', 'text-to-speech-tts'),
                'resetError' => __('Failed to reset player settings. Please try again.', 'text-to-speech-tts')
            )
        ));
    }

    /**
     * Register AJAX actions
     */
    public function register_ajax_actions() {
        add_action('wp_ajax_mementor_tts_reset_player', array($this, 'reset_player_settings_ajax'));
    }

    /**
     * Sanitize max width value
     * 
     * @param mixed $value The value to sanitize
     * @return int Sanitized max width value between 0 and 2000
     */
    public function sanitize_max_width($value) {
        $value = absint($value);
        return max(0, min(2000, $value)); // Ensure value is between 0 and 2000
    }

    /**
     * Sanitize width unit
     * 
     * @param string $value The value to sanitize
     * @return string Sanitized width unit (px or %)
     */
    public function sanitize_width_unit($value) {
        $allowed_units = array('px', '%');
        $value = sanitize_text_field($value);
        return in_array($value, $allowed_units) ? $value : '%';
    }

    /**
     * Sanitize alignment value
     * 
     * @param string $value The value to sanitize
     * @return string Sanitized alignment (left, center, or right)
     */
    public function sanitize_alignment($value) {
        $allowed_alignments = array('left', 'center', 'right');
        $value = sanitize_text_field($value);
        return in_array($value, $allowed_alignments) ? $value : 'left';
    }

    /**
     * Sanitize margin value
     * 
     * @param mixed $value The value to sanitize
     * @return int Sanitized margin value between 0 and 100
     */
    public function sanitize_margin($value) {
        $value = absint($value);
        return max(0, min(100, $value)); // Ensure value is between 0 and 100
    }

    /**
     * Prepare text with title and excerpt
     * 
     * @param object $post The post object
     * @return string The prepared text
     */
    private function prepare_text_with_title_and_excerpt($post) {
        $text = '';
        
        // Get post title and settings
        $title = $post->post_title;
        $include_title = get_option('mementor_tts_include_title', true);
        $include_author = get_option('mementor_tts_include_author', false);
        
        // Add title if enabled
        if ($include_title) {
            $text .= $title . ' -- ';
        }

        // Add author if enabled
        if ($include_author) {
            $author_id = $post->post_author;
            $author_name = get_the_author_meta('display_name', $author_id);
            $text .= __('By', 'text-to-speech-tts') . ' ' . $author_name . '. ';
        }
        
        // Try to get excerpt
        $excerpt = '';
        
        // Try WordPress's get_the_excerpt function
        if (function_exists('get_the_excerpt')) {
            $excerpt = get_the_excerpt($post);
        }
        
        // If no excerpt, try to extract from content
        if (empty($excerpt) && !empty($post->post_content)) {
            $excerpt = wp_trim_words(strip_shortcodes($post->post_content), 55, '...');
        }
        
        // Add excerpt if available
        if (!empty($excerpt)) {
            $text .= $excerpt . ' -- ';
        }
        
        // Get the main content
        $content = $post->post_content;
        
        // Strip shortcodes and process for better reading
        $content = strip_shortcodes($content);
        $content = wp_strip_all_tags($content);
        
        // Add the content to the text
        $text .= $content;
        
        // Process text for SSML
        $text = $this->process_text_for_ssml($text);
        
        return $text;
    }

    /**
     * Validate an API key with ElevenLabs
     * 
     * @param string $api_key The API key to validate
     * @return bool Whether the API key is valid
     */
    private function validate_api_key($api_key) {
        // Include required files for API validation
        require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-elevenlabs-api.php';
        
        try {
            // Create a new instance of the API class
            $api = Mementor_TTS_ElevenLabs_API::get_instance();
            
            // Set the API key for validation
            $api->set_api_key($api_key);
            
            // Validate the API key with the service
            $result = $api->validate_api_key();
            
            // Check for a successful result
            if (!is_wp_error($result) && isset($result['status']) && $result['status'] === 'success') {
                // Check if the key has limited permissions
                if (isset($result['data']['missing_user_read']) && $result['data']['missing_user_read']) {
                    update_option('mementor_tts_api_key_permissions', 'limited');
                } else {
                    update_option('mementor_tts_api_key_permissions', 'full');
                }
                // Cache the validation result
                set_transient('mementor_tts_api_key_valid', true, 12 * HOUR_IN_SECONDS);
                return true;
            }
            
            // Check for missing_permissions error - this is still a valid key for TTS operations
            if (is_wp_error($result)) {
                $error_message = $result->get_error_message();
                // Ensure error_message is never null to prevent PHP 8+ deprecation warnings
                $error_message = $error_message ?? '';

                // Store the error for rate limit bypass logic
                set_transient('mementor_tts_last_validation_error', $error_message, 60);

                if (strpos($error_message, 'missing_permissions') !== false) {
                    // The key is valid but has limited permissions - that's OK for our purposes
                    update_option('mementor_tts_api_key_permissions', 'limited');
                    set_transient('mementor_tts_api_key_valid', true, 12 * HOUR_IN_SECONDS);
                    return true;
                }
            }

            return false;
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * Store the API key securely
     * 
     * @param string $api_key The API key to store
     * @return bool Whether the key was stored successfully
     */
    private function store_api_key($api_key) {
        // Sanitize the API key
        $api_key = sanitize_text_field($api_key);
        
        // Initialize encryption class
        require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-encryption.php';
        $encryption = new Mementor_TTS_Encryption();
        
        // Encrypt the API key
        $encrypted_key = $encryption->encrypt($api_key);
        if ($encrypted_key === false) {
            return false;
        }
        
        // Store the encrypted API key in WordPress options
        $result = update_option('mementor_tts_api_key', $encrypted_key);
        
        // Clear any cached API key validation results
        delete_transient('mementor_tts_api_key_valid');
        
        return $result;
    }

    /**
     * Sanitize checkbox values
     * 
     * @param mixed $input The value to sanitize.
     * @return int Returns 1 if checked, 0 if not checked
     */
    public function sanitize_checkbox($input) {
        return ( isset($input) && $input == 1 ) ? 1 : 0;
    }

    /**
     * AJAX handler for loading voices
     */
    public function load_voices_ajax() {
        if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['nonce'] ) ), 'mementor_tts_nonce' ) ) {
            wp_send_json_error( array( 'message' => 'Security check failed', 'voices' => array() ) );
            return;
        }

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => 'Permission denied', 'voices' => array() ) );
            return;
        }

        $api_client = Mementor_TTS_Api_Client::get_instance();
        $result = $api_client->get_voices();

        if ( is_wp_error( $result ) ) {
            wp_send_json_error( array( 'message' => $result->get_error_message(), 'voices' => array() ) );
            return;
        }

        // ElevenLabs response has 'voices' key at top level
        wp_send_json_success( array( 'voices' => $result['voices'] ?? array() ) );
    }

    /**
     * Display the affiliate program page content.
     *
     * @since    1.0.0
     */
    public function display_plugin_admin_affiliate() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/affiliate.php';
    }
    
    /**
     * Get API key via AJAX (for settings page toggle functionality)
     * @since 1.7.4
     */
    public function get_api_key_ajax() {
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'mementor_tts_nonce')) {
            wp_send_json_error(array('message' => __('Security check failed.', 'text-to-speech-tts')));
            return;
        }
        
        // Check if user has permission
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('Permission denied.', 'text-to-speech-tts')));
            return;
        }
        
        // Get encrypted key
        $encrypted_key = get_option('mementor_tts_api_key');
        if (empty($encrypted_key)) {
            wp_send_json_error(array('message' => __('API key not found.', 'text-to-speech-tts')));
            return;
        }
        
        // Decrypt the key
        require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-encryption.php';
        $encryption = new Mementor_TTS_Encryption();
        $decrypted_key = $encryption->decrypt($encrypted_key);
        
        if ($decrypted_key === false) {
            wp_send_json_error(array('message' => __('Failed to decrypt API key. Please re-enter your API key.', 'text-to-speech-tts')));
            return;
        }
        
        // Check if the decrypted key is a valid API key format
        if (strlen($decrypted_key) != 32 && strpos($decrypted_key, 'sk_') !== 0) {
            // The decrypted key doesn't look like a valid API key
            // This suggests the key is corrupted - clear it and ask user to re-enter
            delete_option('mementor_tts_api_key');
            wp_send_json_error(array('message' => __('API key appears to be corrupted. Please re-enter your ElevenLabs API key.', 'text-to-speech-tts')));
            return;
        }
        
        // If a special license is active, don't reveal the mapped ElevenLabs key
        $special_license = Mementor_TTS_Special_License::get_instance();
        if ( $special_license->is_active() ) {
            $tier_label = $special_license->get_tier_label( $special_license->get_active_tier() );
            wp_send_json_success( array(
                'key'                => __( 'Managed by Extra Credits License', 'text-to-speech-tts' ) . ' (' . $tier_label . ')',
                'is_special_license' => true,
            ) );
            return;
        }

        // Send the decrypted key with the expected 'key' property
        wp_send_json_success(array('key' => $decrypted_key));
    }

    /**
     * AJAX handler: Activate a special license key.
     *
     * @since 2.0.7
     */
    public function activate_special_license_ajax() {
        // Verify nonce
        if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'mementor_tts_nonce' ) ) {
            wp_send_json_error( array( 'message' => __( 'Security check failed.', 'text-to-speech-tts' ) ) );
            return;
        }

        // Check permissions
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'text-to-speech-tts' ) ) );
            return;
        }

        $key = isset( $_POST['license_key'] ) ? sanitize_text_field( wp_unslash( $_POST['license_key'] ) ) : '';
        if ( empty( $key ) ) {
            wp_send_json_error( array( 'message' => __( 'Please enter a license key.', 'text-to-speech-tts' ) ) );
            return;
        }

        $special_license = Mementor_TTS_Special_License::get_instance();
        $result = $special_license->activate_key( $key );

        if ( $result === false ) {
            wp_send_json_error( array( 'message' => __( 'Invalid license key. Please check your key and try again.', 'text-to-speech-tts' ) ) );
            return;
        }

        $tier_label = $special_license->get_tier_label( $result['tier'] );
        wp_send_json_success( array(
            'message' => sprintf(
                /* translators: %s: credit tier label */
                __( 'Extra credits license activated! Your monthly quota is now %s characters.', 'text-to-speech-tts' ),
                $tier_label
            ),
            'tier'       => $result['tier'],
            'tier_label' => $tier_label,
        ) );
    }

    /**
     * AJAX handler: Deactivate the special license key.
     *
     * @since 2.0.7
     */
    public function deactivate_special_license_ajax() {
        // Verify nonce
        if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'mementor_tts_nonce' ) ) {
            wp_send_json_error( array( 'message' => __( 'Security check failed.', 'text-to-speech-tts' ) ) );
            return;
        }

        // Check permissions
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'text-to-speech-tts' ) ) );
            return;
        }

        $special_license = Mementor_TTS_Special_License::get_instance();
        $special_license->deactivate();

        wp_send_json_success( array(
            'message' => __( 'Extra credits license deactivated. Your account has been reverted to the standard PRO plan.', 'text-to-speech-tts' ),
        ) );
    }

    /**
     * Sanitize API timeout value
     * 
     * @param mixed $input The input value to sanitize
     * @return int Sanitized timeout value between 5 and 60 seconds
     * @since 1.0.0
     */
    public function sanitize_api_timeout($input) {
        $timeout = absint($input);
        return max(5, min(60, $timeout)); // Ensure value is between 5 and 60
    }
    
    /**
     * Sanitize pause duration value
     * 
     * @param mixed $input The input value to sanitize
     * @return float Sanitized pause duration value between 0 and 10 seconds
     * @since 1.0.0
     */
    public function sanitize_pause_duration($input) {
        if (!ttswp_has_paid_plan()) {
            return 0.6;
        }

        $pause = floatval($input);
        return max(0, min(10, $pause));
    }
    
    /**
     * Sanitize retry attempts value
     * 
     * @param mixed $input The input value to sanitize
     * @return int Sanitized retry attempts value between 0 and 5
     * @since 1.0.0
     */
    public function sanitize_retry_attempts($input) {
        $attempts = absint($input);
        return max(0, min(5, $attempts)); // Ensure value is between 0 and 5
    }

    /**
     * Sanitize an array of post types.
     *
     * Ensures the input is an array and contains only valid, registered post type slugs.
     *
     * @param mixed $input_array The input value.
     * @return array Sanitized array of valid post type slugs.
     */
    public function sanitize_post_types_array($input_array) {
        $sanitized_array = array();
        $default = array('post'); // Default value if input is invalid

        if (!is_array($input_array)) {
            // If input is not an array, return the default
            return $default;
        }

        foreach ($input_array as $post_type) {
            $sanitized_post_type = sanitize_key($post_type);
            // Check if the sanitized key corresponds to a registered post type
            if (post_type_exists($sanitized_post_type)) {
                $sanitized_array[] = $sanitized_post_type;
            }
        }

        // If the sanitized array is empty after filtering, return the default
        return !empty($sanitized_array) ? $sanitized_array : $default;
    }

    /**
     * Get decrypted API key via AJAX
     * @since x.x.x
     */
    public function get_decrypted_api_key_ajax() {
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'mementor_tts_nonce')) {
            wp_send_json_error(array('message' => __('Security check failed.', 'text-to-speech-tts')));
            return;
        }

        // Check if user has permission
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('Permission denied.', 'text-to-speech-tts')));
            return;
        }

        // Get encrypted key
        $encrypted_key = get_option('mementor_tts_api_key');
        if (empty($encrypted_key)) {
             wp_send_json_error(array('message' => __('API key not found.', 'text-to-speech-tts')));
            return;
        }

        // Decrypt the key
        require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-encryption.php';
        $encryption = new Mementor_TTS_Encryption();
        $decrypted_key = $encryption->decrypt($encrypted_key);

        if ($decrypted_key === false) {
             wp_send_json_error(array('message' => __('Failed to decrypt API key.', 'text-to-speech-tts')));
            return;
        }

        // Send the decrypted key
        wp_send_json_success(array('decrypted_key' => $decrypted_key));
    }

    /**
     * Modify the Support Forum submenu link to open in a new window
     */
    public function modify_support_forum_link($submenu_file) {
        global $submenu;
        if (isset($submenu['text-to-speech-tts-settings'])) {
            foreach ($submenu['text-to-speech-tts-settings'] as $key => $item) {
                if ($item[2] === 'https://wordpress.org/support/plugin/text-to-speech-tts/') {
                    $submenu['text-to-speech-tts-settings'][$key][4] = 'sf-with-ul _blank';
                }
            }
        }
        return $submenu_file;
    }

    /**
     * Force "Text to Speech" menu to be highlighted on all plugin pages.
     * Needed because subpages use parent_slug = null to hide from WP submenu.
     */
/**
     * Register the admin menu for the plugin.
     *
     * @since    1.0.0
     */
    public function add_plugin_admin_menu() {
        // Add the main menu item
        add_menu_page(
            __('Text to Speech for WordPress', 'text-to-speech-tts'),
            __('Text to Speech', 'text-to-speech-tts'),
            'manage_options',
            'text-to-speech-tts-settings',
            array($this, 'display_settings_page'),
            'dashicons-controls-volumeon',
            25
        );

        // All subpages are registered under the real parent so WordPress
        // can resolve parent_file for active-menu highlighting.
        // We then strip all but Overview from $submenu to hide them from the WP sidebar.
        $parent = 'text-to-speech-tts-settings';

        // Order matches the sidebar nav in navigation.php
        // Audio
        add_submenu_page($parent, __('Overview', 'text-to-speech-tts'), __('Overview', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-settings', array($this, 'display_settings_page'));
        add_submenu_page($parent, __('Voices', 'text-to-speech-tts'), __('Voices', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-voices', array($this, 'display_voices_page'));
        add_submenu_page($parent, __('Player', 'text-to-speech-tts'), __('Player', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-player', array($this, 'display_player_page'));
        add_submenu_page($parent, __('Content', 'text-to-speech-tts'), __('Content', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-content', array($this, 'display_content_page'));
        // Tools
        add_submenu_page($parent, __('Shortcode Generator', 'text-to-speech-tts'), __('Shortcode Generator', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-generator', array($this, 'display_generator_page'));
        add_submenu_page($parent, __('Word Replacement', 'text-to-speech-tts'), __('Word Replacement', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-word-replacement', array($this, 'display_word_replacement_page'));
        add_submenu_page($parent, __('CSS Selectors', 'text-to-speech-tts'), __('CSS Selectors', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-css-selectors', array($this, 'display_css_selectors_page'));
        add_submenu_page($parent, __('Speech to Text', 'text-to-speech-tts'), __('Speech to Text', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-speech-to-text', array($this, 'display_speech_to_text_page'));
        // Stats
        add_submenu_page($parent, __('My Stats', 'text-to-speech-tts'), __('My Stats', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-my-stats-voices', array($this, 'display_my_stats_voices_page'));
        add_submenu_page($parent, __('Audio Statistics', 'text-to-speech-tts'), __('Audio Statistics', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-statistics', array($this, 'display_statistics_page'));
        add_submenu_page($parent, __('Audio Library', 'text-to-speech-tts'), __('Audio Library', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-audio-library', array($this, 'display_audio_library_page'));
        // Account
        add_submenu_page($parent, __('My Plan & Credits', 'text-to-speech-tts'), __('My Plan & Credits', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-account', array($this, 'display_account_page'));
        if ($this->can_user_see_white_label()) {
            add_submenu_page($parent, __('White Label', 'text-to-speech-tts'), __('White Label', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-white-label', array($this, 'display_white_label_page'));
        }
        if ($this->should_show_menu_item('affiliate')) {
            add_submenu_page($parent, __('Affiliate Program', 'text-to-speech-tts'), __('Affiliate Program', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-affiliate', array($this, 'display_affiliate_page'));
        }
        // System & Help
        add_submenu_page($parent, __('Advanced', 'text-to-speech-tts'), __('Advanced', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-advanced', array($this, 'display_advanced_page'));
        add_submenu_page($parent, __('PRO Support', 'text-to-speech-tts'), __('PRO Support', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-pro-help', array($this, 'display_pro_help_page'));
        add_submenu_page($parent, __('Documentation', 'text-to-speech-tts'), __('Documentation', 'text-to-speech-tts'), 'manage_options', 'text-to-speech-tts-documentation', array($this, 'display_documentation_page'));

        // Hidden from sidebar but still accessible via URL
        add_submenu_page($parent, __('License', 'text-to-speech-tts'), null, 'manage_options', 'text-to-speech-tts-upgrade', array($this, 'display_license_page'));

        // Add settings link to plugins page
        add_filter('plugin_action_links_' . MEMENTOR_TTS_PLUGIN_BASENAME, array($this, 'add_action_links'));

        add_action('admin_notices', array($this, 'display_admin_notices'));
        
        // Add white label plugin name filter if paid plan active
        if (ttswp_has_paid_plan()) {
            add_filter('all_plugins', array($this, 'white_label_plugin_name'));
        }
    }

    /**
     * Add settings action link to the plugins page.
     */
    public function add_action_links($links) {
        $settings_link = array(
            '<a href="' . admin_url('admin.php?page=text-to-speech-tts-settings') . '">' . __('Settings', 'text-to-speech-tts') . '</a>',
        );
        return array_merge($settings_link, $links);
    }

    /**
     * Display the settings page content.
     *
     * @since    1.0.0
     */
    public function display_settings_page() {
        // Always fetch fresh billing data on Overview to keep plan status current.
        // The result is also passed into settings.php so the shared Account Card partial
        // can render the user's plan + credits + next-reset summary above Quick Stats.
        $billing_data = null;
        if ( Mementor_TTS_Connect::get_connection_status() === 'connected' ) {
            $billing_data = $this->get_billing_data( true );
        }
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/settings.php';
    }
    
    /**
     * Display the content page content.
     *
     * @since    1.0.0
     */
    public function display_content_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/content.php';
    }
    
    /**
     * Display the voices page content.
     *
     * @since    1.0.0
     */
    public function display_voices_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/voices.php';
    }
    
    /**
     * Display Player Settings page
     */
    public function display_player_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/player.php';
    }

    /**
     * Display the shortcodes page content.
     *
     * @since    1.0.0
     */
    public function display_shortcodes_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/shortcodes.php';
    }

    /**
     * Display the statistics page content.
     *
     * @since    1.0.0
     */
    public function display_statistics_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/statistics.php';
    }

    /**
     * Display the audio library page content.
     *
     * @since    2.1.3
     */
    public function display_audio_library_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/audio-library.php';
    }

    /**
     * Display the add credits page content.
     *
     * @since    2.1.3
     */
    public function display_add_credits_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/add-credits.php';
    }

    /**
     * Fetch billing data from app.ttswp.com API.
     * Cached in a transient for 5 minutes.
     */
    public function get_billing_data( $force_refresh = false ) {
        $token = Mementor_TTS_Connect::get_site_token();
        if ( empty( $token ) ) {
            return null;
        }

        $cache_key = 'tts_billing_account';

        if ( ! $force_refresh ) {
            $cached = get_transient( $cache_key );
            if ( false !== $cached ) {
                return $cached;
            }
        }

        $api_url = Mementor_TTS_Connect::get_app_url() . '/api/v1/billing/account';

        $response = wp_remote_get( $api_url, array(
            'timeout' => 15,
            'headers' => array(
                'X-Site-Token'     => $token,
                'X-Plugin-Version' => defined( 'MEMENTOR_TTS_VERSION' ) ? MEMENTOR_TTS_VERSION : '',
                'Accept'           => 'application/json',
            ),
        ) );

        if ( is_wp_error( $response ) ) {
            return null;
        }

        $status_code = wp_remote_retrieve_response_code( $response );

        // Auto-disconnect if token is invalid (site deleted or token revoked)
        if ( 401 === $status_code ) {
            Mementor_TTS_Connect::disconnect();
            delete_transient( 'tts_billing_account' );
            delete_transient( 'tts_credits_balance' );
            delete_transient( 'tts_usage_stats' );
            return null;
        }

        if ( 200 !== $status_code ) {
            return null;
        }

        $data = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( ! is_array( $data ) ) {
            return null;
        }

        set_transient( $cache_key, $data, DAY_IN_SECONDS );

        // Also update credit balance transient for the low-credit banner
        if ( isset( $data['credits']['balance'] ) ) {
            set_transient( 'tts_credits_balance', (int) $data['credits']['balance'], DAY_IN_SECONDS );
        }

        // Persist plan slug for feature gating (e.g. White Label = Agency only)
        if ( isset( $data['plan']['slug'] ) ) {
            update_option( 'tts_site_plan_slug', sanitize_text_field( $data['plan']['slug'] ), false );
        }

        // Persist plan status for feature gating
        if ( isset( $data['plan']['source'] ) ) {
            $source = $data['plan']['source'];
            $plan_status = ( $source === 'site' || $source === 'agency' ) ? 'active' : 'inactive';
            update_option( 'tts_site_plan_status', $plan_status, false );
        }

        // Persist BYOK status
        if ( isset( $data['site']['has_elevenlabs_key'] ) ) {
            update_option( 'tts_site_has_byok', $data['site']['has_elevenlabs_key'] ? '1' : '0', false );
        }

        if ( isset( $data['site']['force_player_info'] ) ) {
            update_option( 'tts_site_info_2', $data['site']['force_player_info'] ? '1' : '0', false );
        }

        // Persist capability flags from SaaS (e.g. cloud_storage=false on BYOK Pro forces local audio).
        if ( isset( $data['plan']['capabilities'] ) && is_array( $data['plan']['capabilities'] ) ) {
            $caps = $data['plan']['capabilities'];
            update_option( 'tts_plan_cloud_storage', ! empty( $caps['cloud_storage'] ) ? '1' : '0', false );
            update_option( 'tts_plan_requires_byok', ! empty( $caps['requires_byok'] ) ? '1' : '0', false );
        }

        return $data;
    }

    /**
     * Display the Account & Billing page.
     */
    public function display_account_page() {
        $force_refresh = isset( $_GET['refresh'] ) && '1' === $_GET['refresh'];
        $billing_data  = $this->get_billing_data( $force_refresh );

        include MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/account.php';
    }

    /**
     * AJAX: Proxy subscription checkout request to app.ttswp.com.
     */
    public function checkout_subscription_ajax() {
        if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'mementor_tts_nonce' ) ) {
            wp_send_json_error( array( 'message' => 'Security check failed.' ) );
        }

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => 'Insufficient permissions.' ) );
        }

        $token = Mementor_TTS_Connect::get_site_token();
        if ( empty( $token ) ) {
            wp_send_json_error( array( 'message' => 'Site not connected.' ) );
        }

        $tier_id  = isset( $_POST['tier_id'] ) ? absint( $_POST['tier_id'] ) : 0;
        $interval = isset( $_POST['interval'] ) ? sanitize_text_field( wp_unslash( $_POST['interval'] ) ) : 'yearly';

        $api_url  = Mementor_TTS_Connect::get_app_url() . '/api/v1/billing/checkout/subscription';

        $response = wp_remote_post( $api_url, array(
            'timeout' => 30,
            'headers' => array(
                'X-Site-Token'     => $token,
                'X-Plugin-Version' => defined( 'MEMENTOR_TTS_VERSION' ) ? MEMENTOR_TTS_VERSION : '',
                'Accept'           => 'application/json',
                'Content-Type'     => 'application/json',
            ),
            'body' => wp_json_encode( array(
                'tier_id'  => $tier_id,
                'interval' => $interval,
            ) ),
        ) );

        if ( is_wp_error( $response ) ) {
            wp_send_json_error( array( 'message' => 'API request failed.' ) );
        }

        $code = wp_remote_retrieve_response_code( $response );
        $body = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( 200 !== $code ) {
            wp_send_json_error( array( 'message' => $body['error'] ?? 'Checkout failed.' ) );
        }

        // Clear billing cache so next page load shows updated data
        delete_transient( 'tts_billing_account' );

        wp_send_json_success( $body );
    }

    /**
     * AJAX: Proxy credit top-up checkout request to app.ttswp.com.
     */
    public function checkout_credits_ajax() {
        if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'mementor_tts_nonce' ) ) {
            wp_send_json_error( array( 'message' => 'Security check failed.' ) );
        }

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => 'Insufficient permissions.' ) );
        }

        $token = Mementor_TTS_Connect::get_site_token();
        if ( empty( $token ) ) {
            wp_send_json_error( array( 'message' => 'Site not connected.' ) );
        }

        $package_id = isset( $_POST['package_id'] ) ? absint( $_POST['package_id'] ) : 0;

        $api_url = Mementor_TTS_Connect::get_app_url() . '/api/v1/billing/checkout/credits';

        $response = wp_remote_post( $api_url, array(
            'timeout' => 30,
            'headers' => array(
                'X-Site-Token'     => $token,
                'X-Plugin-Version' => defined( 'MEMENTOR_TTS_VERSION' ) ? MEMENTOR_TTS_VERSION : '',
                'Accept'           => 'application/json',
                'Content-Type'     => 'application/json',
            ),
            'body' => wp_json_encode( array(
                'package_id' => $package_id,
            ) ),
        ) );

        if ( is_wp_error( $response ) ) {
            wp_send_json_error( array( 'message' => 'API request failed.' ) );
        }

        $code = wp_remote_retrieve_response_code( $response );
        $body = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( 200 !== $code ) {
            wp_send_json_error( array( 'message' => $body['error'] ?? 'Checkout failed.' ) );
        }

        delete_transient( 'tts_billing_account' );

        wp_send_json_success( $body );
    }

    /**
     * Show a global admin notice when TTS credits are running low.
     * Thresholds: < 2500 = warning (yellow), < 500 = critical (red).
     */
    public function display_low_credit_banner() {
        // Only for admins
        if ( ! current_user_can( 'manage_options' ) ) {
            return;
        }

        // Only if connected
        if ( 'connected' !== Mementor_TTS_Connect::get_connection_status() ) {
            return;
        }

        // Read cached credit balance (set by billing/account fetch or synthesize responses)
        $balance = get_transient( 'tts_credits_balance' );
        if ( false === $balance ) {
            return; // No data — don't show false alarms
        }

        $balance = (int) $balance;

        if ( $balance >= 2500 ) {
            return; // Credits are fine
        }

        // Check if dismissed within last 24 hours
        $dismissed_at = (int) get_user_meta( get_current_user_id(), 'tts_banner_dismissed_at', true );
        if ( $dismissed_at && ( time() - $dismissed_at ) < DAY_IN_SECONDS ) {
            return;
        }

        $is_critical  = $balance < 500;
        $topup_url    = admin_url( 'admin.php?page=text-to-speech-tts-account#top-ups' );
        $upgrade_url  = admin_url( 'admin.php?page=text-to-speech-tts-account#upgrade' );
        $nonce        = wp_create_nonce( 'mementor_tts_nonce' );

        $message = ( 0 === $balance )
            ? __( 'Your credits are used up.', 'text-to-speech-tts' )
            /* translators: %s: Remaining credit balance formatted with thousands separators. */
            : sprintf( __( 'Your credits are running low (%s remaining).', 'text-to-speech-tts' ), number_format( $balance ) );

        $border_color = '#6B45D4';
        ?>
        <style>
        .tts-low-credit-banner {
            display: flex;
            align-items: center;
            gap: 16px;
            padding: 14px 20px;
            margin: 5px 0 15px;
            background: #fff;
            border: 1px solid #e5e7eb;
            border-left: 4px solid <?php echo $border_color; ?>;
            border-radius: 6px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.06);
        }
        .tts-low-credit-banner .tts-banner-logo {
            display: flex;
            align-items: center;
            gap: 8px;
            font-weight: 600;
            font-size: 13px;
            color: #1a1a1a;
            white-space: nowrap;
            flex-shrink: 0;
        }
        .tts-low-credit-banner .tts-banner-logo svg {
            flex-shrink: 0;
        }
        .tts-low-credit-banner .tts-banner-text {
            flex: 1;
            font-size: 13px;
            color: #434048;
            line-height: 1.4;
        }
        .tts-low-credit-banner .tts-banner-text strong {
            color: #1a1a1a;
        }
        .tts-low-credit-banner .tts-banner-actions {
            display: flex;
            align-items: center;
            gap: 8px;
            flex-shrink: 0;
        }
        .tts-banner-btn-primary {
            display: inline-block;
            background: #6B45D4;
            color: #fff !important;
            font-size: 12px;
            font-weight: 600;
            padding: 6px 14px;
            border-radius: 6px;
            text-decoration: none;
            white-space: nowrap;
        }
        .tts-banner-btn-primary:hover {
            background: #5435B0;
            color: #fff !important;
        }
        .tts-banner-btn-secondary {
            display: inline-block;
            background: #fff;
            color: #6B45D4 !important;
            font-size: 12px;
            font-weight: 600;
            padding: 5px 13px;
            border: 1px solid #6B45D4;
            border-radius: 6px;
            text-decoration: none;
            white-space: nowrap;
        }
        .tts-banner-btn-secondary:hover {
            background: #f5f3ff;
            color: #6B45D4 !important;
        }
        .tts-banner-dismiss {
            background: none;
            border: none;
            color: #999;
            font-size: 24px;
            line-height: 0;
            cursor: pointer;
            padding: 0 0 0 7px;
            flex-shrink: 0;
            margin-top: -3px;
        }
        .tts-banner-dismiss:hover {
            color: #1a1a1a;
        }
        </style>
        <div class="mementor-tts-notice tts-low-credit-banner">
            <span class="tts-banner-logo">
                <img src="<?php echo esc_url( MEMENTOR_TTS_PLUGIN_URL . 'admin/images/ttswp-icon.svg' ); ?>" alt="" width="32" height="32">
                Text to Speech
            </span>
            <span class="tts-banner-text">
                <?php echo esc_html( $message ); ?>
                <?php esc_html_e( 'Top up or upgrade to keep generating audio.', 'text-to-speech-tts' ); ?>
            </span>
            <span class="tts-banner-actions">
                <a href="<?php echo esc_url( $topup_url ); ?>" class="tts-banner-btn-primary"><?php esc_html_e( 'Top up now', 'text-to-speech-tts' ); ?></a>
                <a href="<?php echo esc_url( $upgrade_url ); ?>" class="tts-banner-btn-secondary"><?php esc_html_e( 'Upgrade plan', 'text-to-speech-tts' ); ?></a>
                <button type="button" class="tts-banner-dismiss tts-dismiss-banner" data-nonce="<?php echo esc_attr( $nonce ); ?>" title="<?php esc_attr_e( 'Dismiss', 'text-to-speech-tts' ); ?>">&times;</button>
            </span>
        </div>
        <script>
        jQuery(function($){
            $('.tts-dismiss-banner').on('click', function(){
                var $banner = $(this).closest('.tts-low-credit-banner');
                $.post(ajaxurl, {
                    action: 'tts_dismiss_credit_banner',
                    nonce: $(this).data('nonce')
                });
                $banner.fadeOut(200);
            });
        });
        </script>
        <?php
    }

    /**
     * Show one-time notice when PRO plugin was auto-deactivated.
     */
    public function display_pro_deactivated_notice() {
        if ( ! get_option( 'tts_legacy_pro_deactivated' ) ) {
            return;
        }

        // Show once, then clear
        delete_option( 'tts_legacy_pro_deactivated' );

        ?>
        <div class="notice notice-info is-dismissible mementor-tts-notice">
            <p>
                <strong><?php esc_html_e( 'Text to Speech PRO has been deactivated.', 'text-to-speech-tts' ); ?></strong>
                <?php esc_html_e( 'Your plan is now managed via TTSWP.com. All PRO features are included based on your plan level. You no longer need this plugin — it can be safely deleted.', 'text-to-speech-tts' ); ?>
            </p>
        </div>
        <?php
    }

    /**
     * AJAX: Dismiss the low-credit banner for 24 hours.
     */
    public function dismiss_credit_banner_ajax() {
        if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'mementor_tts_nonce' ) ) {
            wp_send_json_error( array( 'message' => 'Security check failed.' ) );
        }

        update_user_meta( get_current_user_id(), 'tts_banner_dismissed_at', time() );
        wp_send_json_success();
    }

    /**
     * Display the documentation page content.
     *
     * @since    2.1.3
     */
    public function display_documentation_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/documentation.php';
    }

    /**
     * Display the license page content.
     *
     * @since    1.0.0
     */
    public function display_license_page() {
        $billing_data = $this->get_billing_data();
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/license.php';
    }

    /**
     * Display the affiliate page content.
     *
     * @since    1.0.0
     */
    public function display_affiliate_page() {
        // Check if user should have access
        if (!$this->should_show_menu_item('affiliate')) {
            wp_die(__('You do not have permission to access this page.', 'text-to-speech-tts'));
        }
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/affiliate.php';
    }

    /**
     * Display the white label page content.
     *
     * @since    1.0.0
     */
    public function display_white_label_page() {
        // Double-check visibility permissions in case someone accesses the URL directly
        if (!$this->can_user_see_white_label()) {
            wp_die(__('You do not have permission to access this page.', 'text-to-speech-tts'));
        }
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/white-label.php';
    }

    /**
     * Display the advanced page content.
     *
     * @since    1.0.0
     */
    public function display_advanced_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/advanced.php';
    }
    
    /**
     * Display the generator page content.
     *
     * @since    1.0.0
     */
    public function display_generator_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/generator.php';
    }

    public function display_word_replacement_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/word-replacement.php';
    }

    public function display_css_selectors_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/css-selectors.php';
    }

    /**
     * Display the speech to text page content.
     */
    public function display_speech_to_text_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/speech-to-text.php';
    }

    /**
     * Display the my stats & voices page content.
     *
     * @since    1.0.0
     */
    public function display_my_stats_voices_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/my-stats-voices.php';
    }
    
    /**
     * Display the PRO help page content.
     *
     * @since    1.0.0
     */
    public function display_pro_help_page() {
        include_once MEMENTOR_TTS_PLUGIN_DIR . 'admin/partials/pages/pro-help.php';
    }
    
    /**
     * AJAX handler for getting ElevenLabs voices
     * 
     * @since    1.0.0
     * @return   void
     */
    public function get_elevenlabs_voices_ajax() {
        // Check nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'mementor_tts_get_elevenlabs_voices')) {
            wp_send_json_error(array('message' => __('Security check failed.', 'text-to-speech-tts')));
        }

        // Don't hit the ElevenLabs API when the site isn't connected to TTSWP — the voices list is served by the SaaS.
        if ( 'connected' !== Mementor_TTS_Connect::get_connection_status() ) {
            wp_send_json_error( array( 'message' => __( 'Connect your site to TTSWP to load voices.', 'text-to-speech-tts' ) ) );
            return;
        }

        // Get ElevenLabs API instance using the singleton pattern
        $elevenlabs_api = Mementor_TTS_ElevenLabs_API::get_instance();
        
        // Check if PRO is active and should be used
        $use_pro = false;
        $original_api_key = null;
        // PRO API key logic removed — all synthesis now goes through SaaS API client
        
        // Get voices from ElevenLabs API with cache busting to ensure fresh data
        $cache_buster = time(); // Add timestamp as cache buster
        $response = $elevenlabs_api->get_voices(null, $cache_buster);
        
        // Check if response is a WP_Error object
        if (is_wp_error($response)) {
            // Restore original API key if we changed it
            if ($use_pro && $original_api_key !== null) {
                $elevenlabs_api->set_api_key($original_api_key);
            }
            wp_send_json_error(array('message' => $response->get_error_message()));
            return;
        }
        
        // Check if response is successful
        if (!isset($response['success']) || $response['success'] !== true) {
            // Restore original API key if we changed it
            if ($use_pro && $original_api_key !== null) {
                $elevenlabs_api->set_api_key($original_api_key);
            }
            $error_message = isset($response['error']) ? $response['error'] : __('Unknown error from ElevenLabs API.', 'text-to-speech-tts');
            wp_send_json_error(array('message' => $error_message));
            return;
        }
        
        // Process the response
        if (isset($response['voices']) && is_array($response['voices'])) {
            // Format the voices data for the frontend
            $voices = array();
            $professional_voices = array();
            
            foreach ($response['voices'] as $voice) {
                if (!isset($voice['voice_id']) || !isset($voice['name'])) {
                    continue; // Skip invalid voice entries
                }
                
                // Skip premade voices, only include professional or custom voices
                if (isset($voice['category']) && $voice['category'] === 'premade') {
                    continue;
                }
                
                // Add voice to the professional/custom voices array
                $professional_voices[] = array(
                    'voice_id' => $voice['voice_id'],
                    'name' => $voice['name'],
                    'category' => isset($voice['category']) ? $voice['category'] : __('Custom', 'text-to-speech-tts'),
                    'can_be_deleted' => !isset($voice['category']) || $voice['category'] !== 'premade',
                    'preview_url' => isset($voice['preview_url']) ? $voice['preview_url'] : '',
                    'labels' => isset($voice['labels']) ? $voice['labels'] : array(),
                    'description' => isset($voice['description']) ? $voice['description'] : '',
                    'language' => isset($voice['language']) ? $voice['language'] : ''
                );
            }
            
            // Get voice limits from user info
            $voice_limit = 3; // Default free plan limit
            $voice_count = count($professional_voices); // Count of professional/custom voices
            
            // Try to get actual limits from API if available
            $user_info_response = $elevenlabs_api->get_user_info();
            if (!is_wp_error($user_info_response) && isset($user_info_response['data'])) {
                $user_data = json_decode($user_info_response['data'], true);
                if (json_last_error() === JSON_ERROR_NONE && is_array($user_data)) {
                    if (isset($user_data['voice_limit'])) {
                        $voice_limit = intval($user_data['voice_limit']);
                    }
                    if (isset($user_data['subscription']) && isset($user_data['subscription']['character_count'])) {
                        $character_limit = intval($user_data['subscription']['character_count']);
                        $character_count = isset($user_data['character_count']) ? intval($user_data['character_count']) : 0;
                    }
                }
            }
            
            // Restore original API key if we changed it
            if ($use_pro && $original_api_key !== null) {
                $elevenlabs_api->set_api_key($original_api_key);
            }
            
            wp_send_json_success(array(
                'voices' => $professional_voices,
                'voice_count' => $voice_count,
                'voice_limit' => $voice_limit,
                'character_count' => isset($character_count) ? $character_count : 0,
                'character_limit' => isset($character_limit) ? $character_limit : 0
            ));
        } else {
            // Restore original API key if we changed it
            if ($use_pro && $original_api_key !== null) {
                $elevenlabs_api->set_api_key($original_api_key);
            }
            
            wp_send_json_error(array('message' => __('Invalid response from ElevenLabs API.', 'text-to-speech-tts')));
        }
    }
    
    /**
     * AJAX handler for deleting an ElevenLabs voice
     * 
     * @since    1.0.0
     * @return   void
     */
    public function delete_elevenlabs_voice_ajax() {
        // Check nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'mementor_tts_delete_elevenlabs_voice')) {
            wp_send_json_error(array('message' => __('Security check failed.', 'text-to-speech-tts')));
        }
        
        // Check if voice_id is provided
        if (!isset($_POST['voice_id']) || empty($_POST['voice_id'])) {
            wp_send_json_error(array('message' => __('Voice ID is required.', 'text-to-speech-tts')));
        }
        
        $voice_id = sanitize_text_field($_POST['voice_id']);
        
        // Get ElevenLabs API instance
        $elevenlabs_api = Mementor_TTS_ElevenLabs_API::get_instance();
        
        // Delete voice from ElevenLabs API
        $response = $elevenlabs_api->delete_voice($voice_id);
        
        if (is_wp_error($response)) {
            wp_send_json_error(array('message' => $response->get_error_message()));
        }
        
        wp_send_json_success(array('message' => __('Voice deleted successfully.', 'text-to-speech-tts')));
    }

    /**
     * AJAX handler for getting ElevenLabs API usage statistics
     */
    public function get_elevenlabs_stats_ajax() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'mementor_tts_get_elevenlabs_stats')) {
            wp_send_json_error(array('message' => __('Security check failed.', 'text-to-speech-tts')));
        }
        
        $elevenlabs_api = Mementor_TTS_ElevenLabs_API::get_instance();
        $response = $elevenlabs_api->get_user_info();
        
        if (is_wp_error($response)) {
            wp_send_json_error(array('message' => $response->get_error_message()));
        }
        
        // Parse the response data
        $data = json_decode($response['data'], true);
        if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) {
            wp_send_json_error(array('message' => __('Invalid response from ElevenLabs API.', 'text-to-speech-tts')));
        }
        
        // Format the subscription data for the frontend
        $subscription = array(
            'tier' => isset($data['tier']) ? $data['tier'] : 'unknown',
            'character_count' => isset($data['character_count']) ? $data['character_count'] : 0,
            'character_limit' => isset($data['character_limit']) ? $data['character_limit'] : 0,
            'can_extend_character_limit' => isset($data['can_extend_character_limit']) ? $data['can_extend_character_limit'] : false,
            'allowed_to_extend_character_limit' => isset($data['allowed_to_extend_character_limit']) ? $data['allowed_to_extend_character_limit'] : false,
            'next_character_count_reset_unix' => isset($data['next_character_count_reset_unix']) ? $data['next_character_count_reset_unix'] : 0,
            'voice_limit' => isset($data['voice_limit']) ? $data['voice_limit'] : 0,
            'professional_voice_limit' => isset($data['professional_voice_limit']) ? $data['professional_voice_limit'] : 0,
            'can_extend_voice_limit' => isset($data['can_extend_voice_limit']) ? $data['can_extend_voice_limit'] : false,
            'can_use_instant_voice_cloning' => isset($data['can_use_instant_voice_cloning']) ? $data['can_use_instant_voice_cloning'] : false,
            'can_use_professional_voice_cloning' => isset($data['can_use_professional_voice_cloning']) ? $data['can_use_professional_voice_cloning'] : false,
            'currency' => isset($data['currency']) ? $data['currency'] : 'USD',
        );
        
        wp_send_json_success(array('subscription' => $subscription));
    }
    
    /**
     * Sanitize custom voice ID and update main voice option
     * 
     * @param string $value The custom voice ID value
     * @return string Sanitized value
     */
    public function sanitize_custom_voice_id($value) {
        $sanitized = sanitize_text_field($value);
        
        // Check if paid plan is active
        if (ttswp_has_paid_plan()) {
            if (!empty($sanitized)) {
                // Save the current voice as a backup
                $current_voice = get_option('mementor_tts_voice', '');
                if ($current_voice !== $sanitized) {
                    update_option('mementor_tts_voice_backup', $current_voice);
                    // Update the main voice option with the custom voice ID
                    update_option('mementor_tts_voice', $sanitized);
                    error_log('[TTS Admin] Updated main voice option to custom voice ID: ' . $sanitized);
                }
            } else {
                // Only restore backup if custom voice ID was previously set (being cleared)
                $previous_custom_id = get_option('mementor_tts_custom_voice_id', '');
                if (!empty($previous_custom_id)) {
                    $backup_voice = get_option('mementor_tts_voice_backup', '');
                    if (!empty($backup_voice)) {
                        update_option('mementor_tts_voice', $backup_voice);
                    }
                }
            }
        }
        
        return $sanitized;
    }

    /**
     * Sanitize language-voice mappings
     *
     * @param mixed $input The input to sanitize
     * @return array Sanitized mappings
     */
    public function sanitize_language_voice_mappings($input) {
        if (!is_array($input)) {
            return array();
        }
        $sanitized = array();
        foreach ($input as $lang_code => $mapping) {
            $lang_code = preg_replace('/[^a-z]/', '', strtolower($lang_code));
            if (strlen($lang_code) < 2 || strlen($lang_code) > 3) {
                continue;
            }
            $sanitized[$lang_code] = array(
                'voice_id'   => isset($mapping['voice_id']) ? sanitize_text_field($mapping['voice_id']) : '',
                'voice_name' => isset($mapping['voice_name']) ? sanitize_text_field($mapping['voice_name']) : '',
            );
        }
        return $sanitized;
    }

    /**
     * Auto-generate audio when a post is published
     * 
     * @param string $new_status New post status
     * @param string $old_status Old post status  
     * @param WP_Post $post Post object
     */
    public function auto_generate_on_publish($new_status, $old_status, $post) {
        error_log('[TTS Auto-Gen] Hook called - new_status: ' . $new_status . ', old_status: ' . $old_status . ', post_id: ' . $post->ID . ', post_type: ' . $post->post_type);
        
        // Only proceed if post is being published
        if ($new_status !== 'publish' || $old_status === 'publish') {
            error_log('[TTS Auto-Gen] Skipping - not a new publish (new: ' . $new_status . ', old: ' . $old_status . ')');
            return;
        }

        // Check if auto-generation is enabled
        $auto_gen_enabled = get_option('mementor_tts_auto_generate_on_publish', false);
        error_log('[TTS Auto-Gen] Auto-generation enabled: ' . ($auto_gen_enabled ? 'YES' : 'NO'));
        if (!$auto_gen_enabled) {
            return;
        }

        // PRO check is already handled in the UI toggle - no need to check again here

        // Check if post type is enabled for TTS
        $enabled_post_types = get_option('mementor_tts_post_types', array('post'));
        error_log('[TTS Auto-Gen] Enabled post types: ' . implode(', ', $enabled_post_types) . ' | Current post type: ' . $post->post_type);
        if (!in_array($post->post_type, $enabled_post_types)) {
            return;
        }

        // Check if post already has audio
        global $wpdb;
        
        // Ensure tables exist (in case plugin wasn't properly activated)
        $this->ensure_tables_exist();
        
        // Double-check table exists before querying
        $table_name = $wpdb->prefix . 'mementor_tts_speeches';
        if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)) != $table_name) {
            error_log('[TTS Auto-Gen] Table does not exist, creating it now');

            // Try to create the table directly
            $charset_collate = $wpdb->get_charset_collate();
            $sql = "CREATE TABLE IF NOT EXISTS $table_name (
                id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
                post_id bigint(20) unsigned NOT NULL,
                audio_url text NOT NULL,
                voice_id varchar(100) NOT NULL,
                model_id varchar(100) NOT NULL,
                language_code varchar(10) NOT NULL DEFAULT 'en',
                character_count int unsigned NOT NULL DEFAULT 0,
                created_at datetime DEFAULT CURRENT_TIMESTAMP,
                updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                PRIMARY KEY (id),
                KEY post_id (post_id),
                KEY language_code (language_code)
            ) $charset_collate;";

            $wpdb->query($sql);

            // Check again
            if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)) != $table_name) {
                error_log('[TTS Auto-Gen] Failed to create table - aborting');
                return;
            }
            error_log('[TTS Auto-Gen] Table created successfully');
        }
        
        $existing_audio = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->prefix}mementor_tts_speeches WHERE post_id = %d",
            $post->ID
        ));

        error_log('[TTS Auto-Gen] Existing audio count: ' . $existing_audio);
        if ($existing_audio > 0) {
            error_log('[TTS Auto-Gen] Skipping - post already has audio');
            return; // Post already has audio
        }

        // Schedule audio generation to run after the current request completes
        error_log('[TTS Auto-Gen] All checks passed - scheduling audio generation for post: ' . $post->ID);
        
        // For REST API requests (Gutenberg), always use cron to avoid interfering with response
        if (defined('REST_REQUEST') && REST_REQUEST) {
            wp_schedule_single_event(time() + 2, 'mementor_tts_generate_audio_cron', array($post->ID));
            error_log('[TTS Auto-Gen] Scheduled via cron for REST API request - post: ' . $post->ID);
        } else {
            // Use shutdown hook for regular requests
            add_action('shutdown', function() use ($post) {
                error_log('[TTS Auto-Gen] Running audio generation in shutdown hook for post: ' . $post->ID);
                $this->generate_audio_for_post($post->ID);
            });
        }
    }

    /**
     * Generate audio for a specific post
     * 
     * @param int $post_id Post ID
     */
    public function generate_audio_for_post($post_id) {
        // Include required classes
        if (!class_exists('Mementor_TTS_Processor')) {
            require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-processor.php';
        }

        try {
            // Get post content
            $post = get_post($post_id);
            if (!$post) {
                error_log('[TTS Auto-Gen] Post not found: ' . $post_id);
                return;
            }

            // Get current voice setting - check language-specific voice based on post language
            $voice_id = get_option('mementor_tts_voice', 'EXAVITQu4vr4xnSDxMaL');
            if (class_exists('Mementor_TTS_I18n_Helper')) {
                $lang_voice_id = Mementor_TTS_I18n_Helper::get_voice_for_post($post_id);
                if (!empty($lang_voice_id)) {
                    $voice_id = $lang_voice_id;
                }
            }

            // For WooCommerce products, use product-specific assembly
            if ($post->post_type === 'product' && function_exists('wc_get_product')) {
                if (!class_exists('Mementor_TTS_Ajax')) {
                    require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-ajax.php';
                }
                $ajax = Mementor_TTS_Ajax::get_instance();
                $complete_text = $ajax->assemble_product_text($post_id);

                if (empty($complete_text)) {
                    error_log('[TTS Auto-Gen] No product content to generate for post: ' . $post_id);
                    return;
                }

                // Product text is already clean — send directly to processor
                $processor = Mementor_TTS_Processor::get_instance();
                $result = $processor->generate_audio($complete_text, $post_id, $voice_id);

                if ($result && !is_wp_error($result)) {
                    error_log('[TTS Auto-Gen] Successfully generated product audio for post: ' . $post_id);
                } else {
                    error_log('[TTS Auto-Gen] Failed to generate product audio for post: ' . $post_id);
                    if (is_wp_error($result)) {
                        error_log('[TTS Auto-Gen] Error: ' . $result->get_error_message());
                    }
                }
                return;
            }

            // Build complete text content using the same logic as the AJAX handler
            $speech_parts = array();
            
            // Add title if enabled
            if (get_option('mementor_tts_include_title', 1)) {
                $speech_parts[] = $post->post_title;
            }

            // Add author if enabled
            if (get_option('mementor_tts_include_author', 0) && $post->post_type === 'post') {
                $author = get_the_author_meta('display_name', $post->post_author);
                $speech_parts[] = sprintf(__('By %s', 'text-to-speech-tts'), $author);
            }

            // Add date if enabled
            if (get_option('mementor_tts_include_date', 0) && $post->post_type === 'post') {
                $speech_parts[] = sprintf(__('Published on %s', 'text-to-speech-tts'), 
                    get_the_date('', $post));
            }

            // Add custom intro text
            $intro_text = get_option('mementor_tts_text_before', '');
            if (!empty($intro_text)) {
                $speech_parts[] = $intro_text;
            }

            // Add excerpt if enabled
            if (get_option('mementor_tts_include_excerpt', 0) && !empty($post->post_excerpt)) {
                $speech_parts[] = $post->post_excerpt;
            }

            // Add main content
            error_log('[TTS Auto-Gen] Raw post content length: ' . strlen($post->post_content));
            error_log('[TTS Auto-Gen] Raw post content preview: ' . substr($post->post_content, 0, 200) . '...');
            $speech_parts[] = $post->post_content;

            // Add custom outro text
            $outro_text = get_option('mementor_tts_text_after', '');
            if (!empty($outro_text)) {
                $speech_parts[] = $outro_text;
            }

            // Process content to remove HTML tags and apply proper formatting
            $processed_parts = array();
            foreach ($speech_parts as $part) {
                // Apply content filters if it's the main content
                if ($part === $post->post_content) {
                    // Render via helper so Fusion Builder / Avada emit the full
                    // post body in auto-generation context, not just the teaser.
                    $part = ttswp_render_content_full($part, $post_id);
                    
                    // Add pauses after list items, line breaks, and headings before extraction
                    $part = preg_replace('/<\/li>\s*/i', '</li> -- ', $part);
                    $part = preg_replace('/<br\s*\/?>\s*/i', '<br> -- ', $part);
                    if (function_exists('ttswp_inject_speech_breaks')) {
                        $part = ttswp_inject_speech_breaks($part);
                    }
                    
                    // Use processor to extract clean content if available
                    if (class_exists('Mementor_TTS_Processor')) {
                        $temp_processor = Mementor_TTS_Processor::get_instance();
                        if (method_exists($temp_processor, 'get_content_from_html')) {
                            error_log('[TTS Auto-Gen] Before get_content_from_html: ' . strlen($part) . ' chars');
                            $part = $temp_processor->get_content_from_html($part);
                            error_log('[TTS Auto-Gen] After get_content_from_html: ' . strlen($part) . ' chars');
                            error_log('[TTS Auto-Gen] Extracted content preview: ' . substr($part, 0, 200) . '...');
                        } else {
                            // Fallback to stripping tags
                            $part = wp_strip_all_tags($part);
                        }
                    } else {
                        // Fallback to stripping tags
                        $part = wp_strip_all_tags($part);
                    }
                } else {
                    // For other parts (title, author, date, etc.), just strip tags
                    $part = wp_strip_all_tags($part);
                }
                
                // Clean up the text
                $part = str_replace('&nbsp;', ' ', $part);
                $part = preg_replace('/\s+/', ' ', $part);
                $part = trim($part);
                
                if (!empty($part)) {
                    $processed_parts[] = $part;
                }
            }
            
            // Combine all parts with double dash for pauses between sections
            $complete_text = implode(' -- ', $processed_parts);
            
            if (empty($complete_text)) {
                error_log('[TTS Auto-Gen] No content to generate for post: ' . $post_id);
                return;
            }

            // Use the processor's generate_audio method directly
            $processor = Mementor_TTS_Processor::get_instance();
            $result = $processor->generate_audio($complete_text, $post_id, $voice_id);

            if ($result && !is_wp_error($result)) {
                error_log('[TTS Auto-Gen] Successfully generated audio for post: ' . $post_id);
            } else {
                error_log('[TTS Auto-Gen] Failed to generate audio for post: ' . $post_id);
                if (is_wp_error($result)) {
                    error_log('[TTS Auto-Gen] Error: ' . $result->get_error_message());
                }
            }

        } catch (Exception $e) {
            error_log('[TTS Auto-Gen] Exception generating audio for post ' . $post_id . ': ' . $e->getMessage());
        }
    }

    /**
     * Ensure required tables exist
     */
    private function ensure_tables_exist() {
        // Include WordPress upgrade functionality for dbDelta
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        
        if (!class_exists('Mementor_TTS_Speech_Builder')) {
            require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-speech-builder.php';
        }
        
        $speech_builder = Mementor_TTS_Speech_Builder::get_instance();
        $speech_builder->create_tables();
        
        // Also create user credits table if needed
        if (!class_exists('Mementor_TTS_User_Credits')) {
            require_once MEMENTOR_TTS_PLUGIN_DIR . 'includes/class-mementor-tts-user-credits.php';
        }
        
        $user_credits = Mementor_TTS_User_Credits::get_instance();
        $user_credits->create_table();
    }
    
    /**
     * Filter plugin name in plugins list for white labeling
     *
     * @param array $plugins Array of plugins
     * @return array Modified plugins array
     */
    public function white_label_plugin_name($plugins) {
        // Get all white label settings
        $custom_name = get_option('mementor_tts_white_label_plugin_list_name', '');
        $custom_name_pro = get_option('mementor_tts_white_label_plugin_list_name_pro', '');
        $custom_author = get_option('mementor_tts_white_label_author', '');
        $custom_author_uri = get_option('mementor_tts_white_label_author_uri', '');
        $custom_plugin_uri = get_option('mementor_tts_white_label_plugin_uri', '');
        
        // Update the free version
        if (isset($plugins[MEMENTOR_TTS_PLUGIN_BASENAME])) {
            if (!empty($custom_name)) {
                $plugins[MEMENTOR_TTS_PLUGIN_BASENAME]['Name'] = $custom_name;
            }
            if (!empty($custom_author)) {
                $plugins[MEMENTOR_TTS_PLUGIN_BASENAME]['Author'] = $custom_author;
                $plugins[MEMENTOR_TTS_PLUGIN_BASENAME]['AuthorName'] = $custom_author;
            }
            if (!empty($custom_author_uri)) {
                $plugins[MEMENTOR_TTS_PLUGIN_BASENAME]['AuthorURI'] = $custom_author_uri;
            }
            if (!empty($custom_plugin_uri)) {
                $plugins[MEMENTOR_TTS_PLUGIN_BASENAME]['PluginURI'] = $custom_plugin_uri;
            }
        }
        
        // Update PRO version if it exists
        $pro_basename = 'text-to-speech-tts-pro/mementor-text-to-speech-pro.php';
        if (isset($plugins[$pro_basename])) {
            if (!empty($custom_name_pro)) {
                $plugins[$pro_basename]['Name'] = $custom_name_pro;
            } elseif (!empty($custom_name)) {
                // Fallback to free name + PRO if no specific PRO name set
                $plugins[$pro_basename]['Name'] = $custom_name . ' PRO';
            }
            
            if (!empty($custom_author)) {
                $plugins[$pro_basename]['Author'] = $custom_author;
                $plugins[$pro_basename]['AuthorName'] = $custom_author;
            }
            if (!empty($custom_author_uri)) {
                $plugins[$pro_basename]['AuthorURI'] = $custom_author_uri;
            }
            if (!empty($custom_plugin_uri)) {
                $plugins[$pro_basename]['PluginURI'] = $custom_plugin_uri;
            }
        }
        
        return $plugins;
    }
    
    /**
     * Check if menu item should be shown based on white label settings
     */
    private function should_show_menu_item($menu_type) {
        // Admins always see everything
        if (current_user_can('administrator')) {
            return true;
        }
        
        // Check if paid plan is hiding menus via white label
        if (ttswp_has_paid_plan()) {
            if ($menu_type === 'affiliate' && get_option('mementor_tts_white_label_hide_affiliate', '0') === '1') {
                return false;
            }
            if ($menu_type === 'license' && get_option('mementor_tts_white_label_hide_license', '0') === '1') {
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * Check if current user can see white label menu
     */
    private function can_user_see_white_label() {
        return true;
    }

    /**
     * Check if the current site can edit White Label settings (Agency plan only).
     */
    public function can_edit_white_label() {
        return get_option( 'tts_site_plan_slug', '' ) === 'agency';
    }

    /**
     * Add white label menu late (after PRO plugin loads)
     * This method is kept for backwards compatibility but menu is now added directly
     */
    public function add_white_label_menu_late() {
        // Menu is now added directly in register_admin_menu()
        // This method is kept for backwards compatibility
        return;
    }

    /**
     * AJAX handler for adding a word replacement
     * 
     * @since 1.0.0
     */
    public function add_word_replacement_ajax() {
        // Check nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'mementor_tts_word_replacement')) {
            wp_send_json_error(array('message' => __('Invalid nonce', 'text-to-speech-tts')));
            return;
        }

        // Check capabilities
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'text-to-speech-tts')));
            return;
        }

        // Check paid plan
        if (!ttswp_has_paid_plan()) {
            wp_send_json_error(array('message' => __('Word replacement is a PRO feature. Please upgrade to use this feature.', 'text-to-speech-tts')));
            return;
        }

        // Get and validate input
        $original = isset($_POST['original']) ? sanitize_text_field($_POST['original']) : '';
        $replacement = isset($_POST['replacement']) ? sanitize_text_field($_POST['replacement']) : '';

        if (empty($original) || empty($replacement)) {
            wp_send_json_error(array('message' => __('Both original and replacement text are required.', 'text-to-speech-tts')));
            return;
        }

        // Get existing replacements
        $replacements = get_option('mementor_tts_word_replacements', array());
        if (!is_array($replacements)) {
            $replacements = array();
        }

        // Check for duplicates
        foreach ($replacements as $existing) {
            if (isset($existing['original']) && $existing['original'] === $original) {
                wp_send_json_error(array('message' => __('This word replacement already exists.', 'text-to-speech-tts')));
                return;
            }
        }

        // Add new replacement
        $replacements[] = array(
            'original' => $original,
            'replacement' => $replacement
        );

        // Save replacements
        update_option('mementor_tts_word_replacements', $replacements);

        wp_send_json_success(array(
            'message' => __('Word replacement added successfully.', 'text-to-speech-tts'),
            'replacement' => array(
                'original' => $original,
                'replacement' => $replacement
            )
        ));
    }

    /**
     * AJAX handler for updating a word replacement
     * 
     * @since 1.0.0
     */
    public function update_word_replacement_ajax() {
        // Check nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'mementor_tts_word_replacement')) {
            wp_send_json_error(array('message' => __('Invalid nonce', 'text-to-speech-tts')));
            return;
        }

        // Check capabilities
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'text-to-speech-tts')));
            return;
        }

        // Check paid plan
        if (!ttswp_has_paid_plan()) {
            wp_send_json_error(array('message' => __('Word replacement is a PRO feature. Please upgrade to use this feature.', 'text-to-speech-tts')));
            return;
        }

        // Get and validate input
        $index = isset($_POST['index']) ? intval($_POST['index']) : -1;
        $original = isset($_POST['original']) ? sanitize_text_field($_POST['original']) : '';
        $replacement = isset($_POST['replacement']) ? sanitize_text_field($_POST['replacement']) : '';

        if ($index < 0 || empty($original) || empty($replacement)) {
            wp_send_json_error(array('message' => __('Invalid input data.', 'text-to-speech-tts')));
            return;
        }

        // Get existing replacements
        $replacements = get_option('mementor_tts_word_replacements', array());
        if (!is_array($replacements)) {
            $replacements = array();
        }

        // Check if index exists
        if (!isset($replacements[$index])) {
            wp_send_json_error(array('message' => __('Replacement not found.', 'text-to-speech-tts')));
            return;
        }

        // Update the replacement
        $replacements[$index] = array(
            'original' => $original,
            'replacement' => $replacement
        );

        // Save replacements
        update_option('mementor_tts_word_replacements', $replacements);

        wp_send_json_success(array(
            'message' => __('Word replacement updated successfully.', 'text-to-speech-tts'),
            'replacement' => array(
                'original' => $original,
                'replacement' => $replacement
            )
        ));
    }

    /**
     * AJAX handler for deleting a word replacement
     * 
     * @since 1.0.0
     */
    public function delete_word_replacement_ajax() {
        // Check nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'mementor_tts_word_replacement')) {
            wp_send_json_error(array('message' => __('Invalid nonce', 'text-to-speech-tts')));
            return;
        }

        // Check capabilities
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'text-to-speech-tts')));
            return;
        }

        // Check paid plan
        if (!ttswp_has_paid_plan()) {
            wp_send_json_error(array('message' => __('Word replacement is a PRO feature. Please upgrade to use this feature.', 'text-to-speech-tts')));
            return;
        }

        // Get and validate input
        $index = isset($_POST['index']) ? intval($_POST['index']) : -1;

        if ($index < 0) {
            wp_send_json_error(array('message' => __('Invalid index.', 'text-to-speech-tts')));
            return;
        }

        // Get existing replacements
        $replacements = get_option('mementor_tts_word_replacements', array());
        if (!is_array($replacements)) {
            $replacements = array();
        }

        // Check if index exists
        if (!isset($replacements[$index])) {
            wp_send_json_error(array('message' => __('Replacement not found.', 'text-to-speech-tts')));
            return;
        }

        // Remove the replacement
        unset($replacements[$index]);
        
        // Re-index array
        $replacements = array_values($replacements);

        // Save replacements
        update_option('mementor_tts_word_replacements', $replacements);

        wp_send_json_success(array(
            'message' => __('Word replacement deleted successfully.', 'text-to-speech-tts')
        ));
    }
    
}
