<?php

// File: classes/chat/admin/ajax/chatbot_ajax_handler.php
// Status: MODIFIED
// UPDATED FILE - Removed duplicate name check during rename.
// UPDATED FILE - If OpenAI Conversation State is enabled for an OpenAI bot, force global OpenAI 'Store Conversation' to true.

namespace WPAICG\Chat\Admin\Ajax;

use WPAICG\Chat\Storage\BotStorage;
use WPAICG\Chat\Storage\DefaultBotSetup;
use WPAICG\Chat\Storage\BotSettingsManager;
use WPAICG\Chat\Storage\SiteWideBotManager;
use WPAICG\Chat\Frontend\Shortcode; // Needed for get_chatbot_shortcode
use WPAICG\Chat\Admin\AdminSetup; // Needed for POST_TYPE constant
use WPAICG\AIPKit_Providers; // Added for updating global provider settings
use function WPAICG\Chat\Storage\SaverMethods\sanitize_settings_logic;
use WP_Error;

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

/**
 * Handles AJAX requests for Chatbot CRUD operations and settings.
 * Uses the BotStorage facade.
 */
class ChatbotAjaxHandler extends BaseAjaxHandler
{
    private $bot_storage;

    public function __construct()
    {
        // Ensure BotStorage exists and instantiate
        if (!class_exists(\WPAICG\Chat\Storage\BotStorage::class)) {
            return;
        }
        $this->bot_storage = new BotStorage();

        // Ensure AIPKit_Providers is available for updating global settings
        if (!class_exists(\WPAICG\AIPKit_Providers::class)) {
            $providers_path = WPAICG_PLUGIN_DIR . 'classes/dashboard/class-aipkit_providers.php';
            if (file_exists($providers_path)) {
                require_once $providers_path;
            }
        }
    }

    public function ajax_create_chatbot()
    {
        // REVISED: Use module access check
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $botName = isset($_POST['bot_name']) ? sanitize_text_field(wp_unslash($_POST['bot_name'])) : '';
        if (empty($botName)) {
            wp_send_json_error(['message' => __('Chatbot name cannot be empty.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        // Uses facade method
        $result = $this->bot_storage->create_bot($botName);
        if (is_wp_error($result)) {
            $this->send_wp_error($result);
        } else {
            wp_send_json_success([
                'message' => __('Chatbot created successfully!', 'gpt3-ai-content-generator'),
                'bot_id' => $result['bot_id'],
                'bot_name' => $result['bot_name'],
                'bot_settings' => $result['bot_settings']
            ]);
        }
    }

    public function ajax_save_chatbot_settings()
    {
        // REVISED: Use module access check
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $botId = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $settings = isset($_POST) ? wp_unslash($_POST) : array(); // Use unslashed $_POST

        if (empty($botId)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        // Uses facade method
        $result = $this->bot_storage->save_bot_settings($botId, $settings);

        if (is_wp_error($result)) {
            $this->send_wp_error($result);
        } else {
            // --- START: Check and update global OpenAI store_conversation setting ---
            if (isset($settings['provider']) && $settings['provider'] === 'OpenAI' &&
                isset($settings['openai_conversation_state_enabled']) && $settings['openai_conversation_state_enabled'] === '1') {
                if (class_exists(\WPAICG\AIPKit_Providers::class)) {
                    $openai_global_settings = AIPKit_Providers::get_provider_data('OpenAI');
                    if (($openai_global_settings['store_conversation'] ?? '0') !== '1') {
                        $openai_global_settings['store_conversation'] = '1';
                        AIPKit_Providers::save_provider_data('OpenAI', $openai_global_settings);
                    }
                }
            }
            // --- END: Check and update global OpenAI store_conversation setting ---

            wp_send_json_success([
                'message' => __('Chatbot settings saved successfully.', 'gpt3-ai-content-generator'),
            ]);
        }
    }

    public function ajax_delete_chatbot()
    {
        // REVISED: Use module access check
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $botId = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;
        if (empty($botId)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        // Check if it's the default bot (uses static method, no facade needed here)
        if (!class_exists(DefaultBotSetup::class)) {
            $this->send_wp_error(new WP_Error('dependency_missing', 'DefaultBotSetup class not found for deletion check.', ['status' => 500]));
            return;
        }
        $default_bot_id = DefaultBotSetup::get_default_bot_id();
        if ($botId === $default_bot_id) {
            $this->send_wp_error(new WP_Error('cannot_delete_default', __('The default chatbot cannot be deleted.', 'gpt3-ai-content-generator'), ['status' => 400]));
            return;
        }

        // Uses facade method
        $result = $this->bot_storage->delete_bot($botId);
        if (is_wp_error($result)) {
            $this->send_wp_error($result);
        } else {
            wp_send_json_success(['message' => __('Chatbot deleted successfully.', 'gpt3-ai-content-generator'), 'bot_id' => $botId]);
        }
    }

    public function ajax_get_chatbot_shortcode()
    {
        // REVISED: Use module access check
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_REQUEST['bot_id']) ? absint($_REQUEST['bot_id']) : 0;

        // Ensure AdminSetup class is available
        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }
        if (empty($bot_id) || get_post_type($bot_id) !== AdminSetup::POST_TYPE) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID provided.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        try {
            // Ensure Shortcode class is available
            if (!class_exists(Shortcode::class)) {
                wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
                return;
            }
            $shortcode_renderer = new Shortcode();
            $shortcode_html = $shortcode_renderer->render_chatbot_shortcode(['id' => $bot_id]);

            if (is_wp_error($shortcode_html)) {
                wp_send_json_error(['message' => $shortcode_html->get_error_message()], 500);
                return;
            }
            if (!is_string($shortcode_html)) {
                wp_send_json_error(['message' => __('Error generating shortcode HTML (non-string result).', 'gpt3-ai-content-generator')], 500);
                return;
            }
            // Basic UTF-8 check (optional but good practice)
            if (!mb_check_encoding($shortcode_html, 'UTF-8')) {
                $shortcode_html = mb_convert_encoding($shortcode_html, 'UTF-8', mb_detect_encoding($shortcode_html));
                if (!$shortcode_html) {
                    wp_send_json_error(['message' => __('Error generating shortcode HTML (encoding issue).', 'gpt3-ai-content-generator')], 500);
                    return;
                }
            }
            wp_send_json_success(['html' => $shortcode_html]);
        } catch (\Throwable $e) {
            wp_send_json_error(['message' => __('Internal server error generating preview.', 'gpt3-ai-content-generator')], 500);
        }
    }

    public function ajax_reset_chatbot_settings()
    {
        // REVISED: Use module access check
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $botId = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;
        if (empty($botId)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        // Uses static method, no facade needed here
        if (!class_exists(DefaultBotSetup::class)) {
            $this->send_wp_error(new WP_Error('dependency_missing', 'DefaultBotSetup class not found for reset.', ['status' => 500]));
            return;
        }
        $result = DefaultBotSetup::reset_bot_settings($botId);
        if (is_wp_error($result)) {
            $this->send_wp_error($result);
        } else {
            wp_send_json_success(['message' => __('Chatbot settings reset to defaults.', 'gpt3-ai-content-generator')]);
        }
    }

    /**
     * AJAX: Renames a chatbot.
     * @since NEXT_VERSION
     */
    public function ajax_rename_chatbot()
    {
        // Permission Check: User needs access to the chatbot module
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $new_name = isset($_POST['new_name']) ? sanitize_text_field(wp_unslash($_POST['new_name'])) : '';

        // Ensure AdminSetup class is available
        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        // --- Validation ---
        // Check Bot ID
        if (empty($bot_id) || get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        // Check if it's the default bot
        if (!class_exists(DefaultBotSetup::class)) {
            $this->send_wp_error(new WP_Error('dependency_missing', 'DefaultBotSetup class not found for rename check.', ['status' => 500]));
            return;
        }
        $default_bot_id = DefaultBotSetup::get_default_bot_id();
        if ($bot_id === $default_bot_id) {
            $this->send_wp_error(new WP_Error('cannot_rename_default', __('The default chatbot cannot be renamed.', 'gpt3-ai-content-generator'), ['status' => 400]));
            return;
        }

        // Check empty name
        if (empty($new_name)) {
            wp_send_json_error(['message' => __('Chatbot name cannot be empty.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        // --- End Validation ---

        // Update the post title
        $update_args = [
            'ID' => $bot_id,
            'post_title' => $new_name,
        ];
        $updated_post_id = wp_update_post($update_args, true); // Pass true to get WP_Error on failure

        if (is_wp_error($updated_post_id)) {
            wp_send_json_error(['message' => __('Failed to update chatbot name.', 'gpt3-ai-content-generator')], 500);
        } else {
            wp_send_json_success([
                'message' => __('Success!', 'gpt3-ai-content-generator'),
                'new_name' => $new_name
            ]);
        }
    }

    /**
     * AJAX: Updates chatbot instructions only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_instructions()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $instructions = isset($_POST['instructions']) ? sanitize_textarea_field(wp_unslash($_POST['instructions'])) : '';

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        update_post_meta($bot_id, '_aipkit_instructions', $instructions);

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Updates chatbot provider/model only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_model_settings()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $provider = isset($_POST['provider']) ? sanitize_text_field(wp_unslash($_POST['provider'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $model = isset($_POST['model']) ? sanitize_text_field(wp_unslash($_POST['model'])) : '';

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        $allowed_providers = ['OpenAI', 'OpenRouter', 'Google', 'Azure', 'DeepSeek', 'Ollama'];
        if (!in_array($provider, $allowed_providers, true)) {
            wp_send_json_error(['message' => __('Invalid provider.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        update_post_meta($bot_id, '_aipkit_provider', $provider);
        if ($model !== '') {
            update_post_meta($bot_id, '_aipkit_model', $model);
        } else {
            delete_post_meta($bot_id, '_aipkit_model');
        }

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Updates chatbot AI parameters only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_ai_parameters()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $temperature_raw = isset($_POST['temperature']) ? wp_unslash($_POST['temperature']) : null;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $max_tokens_raw = isset($_POST['max_completion_tokens']) ? wp_unslash($_POST['max_completion_tokens']) : null;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $max_messages_raw = isset($_POST['max_messages']) ? wp_unslash($_POST['max_messages']) : null;

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if ($temperature_raw === null || $max_tokens_raw === null) {
            wp_send_json_error(['message' => __('Invalid parameters.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        $temperature = floatval($temperature_raw);
        $temperature = max(0.0, min($temperature, 2.0));
        $max_tokens = absint($max_tokens_raw);
        $max_tokens = max(1, min($max_tokens, 128000));

        update_post_meta($bot_id, '_aipkit_temperature', (string) $temperature);
        update_post_meta($bot_id, '_aipkit_max_completion_tokens', $max_tokens);
        if ($max_messages_raw !== null) {
            $max_messages = absint($max_messages_raw);
            $max_messages = max(1, min($max_messages, 1024));
            update_post_meta($bot_id, '_aipkit_max_messages', $max_messages);
        }

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Updates chatbot conversation settings only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_conversation_settings()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $stream_enabled = (isset($_POST['stream_enabled']) && wp_unslash($_POST['stream_enabled']) === '1') ? '1' : '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $openai_conversation_state_enabled = (isset($_POST['openai_conversation_state_enabled']) && wp_unslash($_POST['openai_conversation_state_enabled']) === '1') ? '1' : '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $reasoning_effort = isset($_POST['reasoning_effort'])
            ? sanitize_key(wp_unslash($_POST['reasoning_effort']))
            : BotSettingsManager::DEFAULT_REASONING_EFFORT;
        $allowed_reasoning_effort = ['minimal', 'low', 'medium', 'high'];
        if (!in_array($reasoning_effort, $allowed_reasoning_effort, true)) {
            $reasoning_effort = BotSettingsManager::DEFAULT_REASONING_EFFORT;
        }

        update_post_meta($bot_id, '_aipkit_stream_enabled', $stream_enabled);
        update_post_meta($bot_id, '_aipkit_openai_conversation_state_enabled', $openai_conversation_state_enabled);
        update_post_meta($bot_id, '_aipkit_reasoning_effort', $reasoning_effort);

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Updates chatbot style settings only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_style_settings()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (isset($_POST['greeting'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $greeting = sanitize_textarea_field(wp_unslash($_POST['greeting']));
            update_post_meta($bot_id, '_aipkit_greeting_message', $greeting);
        }

        if (isset($_POST['subgreeting'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $subgreeting = sanitize_textarea_field(wp_unslash($_POST['subgreeting']));
            update_post_meta($bot_id, '_aipkit_subgreeting_message', $subgreeting);
        }

        if (isset($_POST['input_placeholder'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $input_placeholder = sanitize_text_field(wp_unslash($_POST['input_placeholder']));
            update_post_meta($bot_id, '_aipkit_input_placeholder', $input_placeholder);
        }

        if (isset($_POST['footer_text'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $footer_text = wp_kses_post(wp_unslash($_POST['footer_text']));
            update_post_meta($bot_id, '_aipkit_footer_text', $footer_text);
        }

        if (
            isset($_POST['header_avatar_type']) ||
            isset($_POST['header_avatar_default']) ||
            isset($_POST['header_avatar_url'])
        ) {
            $allowed_header_icons = ['chat-bubble', 'spark', 'openai', 'plus', 'question-mark'];
            $header_avatar_type = get_post_meta($bot_id, '_aipkit_header_avatar_type', BotSettingsManager::DEFAULT_HEADER_AVATAR_TYPE);
            if (isset($_POST['header_avatar_type'])) {
                // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
                $header_avatar_type = sanitize_key(wp_unslash($_POST['header_avatar_type']));
            }
            if (!in_array($header_avatar_type, ['default', 'custom'], true)) {
                $header_avatar_type = BotSettingsManager::DEFAULT_HEADER_AVATAR_TYPE;
            }
            if (!isset($_POST['header_avatar_type']) && isset($_POST['header_avatar_url']) && !empty($_POST['header_avatar_url'])) {
                $header_avatar_type = 'custom';
            }

            if ($header_avatar_type === 'custom') {
                $header_avatar_url = isset($_POST['header_avatar_url'])
                    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
                    ? esc_url_raw(trim((string)wp_unslash($_POST['header_avatar_url'])))
                    : get_post_meta($bot_id, '_aipkit_header_avatar_url', BotSettingsManager::DEFAULT_HEADER_AVATAR_URL);
                $header_avatar_value = $header_avatar_url;
                update_post_meta($bot_id, '_aipkit_header_avatar_url', $header_avatar_url);
            } else {
                $header_avatar_default = isset($_POST['header_avatar_default'])
                    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
                    ? sanitize_key(wp_unslash($_POST['header_avatar_default']))
                    : get_post_meta($bot_id, '_aipkit_header_avatar_value', BotSettingsManager::DEFAULT_HEADER_AVATAR_VALUE);
                if (!in_array($header_avatar_default, $allowed_header_icons, true)) {
                    $header_avatar_default = BotSettingsManager::DEFAULT_HEADER_AVATAR_VALUE;
                }
                $header_avatar_value = $header_avatar_default;
                update_post_meta($bot_id, '_aipkit_header_avatar_url', '');
            }

            update_post_meta($bot_id, '_aipkit_header_avatar_type', $header_avatar_type);
            update_post_meta($bot_id, '_aipkit_header_avatar_value', $header_avatar_value);
        }

        if (isset($_POST['custom_typing_text'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $typing_text = sanitize_text_field(wp_unslash($_POST['custom_typing_text']));
            update_post_meta($bot_id, '_aipkit_custom_typing_text', $typing_text);
        }

        if (isset($_POST['theme'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $theme = sanitize_key(wp_unslash($_POST['theme']));
            $allowed_themes = ['light', 'dark', 'custom', 'chatgpt'];
            if (!in_array($theme, $allowed_themes, true)) {
                $theme = 'dark';
            }
            update_post_meta($bot_id, '_aipkit_theme', $theme);
        }

        if (isset($_POST['enable_download'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $enable_download = (wp_unslash($_POST['enable_download']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_enable_download', $enable_download);
        }

        if (isset($_POST['enable_copy_button'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $enable_copy_button = (wp_unslash($_POST['enable_copy_button']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_enable_copy_button', $enable_copy_button);
        }

        if (isset($_POST['enable_fullscreen'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $enable_fullscreen = (wp_unslash($_POST['enable_fullscreen']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_enable_fullscreen', $enable_fullscreen);
        }

        if (isset($_POST['enable_feedback'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $enable_feedback = (wp_unslash($_POST['enable_feedback']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_enable_feedback', $enable_feedback);
        }

        if (isset($_POST['enable_consent_compliance'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $enable_consent = (wp_unslash($_POST['enable_consent_compliance']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_enable_consent_compliance', $enable_consent);
        }

        if (isset($_POST['enable_ip_anonymization'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $enable_ip_anonymization = (wp_unslash($_POST['enable_ip_anonymization']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_enable_ip_anonymization', $enable_ip_anonymization);
        }

        if (isset($_POST['consent_title'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $consent_title = sanitize_text_field(wp_unslash($_POST['consent_title']));
            update_post_meta($bot_id, '_aipkit_consent_title', $consent_title);
        }

        if (isset($_POST['consent_message'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $consent_message = wp_kses_post(wp_unslash($_POST['consent_message']));
            update_post_meta($bot_id, '_aipkit_consent_message', $consent_message);
        }

        if (isset($_POST['consent_button'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $consent_button = sanitize_text_field(wp_unslash($_POST['consent_button']));
            update_post_meta($bot_id, '_aipkit_consent_button', $consent_button);
        }

        if (isset($_POST['openai_moderation_enabled'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $openai_moderation_enabled = (wp_unslash($_POST['openai_moderation_enabled']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_openai_moderation_enabled', $openai_moderation_enabled);
        }

        if (isset($_POST['openai_moderation_message'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $openai_moderation_message = sanitize_text_field(wp_unslash($_POST['openai_moderation_message']));
            update_post_meta($bot_id, '_aipkit_openai_moderation_message', $openai_moderation_message);
        }

        if (isset($_POST['banned_words'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $banned_words_raw = sanitize_textarea_field(wp_unslash($_POST['banned_words']));
            $banned_words_array = array_map('trim', explode(',', strtolower($banned_words_raw)));
            $banned_words_clean = implode(',', array_filter($banned_words_array, 'strlen'));
            update_post_meta($bot_id, '_aipkit_banned_words', $banned_words_clean);
        }

        if (isset($_POST['banned_words_message'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $banned_words_message = sanitize_text_field(wp_unslash($_POST['banned_words_message']));
            update_post_meta($bot_id, '_aipkit_banned_words_message', $banned_words_message);
        }

        if (isset($_POST['banned_ips'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $banned_ips_raw = sanitize_textarea_field(wp_unslash($_POST['banned_ips']));
            $banned_ips_array = array_map('trim', explode(',', $banned_ips_raw));
            $banned_ips_clean = implode(',', array_filter($banned_ips_array, 'strlen'));
            update_post_meta($bot_id, '_aipkit_banned_ips', $banned_ips_clean);
        }

        if (isset($_POST['banned_ips_message'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $banned_ips_message = sanitize_text_field(wp_unslash($_POST['banned_ips_message']));
            update_post_meta($bot_id, '_aipkit_banned_ips_message', $banned_ips_message);
        }

        if (isset($_POST['enable_conversation_sidebar'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $enable_sidebar = (wp_unslash($_POST['enable_conversation_sidebar']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_enable_conversation_sidebar', $enable_sidebar);
        }

        if (isset($_POST['enable_conversation_starters'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $enable_starters = (wp_unslash($_POST['enable_conversation_starters']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_enable_conversation_starters', $enable_starters);
        }

        if (isset($_POST['conversation_starters'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $starters_raw = wp_unslash($_POST['conversation_starters']);
            $starters_array = [];
            if (!empty($starters_raw)) {
                $lines = explode("\n", $starters_raw);
                foreach ($lines as $line) {
                    $trimmed_line = trim($line);
                    if (!empty($trimmed_line)) {
                        $starters_array[] = $trimmed_line;
                    }
                }
                $starters_array = array_slice($starters_array, 0, 6);
            }
            update_post_meta(
                $bot_id,
                '_aipkit_conversation_starters',
                wp_json_encode($starters_array, JSON_UNESCAPED_UNICODE)
            );
        }

        if (isset($_POST['custom_theme_settings']) && is_array($_POST['custom_theme_settings'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $custom_theme_raw = wp_unslash($_POST['custom_theme_settings']);
            if (is_array($custom_theme_raw)) {
                if (!function_exists('\\WPAICG\\Chat\\Storage\\SaverMethods\\sanitize_settings_logic')) {
                    $sanitize_path = WPAICG_PLUGIN_DIR . 'classes/chat/storage/saver/sanitize-settings-logic.php';
                    if (file_exists($sanitize_path)) {
                        require_once $sanitize_path;
                    }
                }
                $sanitized = sanitize_settings_logic([
                    'custom_theme_settings' => $custom_theme_raw,
                ], $bot_id);
                if (!empty($sanitized['custom_theme_settings']) && is_array($sanitized['custom_theme_settings'])) {
                    foreach ($sanitized['custom_theme_settings'] as $key => $value) {
                        update_post_meta($bot_id, '_aipkit_cts_' . $key, $value);
                    }
                }
            }
        }

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Updates chatbot web search/grounding settings only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_web_settings()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $openai_web_search_enabled = (isset($_POST['openai_web_search_enabled']) && wp_unslash($_POST['openai_web_search_enabled']) === '1') ? '1' : '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $openai_web_search_context_size = isset($_POST['openai_web_search_context_size']) ? sanitize_text_field(wp_unslash($_POST['openai_web_search_context_size'])) : BotSettingsManager::DEFAULT_OPENAI_WEB_SEARCH_CONTEXT_SIZE;
        $allowed_context_sizes = ['low', 'medium', 'high'];
        if (!in_array($openai_web_search_context_size, $allowed_context_sizes, true)) {
            $openai_web_search_context_size = BotSettingsManager::DEFAULT_OPENAI_WEB_SEARCH_CONTEXT_SIZE;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $openai_web_search_loc_type = isset($_POST['openai_web_search_loc_type']) ? sanitize_text_field(wp_unslash($_POST['openai_web_search_loc_type'])) : BotSettingsManager::DEFAULT_OPENAI_WEB_SEARCH_LOC_TYPE;
        $allowed_loc_types = ['none', 'approximate'];
        if (!in_array($openai_web_search_loc_type, $allowed_loc_types, true)) {
            $openai_web_search_loc_type = BotSettingsManager::DEFAULT_OPENAI_WEB_SEARCH_LOC_TYPE;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $openai_web_search_loc_country = isset($_POST['openai_web_search_loc_country']) ? sanitize_text_field(wp_unslash($_POST['openai_web_search_loc_country'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $openai_web_search_loc_city = isset($_POST['openai_web_search_loc_city']) ? sanitize_text_field(wp_unslash($_POST['openai_web_search_loc_city'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $openai_web_search_loc_region = isset($_POST['openai_web_search_loc_region']) ? sanitize_text_field(wp_unslash($_POST['openai_web_search_loc_region'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $openai_web_search_loc_timezone = isset($_POST['openai_web_search_loc_timezone']) ? sanitize_text_field(wp_unslash($_POST['openai_web_search_loc_timezone'])) : '';

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $google_search_grounding_enabled = (isset($_POST['google_search_grounding_enabled']) && wp_unslash($_POST['google_search_grounding_enabled']) === '1') ? '1' : '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $google_grounding_mode = isset($_POST['google_grounding_mode']) ? sanitize_text_field(wp_unslash($_POST['google_grounding_mode'])) : BotSettingsManager::DEFAULT_GOOGLE_GROUNDING_MODE;
        $allowed_grounding_modes = ['DEFAULT_MODE', 'MODE_DYNAMIC'];
        if (!in_array($google_grounding_mode, $allowed_grounding_modes, true)) {
            $google_grounding_mode = BotSettingsManager::DEFAULT_GOOGLE_GROUNDING_MODE;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $google_grounding_dynamic_threshold = isset($_POST['google_grounding_dynamic_threshold'])
            ? floatval(wp_unslash($_POST['google_grounding_dynamic_threshold']))
            : BotSettingsManager::DEFAULT_GOOGLE_GROUNDING_DYNAMIC_THRESHOLD;
        $google_grounding_dynamic_threshold = max(0.0, min($google_grounding_dynamic_threshold, 1.0));

        update_post_meta($bot_id, '_aipkit_openai_web_search_enabled', $openai_web_search_enabled);
        update_post_meta($bot_id, '_aipkit_openai_web_search_context_size', $openai_web_search_context_size);
        update_post_meta($bot_id, '_aipkit_openai_web_search_loc_type', $openai_web_search_loc_type);
        update_post_meta($bot_id, '_aipkit_openai_web_search_loc_country', $openai_web_search_loc_country);
        update_post_meta($bot_id, '_aipkit_openai_web_search_loc_city', $openai_web_search_loc_city);
        update_post_meta($bot_id, '_aipkit_openai_web_search_loc_region', $openai_web_search_loc_region);
        update_post_meta($bot_id, '_aipkit_openai_web_search_loc_timezone', $openai_web_search_loc_timezone);
        update_post_meta($bot_id, '_aipkit_google_search_grounding_enabled', $google_search_grounding_enabled);
        update_post_meta($bot_id, '_aipkit_google_grounding_mode', $google_grounding_mode);
        update_post_meta($bot_id, '_aipkit_google_grounding_dynamic_threshold', $google_grounding_dynamic_threshold);

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Updates chatbot context settings only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_context_settings()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $content_aware_enabled = (isset($_POST['content_aware_enabled']) && wp_unslash($_POST['content_aware_enabled']) === '1') ? '1' : '0';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $enable_vector_store = (isset($_POST['enable_vector_store']) && wp_unslash($_POST['enable_vector_store']) === '1') ? '1' : '0';

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $vector_store_provider = isset($_POST['vector_store_provider'])
            ? sanitize_text_field(wp_unslash($_POST['vector_store_provider']))
            : BotSettingsManager::DEFAULT_VECTOR_STORE_PROVIDER;
        $allowed_providers = ['openai', 'pinecone', 'qdrant'];
        if (!in_array($vector_store_provider, $allowed_providers, true)) {
            $vector_store_provider = BotSettingsManager::DEFAULT_VECTOR_STORE_PROVIDER;
        }

        $openai_vector_store_ids = [];
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        if (isset($_POST['openai_vector_store_ids'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $raw_ids = wp_unslash($_POST['openai_vector_store_ids']);
            if (is_array($raw_ids)) {
                foreach ($raw_ids as $vs_id) {
                    $sanitized_id = sanitize_text_field(trim((string) $vs_id));
                    if (!empty($sanitized_id) && strpos($sanitized_id, 'vs_') === 0) {
                        $openai_vector_store_ids[] = $sanitized_id;
                    }
                }
            }
        }
        $openai_vector_store_ids = array_values(array_unique($openai_vector_store_ids));

        $pinecone_index_name = '';
        if ($vector_store_provider === 'pinecone') {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $pinecone_index_name = isset($_POST['pinecone_index_name'])
                ? sanitize_text_field(wp_unslash($_POST['pinecone_index_name']))
                : '';
        }

        $qdrant_collection_names = [];
        if ($vector_store_provider === 'qdrant') {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            if (isset($_POST['qdrant_collection_names'])) {
                // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
                $raw_names = wp_unslash($_POST['qdrant_collection_names']);
                if (is_array($raw_names)) {
                    foreach ($raw_names as $name) {
                        $sanitized_name = sanitize_text_field(trim((string) $name));
                        if ($sanitized_name !== '') {
                            $qdrant_collection_names[] = $sanitized_name;
                        }
                    }
                }
            }
        }
        $qdrant_collection_names = array_values(array_unique($qdrant_collection_names));
        $qdrant_collection_name = $qdrant_collection_names[0] ?? '';

        $vector_embedding_provider = BotSettingsManager::DEFAULT_VECTOR_EMBEDDING_PROVIDER;
        $vector_embedding_model = '';
        if (in_array($vector_store_provider, ['pinecone', 'qdrant'], true)) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $vector_embedding_provider = isset($_POST['vector_embedding_provider'])
                ? sanitize_key(wp_unslash($_POST['vector_embedding_provider']))
                : BotSettingsManager::DEFAULT_VECTOR_EMBEDDING_PROVIDER;
            $allowed_embedding_providers = ['openai', 'google', 'azure'];
            if (!in_array($vector_embedding_provider, $allowed_embedding_providers, true)) {
                $vector_embedding_provider = BotSettingsManager::DEFAULT_VECTOR_EMBEDDING_PROVIDER;
            }
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $vector_embedding_model = isset($_POST['vector_embedding_model'])
                ? sanitize_text_field(wp_unslash($_POST['vector_embedding_model']))
                : '';
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $vector_store_top_k = isset($_POST['vector_store_top_k'])
            ? absint(wp_unslash($_POST['vector_store_top_k']))
            : BotSettingsManager::DEFAULT_VECTOR_STORE_TOP_K;
        $vector_store_top_k = max(1, min($vector_store_top_k, 20));

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $vector_store_confidence_threshold = isset($_POST['vector_store_confidence_threshold'])
            ? absint(wp_unslash($_POST['vector_store_confidence_threshold']))
            : BotSettingsManager::DEFAULT_VECTOR_STORE_CONFIDENCE_THRESHOLD;
        $vector_store_confidence_threshold = max(0, min($vector_store_confidence_threshold, 100));

        update_post_meta($bot_id, '_aipkit_content_aware_enabled', $content_aware_enabled);
        update_post_meta($bot_id, '_aipkit_enable_vector_store', $enable_vector_store);
        update_post_meta($bot_id, '_aipkit_vector_store_provider', $vector_store_provider);

        if ($vector_store_provider === 'openai') {
            update_post_meta($bot_id, '_aipkit_openai_vector_store_ids', wp_json_encode($openai_vector_store_ids));
            delete_post_meta($bot_id, '_aipkit_pinecone_index_name');
            delete_post_meta($bot_id, '_aipkit_qdrant_collection_name');
            delete_post_meta($bot_id, '_aipkit_qdrant_collection_names');
            delete_post_meta($bot_id, '_aipkit_vector_embedding_provider');
            delete_post_meta($bot_id, '_aipkit_vector_embedding_model');
        } elseif ($vector_store_provider === 'pinecone') {
            update_post_meta($bot_id, '_aipkit_pinecone_index_name', $pinecone_index_name);
            update_post_meta($bot_id, '_aipkit_vector_embedding_provider', $vector_embedding_provider);
            update_post_meta($bot_id, '_aipkit_vector_embedding_model', $vector_embedding_model);
            delete_post_meta($bot_id, '_aipkit_openai_vector_store_ids');
            delete_post_meta($bot_id, '_aipkit_qdrant_collection_name');
            delete_post_meta($bot_id, '_aipkit_qdrant_collection_names');
        } elseif ($vector_store_provider === 'qdrant') {
            update_post_meta($bot_id, '_aipkit_qdrant_collection_name', $qdrant_collection_name);
            update_post_meta($bot_id, '_aipkit_qdrant_collection_names', wp_json_encode($qdrant_collection_names));
            update_post_meta($bot_id, '_aipkit_vector_embedding_provider', $vector_embedding_provider);
            update_post_meta($bot_id, '_aipkit_vector_embedding_model', $vector_embedding_model);
            delete_post_meta($bot_id, '_aipkit_openai_vector_store_ids');
            delete_post_meta($bot_id, '_aipkit_pinecone_index_name');
        } else {
            delete_post_meta($bot_id, '_aipkit_openai_vector_store_ids');
            delete_post_meta($bot_id, '_aipkit_pinecone_index_name');
            delete_post_meta($bot_id, '_aipkit_qdrant_collection_name');
            delete_post_meta($bot_id, '_aipkit_qdrant_collection_names');
            delete_post_meta($bot_id, '_aipkit_vector_embedding_provider');
            delete_post_meta($bot_id, '_aipkit_vector_embedding_model');
        }

        update_post_meta($bot_id, '_aipkit_vector_store_top_k', $vector_store_top_k);
        update_post_meta($bot_id, '_aipkit_vector_store_confidence_threshold', $vector_store_confidence_threshold);

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Updates chatbot token limit settings only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_token_limits()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $token_limit_mode = isset($_POST['token_limit_mode']) ? sanitize_key(wp_unslash($_POST['token_limit_mode'])) : BotSettingsManager::DEFAULT_TOKEN_LIMIT_MODE;
        if (!in_array($token_limit_mode, ['general', 'role_based'], true)) {
            $token_limit_mode = BotSettingsManager::DEFAULT_TOKEN_LIMIT_MODE;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $guest_limit_raw = isset($_POST['token_guest_limit']) ? trim(wp_unslash($_POST['token_guest_limit'])) : '';
        $token_guest_limit = ($guest_limit_raw === '0' || (ctype_digit($guest_limit_raw) && $guest_limit_raw > 0))
            ? (string) absint($guest_limit_raw)
            : '';

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $user_limit_raw = isset($_POST['token_user_limit']) ? trim(wp_unslash($_POST['token_user_limit'])) : '';
        $token_user_limit = ($user_limit_raw === '0' || (ctype_digit($user_limit_raw) && $user_limit_raw > 0))
            ? (string) absint($user_limit_raw)
            : '';

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $token_reset_period = isset($_POST['token_reset_period']) ? sanitize_key(wp_unslash($_POST['token_reset_period'])) : BotSettingsManager::DEFAULT_TOKEN_RESET_PERIOD;
        if (!in_array($token_reset_period, ['never', 'daily', 'weekly', 'monthly'], true)) {
            $token_reset_period = BotSettingsManager::DEFAULT_TOKEN_RESET_PERIOD;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $token_limit_message = isset($_POST['token_limit_message']) ? sanitize_text_field(wp_unslash($_POST['token_limit_message'])) : '';

        $role_limits_to_save = [];
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        if (isset($_POST['token_role_limits']) && is_array($_POST['token_role_limits'])) {
            $editable_roles = get_editable_roles();
            $posted_role_limits = wp_unslash($_POST['token_role_limits']);
            foreach ($editable_roles as $role_slug => $role_info) {
                if (!isset($posted_role_limits[$role_slug])) {
                    continue;
                }
                $raw_limit = trim((string) $posted_role_limits[$role_slug]);
                if ($raw_limit === '0' || (ctype_digit($raw_limit) && $raw_limit > 0)) {
                    $role_limits_to_save[$role_slug] = (string) absint($raw_limit);
                } else {
                    $role_limits_to_save[$role_slug] = '';
                }
            }
        }

        update_post_meta($bot_id, '_aipkit_token_limit_mode', $token_limit_mode);

        if ($token_guest_limit === '') {
            delete_post_meta($bot_id, '_aipkit_token_guest_limit');
        } else {
            update_post_meta($bot_id, '_aipkit_token_guest_limit', $token_guest_limit);
        }

        if ($token_user_limit === '') {
            delete_post_meta($bot_id, '_aipkit_token_user_limit');
        } else {
            update_post_meta($bot_id, '_aipkit_token_user_limit', $token_user_limit);
        }

        update_post_meta($bot_id, '_aipkit_token_reset_period', $token_reset_period);

        if ($token_limit_message === '') {
            delete_post_meta($bot_id, '_aipkit_token_limit_message');
        } else {
            update_post_meta($bot_id, '_aipkit_token_limit_message', $token_limit_message);
        }

        $role_limits_json = wp_json_encode($role_limits_to_save, JSON_UNESCAPED_UNICODE);
        if (empty(json_decode($role_limits_json, true))) {
            delete_post_meta($bot_id, '_aipkit_token_role_limits');
        } else {
            update_post_meta($bot_id, '_aipkit_token_role_limits', $role_limits_json);
        }

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Updates chatbot image settings only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_image_settings()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $chat_image_model_id = isset($_POST['chat_image_model_id'])
            ? sanitize_text_field(wp_unslash($_POST['chat_image_model_id']))
            : BotSettingsManager::DEFAULT_CHAT_IMAGE_MODEL_ID;
        if ($chat_image_model_id === '') {
            $chat_image_model_id = BotSettingsManager::DEFAULT_CHAT_IMAGE_MODEL_ID;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $raw_image_triggers = isset($_POST['image_triggers'])
            ? sanitize_text_field(wp_unslash($_POST['image_triggers']))
            : BotSettingsManager::DEFAULT_IMAGE_TRIGGERS;
        $triggers_array = array_map('trim', explode(',', $raw_image_triggers));
        $image_triggers = !empty($triggers_array)
            ? implode(',', $triggers_array)
            : BotSettingsManager::DEFAULT_IMAGE_TRIGGERS;

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $enable_image_upload = (isset($_POST['enable_image_upload']) && wp_unslash($_POST['enable_image_upload']) === '1') ? '1' : '0';

        update_post_meta($bot_id, '_aipkit_chat_image_model_id', $chat_image_model_id);
        update_post_meta($bot_id, '_aipkit_image_triggers', $image_triggers);
        update_post_meta($bot_id, '_aipkit_enable_image_upload', $enable_image_upload);

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Updates chatbot file upload setting only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_file_upload_settings()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $enable_file_upload = (isset($_POST['enable_file_upload']) && wp_unslash($_POST['enable_file_upload']) === '1') ? '1' : '0';
        update_post_meta($bot_id, '_aipkit_enable_file_upload', $enable_file_upload);

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Updates chatbot audio settings only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_audio_settings()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (isset($_POST['enable_voice_input'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $enable_voice_input = (wp_unslash($_POST['enable_voice_input']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_enable_voice_input', $enable_voice_input);
        }

        if (isset($_POST['stt_provider'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $stt_provider = sanitize_text_field(wp_unslash($_POST['stt_provider']));
            $allowed_stt_providers = ['OpenAI', 'Azure'];
            if (!in_array($stt_provider, $allowed_stt_providers, true)) {
                $stt_provider = BotSettingsManager::DEFAULT_STT_PROVIDER;
            }
            update_post_meta($bot_id, '_aipkit_stt_provider', $stt_provider);
        }

        if (isset($_POST['stt_openai_model_id'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $stt_openai_model_id = sanitize_text_field(wp_unslash($_POST['stt_openai_model_id']));
            if ($stt_openai_model_id === '') {
                $stt_openai_model_id = BotSettingsManager::DEFAULT_STT_OPENAI_MODEL_ID;
            }
            update_post_meta($bot_id, '_aipkit_stt_openai_model_id', $stt_openai_model_id);
        }

        if (isset($_POST['tts_enabled'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $tts_enabled = (wp_unslash($_POST['tts_enabled']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_tts_enabled', $tts_enabled);
        }

        if (isset($_POST['tts_auto_play'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $tts_auto_play = (wp_unslash($_POST['tts_auto_play']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_tts_auto_play', $tts_auto_play);
        }

        if (isset($_POST['tts_provider'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $tts_provider = sanitize_text_field(wp_unslash($_POST['tts_provider']));
            $allowed_tts_providers = ['Google', 'OpenAI', 'ElevenLabs'];
            if (!in_array($tts_provider, $allowed_tts_providers, true)) {
                $tts_provider = BotSettingsManager::DEFAULT_TTS_PROVIDER;
            }
            update_post_meta($bot_id, '_aipkit_tts_provider', $tts_provider);
        }

        if (isset($_POST['tts_google_voice_id'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $tts_google_voice_id = sanitize_text_field(wp_unslash($_POST['tts_google_voice_id']));
            update_post_meta($bot_id, '_aipkit_tts_google_voice_id', $tts_google_voice_id);
        }

        if (isset($_POST['tts_openai_voice_id'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $tts_openai_voice_id = sanitize_text_field(wp_unslash($_POST['tts_openai_voice_id']));
            if ($tts_openai_voice_id === '') {
                $tts_openai_voice_id = 'alloy';
            }
            update_post_meta($bot_id, '_aipkit_tts_openai_voice_id', $tts_openai_voice_id);
        }

        if (isset($_POST['tts_openai_model_id'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $tts_openai_model_id = sanitize_text_field(wp_unslash($_POST['tts_openai_model_id']));
            if ($tts_openai_model_id === '') {
                $tts_openai_model_id = BotSettingsManager::DEFAULT_TTS_OPENAI_MODEL_ID;
            }
            update_post_meta($bot_id, '_aipkit_tts_openai_model_id', $tts_openai_model_id);
        }

        if (isset($_POST['tts_elevenlabs_voice_id'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $tts_elevenlabs_voice_id = sanitize_text_field(wp_unslash($_POST['tts_elevenlabs_voice_id']));
            update_post_meta($bot_id, '_aipkit_tts_elevenlabs_voice_id', $tts_elevenlabs_voice_id);
        }

        if (isset($_POST['tts_elevenlabs_model_id'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $tts_elevenlabs_model_id = sanitize_text_field(wp_unslash($_POST['tts_elevenlabs_model_id']));
            update_post_meta($bot_id, '_aipkit_tts_elevenlabs_model_id', $tts_elevenlabs_model_id);
        }

        if (isset($_POST['enable_realtime_voice'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $enable_realtime_voice = (wp_unslash($_POST['enable_realtime_voice']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_enable_realtime_voice', $enable_realtime_voice);
        }

        if (isset($_POST['direct_voice_mode'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $direct_voice_mode = (wp_unslash($_POST['direct_voice_mode']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_direct_voice_mode', $direct_voice_mode);
        }

        if (isset($_POST['input_audio_noise_reduction'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $input_audio_noise_reduction = (wp_unslash($_POST['input_audio_noise_reduction']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_input_audio_noise_reduction', $input_audio_noise_reduction);
        }

        if (isset($_POST['realtime_model'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $realtime_model = sanitize_text_field(wp_unslash($_POST['realtime_model']));
            $allowed_realtime_models = ['gpt-4o-realtime-preview', 'gpt-4o-mini-realtime'];
            if (!in_array($realtime_model, $allowed_realtime_models, true)) {
                $realtime_model = BotSettingsManager::DEFAULT_REALTIME_MODEL;
            }
            update_post_meta($bot_id, '_aipkit_realtime_model', $realtime_model);
        }

        if (isset($_POST['realtime_voice'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $realtime_voice = sanitize_text_field(wp_unslash($_POST['realtime_voice']));
            $allowed_realtime_voices = ['alloy', 'ash', 'ballad', 'coral', 'echo', 'fable', 'onyx', 'nova', 'shimmer', 'verse'];
            if (!in_array($realtime_voice, $allowed_realtime_voices, true)) {
                $realtime_voice = BotSettingsManager::DEFAULT_REALTIME_VOICE;
            }
            update_post_meta($bot_id, '_aipkit_realtime_voice', $realtime_voice);
        }

        if (isset($_POST['turn_detection'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $turn_detection = sanitize_text_field(wp_unslash($_POST['turn_detection']));
            $allowed_turn_detection = ['none', 'server_vad', 'semantic_vad'];
            if (!in_array($turn_detection, $allowed_turn_detection, true)) {
                $turn_detection = BotSettingsManager::DEFAULT_TURN_DETECTION;
            }
            update_post_meta($bot_id, '_aipkit_turn_detection', $turn_detection);
        }

        if (isset($_POST['speed'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $speed = floatval(wp_unslash($_POST['speed']));
            $speed = max(0.25, min($speed, 1.5));
            update_post_meta($bot_id, '_aipkit_speed', (string) $speed);
        }

        if (isset($_POST['input_audio_format'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $input_audio_format = sanitize_text_field(wp_unslash($_POST['input_audio_format']));
            $valid_formats = ['pcm16', 'g711_ulaw', 'g711_alaw'];
            if (!in_array($input_audio_format, $valid_formats, true)) {
                $input_audio_format = BotSettingsManager::DEFAULT_INPUT_AUDIO_FORMAT;
            }
            update_post_meta($bot_id, '_aipkit_input_audio_format', $input_audio_format);
        }

        if (isset($_POST['output_audio_format'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $output_audio_format = sanitize_text_field(wp_unslash($_POST['output_audio_format']));
            $valid_formats = ['pcm16', 'g711_ulaw', 'g711_alaw'];
            if (!in_array($output_audio_format, $valid_formats, true)) {
                $output_audio_format = BotSettingsManager::DEFAULT_OUTPUT_AUDIO_FORMAT;
            }
            update_post_meta($bot_id, '_aipkit_output_audio_format', $output_audio_format);
        }

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Updates chatbot popup settings only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_popup_settings()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (isset($_POST['popup_position'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $popup_position = sanitize_key(wp_unslash($_POST['popup_position']));
            $allowed_positions = ['bottom-right', 'bottom-left', 'top-right', 'top-left'];
            if (!in_array($popup_position, $allowed_positions, true)) {
                $popup_position = 'bottom-right';
            }
            update_post_meta($bot_id, '_aipkit_popup_position', $popup_position);
        }

        if (isset($_POST['popup_delay'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $popup_delay = absint(wp_unslash($_POST['popup_delay']));
            update_post_meta($bot_id, '_aipkit_popup_delay', $popup_delay);
        }

        $icon_type = null;
        if (isset($_POST['popup_icon_type'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $icon_type = sanitize_key(wp_unslash($_POST['popup_icon_type']));
            if (!in_array($icon_type, ['default', 'custom'], true)) {
                $icon_type = BotSettingsManager::DEFAULT_POPUP_ICON_TYPE;
            }
            update_post_meta($bot_id, '_aipkit_popup_icon_type', $icon_type);
        }

        if (isset($_POST['popup_icon_style'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $icon_style = sanitize_key(wp_unslash($_POST['popup_icon_style']));
            if (!in_array($icon_style, ['circle', 'square', 'none'], true)) {
                $icon_style = BotSettingsManager::DEFAULT_POPUP_ICON_STYLE;
            }
            update_post_meta($bot_id, '_aipkit_popup_icon_style', $icon_style);
        }

        if (isset($_POST['popup_icon_size'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $icon_size = sanitize_key(wp_unslash($_POST['popup_icon_size']));
            $allowed_sizes = ['small', 'medium', 'large', 'xlarge'];
            if (!in_array($icon_size, $allowed_sizes, true)) {
                $icon_size = BotSettingsManager::DEFAULT_POPUP_ICON_SIZE;
            }
            update_post_meta($bot_id, '_aipkit_popup_icon_size', $icon_size);
        }

        if (
            isset($_POST['popup_icon_type']) ||
            isset($_POST['popup_icon_default']) ||
            isset($_POST['popup_icon_custom_url'])
        ) {
            $current_icon_value = get_post_meta($bot_id, '_aipkit_popup_icon_value', true);
            if ($icon_type === null) {
                $icon_type = get_post_meta($bot_id, '_aipkit_popup_icon_type', BotSettingsManager::DEFAULT_POPUP_ICON_TYPE);
            }

            if ($icon_type === 'custom') {
                if (isset($_POST['popup_icon_custom_url'])) {
                    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
                    $icon_value = esc_url_raw(trim(wp_unslash($_POST['popup_icon_custom_url'])));
                } else {
                    $icon_value = filter_var($current_icon_value, FILTER_VALIDATE_URL) ? $current_icon_value : '';
                }
            } else {
                $allowed_defaults = ['chat-bubble', 'spark', 'openai', 'plus', 'question-mark'];
                if (isset($_POST['popup_icon_default'])) {
                    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
                    $icon_value = sanitize_key(wp_unslash($_POST['popup_icon_default']));
                } else {
                    $icon_value = $current_icon_value;
                }
                if (!in_array($icon_value, $allowed_defaults, true)) {
                    $icon_value = BotSettingsManager::DEFAULT_POPUP_ICON_VALUE;
                }
            }

            update_post_meta($bot_id, '_aipkit_popup_icon_value', $icon_value);
        }

        if (
            isset($_POST['header_avatar_type']) ||
            isset($_POST['header_avatar_default']) ||
            isset($_POST['header_avatar_url'])
        ) {
            $allowed_header_icons = ['chat-bubble', 'spark', 'openai', 'plus', 'question-mark'];
            $header_avatar_type = isset($_POST['header_avatar_type'])
                // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
                ? sanitize_key(wp_unslash($_POST['header_avatar_type']))
                : get_post_meta($bot_id, '_aipkit_header_avatar_type', BotSettingsManager::DEFAULT_HEADER_AVATAR_TYPE);
            if (!in_array($header_avatar_type, ['default', 'custom'], true)) {
                $header_avatar_type = BotSettingsManager::DEFAULT_HEADER_AVATAR_TYPE;
            }
            if (!isset($_POST['header_avatar_type']) && isset($_POST['header_avatar_url']) && !empty($_POST['header_avatar_url'])) {
                $header_avatar_type = 'custom';
            }

            if ($header_avatar_type === 'custom') {
                $header_avatar_url = isset($_POST['header_avatar_url'])
                    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
                    ? esc_url_raw(trim((string)wp_unslash($_POST['header_avatar_url'])))
                    : get_post_meta($bot_id, '_aipkit_header_avatar_url', BotSettingsManager::DEFAULT_HEADER_AVATAR_URL);
                $header_avatar_value = $header_avatar_url;
                update_post_meta($bot_id, '_aipkit_header_avatar_url', $header_avatar_url);
            } else {
                $header_avatar_default = isset($_POST['header_avatar_default'])
                    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
                    ? sanitize_key(wp_unslash($_POST['header_avatar_default']))
                    : get_post_meta($bot_id, '_aipkit_header_avatar_value', BotSettingsManager::DEFAULT_HEADER_AVATAR_VALUE);
                if (!in_array($header_avatar_default, $allowed_header_icons, true)) {
                    $header_avatar_default = BotSettingsManager::DEFAULT_HEADER_AVATAR_VALUE;
                }
                $header_avatar_value = $header_avatar_default;
                update_post_meta($bot_id, '_aipkit_header_avatar_url', '');
            }

            update_post_meta($bot_id, '_aipkit_header_avatar_type', $header_avatar_type);
            update_post_meta($bot_id, '_aipkit_header_avatar_value', $header_avatar_value);
        }

        if (isset($_POST['header_online_text'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $header_online_text = sanitize_text_field(wp_unslash($_POST['header_online_text']));
            update_post_meta($bot_id, '_aipkit_header_online_text', $header_online_text);
        }

        if (isset($_POST['popup_label_enabled'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $label_enabled = (wp_unslash($_POST['popup_label_enabled']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_popup_label_enabled', $label_enabled);
        }

        if (isset($_POST['popup_label_text'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $label_text = sanitize_text_field(wp_unslash($_POST['popup_label_text']));
            update_post_meta($bot_id, '_aipkit_popup_label_text', $label_text);
        }

        if (isset($_POST['popup_label_mode'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $label_mode = sanitize_key(wp_unslash($_POST['popup_label_mode']));
            $allowed_modes = ['always', 'on_delay', 'until_open', 'until_dismissed'];
            if (!in_array($label_mode, $allowed_modes, true)) {
                $label_mode = BotSettingsManager::DEFAULT_POPUP_LABEL_MODE;
            }
            update_post_meta($bot_id, '_aipkit_popup_label_mode', $label_mode);
        }

        if (isset($_POST['popup_label_size'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $label_size = sanitize_key(wp_unslash($_POST['popup_label_size']));
            $allowed_sizes = ['small', 'medium', 'large', 'xlarge'];
            if (!in_array($label_size, $allowed_sizes, true)) {
                $label_size = BotSettingsManager::DEFAULT_POPUP_LABEL_SIZE;
            }
            update_post_meta($bot_id, '_aipkit_popup_label_size', $label_size);
        }

        if (isset($_POST['popup_label_delay_seconds'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $label_delay = max(0, absint(wp_unslash($_POST['popup_label_delay_seconds'])));
            update_post_meta($bot_id, '_aipkit_popup_label_delay_seconds', $label_delay);
        }

        if (isset($_POST['popup_label_auto_hide_seconds'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $label_auto_hide = max(0, absint(wp_unslash($_POST['popup_label_auto_hide_seconds'])));
            update_post_meta($bot_id, '_aipkit_popup_label_auto_hide_seconds', $label_auto_hide);
        }

        if (isset($_POST['popup_label_dismissible'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $label_dismissible = (wp_unslash($_POST['popup_label_dismissible']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_popup_label_dismissible', $label_dismissible);
        }

        if (isset($_POST['popup_label_show_on_desktop'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $label_show_desktop = (wp_unslash($_POST['popup_label_show_on_desktop']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_popup_label_show_on_desktop', $label_show_desktop);
        }

        if (isset($_POST['popup_label_show_on_mobile'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $label_show_mobile = (wp_unslash($_POST['popup_label_show_on_mobile']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_popup_label_show_on_mobile', $label_show_mobile);
        }

        if (isset($_POST['popup_label_frequency'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $label_frequency = sanitize_key(wp_unslash($_POST['popup_label_frequency']));
            $allowed_frequency = ['once_per_visitor', 'once_per_session', 'always'];
            if (!in_array($label_frequency, $allowed_frequency, true)) {
                $label_frequency = BotSettingsManager::DEFAULT_POPUP_LABEL_FREQUENCY;
            }
            update_post_meta($bot_id, '_aipkit_popup_label_frequency', $label_frequency);
        }

        if (isset($_POST['popup_label_version'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $label_version = sanitize_text_field(wp_unslash($_POST['popup_label_version']));
            update_post_meta($bot_id, '_aipkit_popup_label_version', $label_version);
        }

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Updates chatbot deploy settings (popup/site-wide/embed) only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_deploy_settings()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        $updated_any = false;

        if (isset($_POST['popup_enabled'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $popup_enabled = (wp_unslash($_POST['popup_enabled']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_popup_enabled', $popup_enabled);
            $updated_any = true;
        }

        if (isset($_POST['site_wide_enabled'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $site_wide_enabled = (wp_unslash($_POST['site_wide_enabled']) === '1') ? '1' : '0';
            update_post_meta($bot_id, '_aipkit_site_wide_enabled', $site_wide_enabled);
            $updated_any = true;

            if (!class_exists(SiteWideBotManager::class)) {
                $site_wide_path = WPAICG_PLUGIN_DIR . 'classes/chat/storage/class-aipkit_site_wide_bot_manager.php';
                if (file_exists($site_wide_path)) {
                    require_once $site_wide_path;
                }
            }
            if (class_exists(SiteWideBotManager::class)) {
                $site_wide_manager = new SiteWideBotManager();
                $clear_cache = $site_wide_manager->ensure_site_wide_uniqueness(
                    $bot_id,
                    $site_wide_enabled === '1'
                );
                if ($clear_cache) {
                    $site_wide_manager->clear_site_wide_cache();
                }
            }
        }

        if (isset($_POST['embed_allowed_domains'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
            $raw_domains = trim(wp_unslash($_POST['embed_allowed_domains']));
            if ($raw_domains === '') {
                $sanitized_domains = '';
            } else {
                $domains_array = preg_split('/[\s,]+/', $raw_domains, -1, PREG_SPLIT_NO_EMPTY);
                $sanitized_list = [];
                foreach ($domains_array as $domain) {
                    $sanitized_url = esc_url_raw(trim($domain));
                    if (!empty($sanitized_url)) {
                        $sanitized_list[] = rtrim($sanitized_url, '/');
                    }
                }
                $sanitized_domains = implode("\n", array_unique($sanitized_list));
            }
            update_post_meta($bot_id, '_aipkit_embed_allowed_domains', $sanitized_domains);
            $updated_any = true;
        }

        if (!$updated_any) {
            wp_send_json_error(['message' => __('No changes to save.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Updates chatbot triggers JSON only (autosave).
     * @since NEXT_VERSION
     */
    public function ajax_update_chatbot_triggers()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $triggers_json = isset($_POST['triggers_json']) ? trim(wp_unslash($_POST['triggers_json'])) : '';

        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if ($triggers_json === '') {
            $triggers_json = '[]';
        }

        $decoded_triggers = json_decode($triggers_json, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            wp_send_json_error(['message' => __('Invalid JSON format for triggers.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!is_array($decoded_triggers)) {
            $triggers_json = '[]';
        }

        $trigger_meta_key = '_aipkit_chatbot_triggers';
        $trigger_storage_class_name = '\WPAICG\Lib\Chat\Triggers\AIPKit_Trigger_Storage';
        if (class_exists($trigger_storage_class_name)) {
            $trigger_meta_key = $trigger_storage_class_name::META_KEY;
        }

        update_post_meta($bot_id, $trigger_meta_key, $triggers_json);

        wp_send_json_success([
            'message' => __('Saved', 'gpt3-ai-content-generator'),
        ]);
    }

    /**
     * AJAX: Returns the training source count for a chatbot.
     * @since NEXT_VERSION
     */
    public function ajax_get_chatbot_training_source_count()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;
        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(BotSettingsManager::class)) {
            $manager_path = WPAICG_PLUGIN_DIR . 'classes/chat/storage/class-aipkit_bot_settings_manager.php';
            if (file_exists($manager_path)) {
                require_once $manager_path;
            }
        }

        $settings_manager = class_exists(BotSettingsManager::class) ? new BotSettingsManager() : null;
        $settings = $settings_manager ? $settings_manager->get_chatbot_settings($bot_id) : [];

        // Optional overrides from UI (for instant reflection before autosave completes).
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $override_enable_vector_store = isset($_POST['enable_vector_store'])
            ? sanitize_text_field(wp_unslash($_POST['enable_vector_store']))
            : null;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $override_provider = isset($_POST['vector_store_provider'])
            ? sanitize_key(wp_unslash($_POST['vector_store_provider']))
            : null;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $has_openai_override = isset($_POST['openai_vector_store_ids']);
        $override_openai_ids = $has_openai_override
            ? (array) wp_unslash($_POST['openai_vector_store_ids'])
            : null;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $has_pinecone_override = isset($_POST['pinecone_index_name']);
        $override_pinecone_index = $has_pinecone_override
            ? sanitize_text_field(wp_unslash($_POST['pinecone_index_name']))
            : null;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $has_qdrant_override = isset($_POST['qdrant_collection_names']);
        $override_qdrant_names = $has_qdrant_override
            ? (array) wp_unslash($_POST['qdrant_collection_names'])
            : null;

        $vector_store_enabled = $settings['enable_vector_store'] ?? BotSettingsManager::DEFAULT_ENABLE_VECTOR_STORE;
        if ($override_enable_vector_store !== null) {
            $vector_store_enabled = in_array($override_enable_vector_store, ['0', '1'], true)
                ? $override_enable_vector_store
                : $vector_store_enabled;
        }
        if ($vector_store_enabled !== '1') {
            wp_send_json_success([
                'count' => 0,
            ]);
            return;
        }

        $provider_key = $settings['vector_store_provider'] ?? BotSettingsManager::DEFAULT_VECTOR_STORE_PROVIDER;
        if ($override_provider && in_array($override_provider, ['openai', 'pinecone', 'qdrant'], true)) {
            $provider_key = $override_provider;
        }
        $provider_map = [
            'openai' => 'OpenAI',
            'pinecone' => 'Pinecone',
            'qdrant' => 'Qdrant',
        ];
        $provider_name = $provider_map[$provider_key] ?? 'OpenAI';

        $store_ids = [];
        if ($provider_key === 'openai') {
            if ($has_openai_override && is_array($override_openai_ids)) {
                $store_ids = array_filter(array_map('sanitize_text_field', $override_openai_ids));
            } else {
                $store_ids = $settings['openai_vector_store_ids'] ?? [];
            }
            if (!is_array($store_ids)) {
                $store_ids = json_decode((string) $store_ids, true);
            }
        } elseif ($provider_key === 'pinecone') {
            if ($has_pinecone_override) {
                $store_ids = !empty($override_pinecone_index) ? [$override_pinecone_index] : [];
            } else {
                $store_ids = !empty($settings['pinecone_index_name'])
                    ? [$settings['pinecone_index_name']]
                    : [];
            }
        } elseif ($provider_key === 'qdrant') {
            if ($has_qdrant_override && is_array($override_qdrant_names)) {
                $store_ids = array_filter(array_map('sanitize_text_field', $override_qdrant_names));
            } else {
                $store_ids = $settings['qdrant_collection_names'] ?? [];
            }
            if (!is_array($store_ids)) {
                $store_ids = json_decode((string) $store_ids, true);
            }
            if (empty($store_ids) && !empty($settings['qdrant_collection_name']) && !$has_qdrant_override) {
                $store_ids = [$settings['qdrant_collection_name']];
            }
        }

        $store_ids = array_filter(array_map('sanitize_text_field', (array) $store_ids));
        $store_ids = array_values(array_unique($store_ids));

        if (empty($store_ids)) {
            wp_send_json_success([
                'count' => 0,
            ]);
            return;
        }

        $cache_store_ids = $store_ids;
        sort($cache_store_ids, SORT_STRING);
        $cache_key = 'aipkit_training_count_' . md5($provider_name . '|' . implode(',', $cache_store_ids));
        $cached_stats = get_transient($cache_key);
        if (is_array($cached_stats)) {
            $cached_count = isset($cached_stats['count']) ? (int) $cached_stats['count'] : 0;
            wp_send_json_success([
                'count' => $cached_count,
            ]);
            return;
        }

        global $wpdb;
        $table_name = $wpdb->prefix . 'aipkit_vector_data_source';
        $placeholders = implode(',', array_fill(0, count($store_ids), '%s'));
        $params = array_merge([$provider_name], $store_ids);
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $table_name is safe and placeholders are prepared below.
        $count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$table_name} WHERE provider = %s AND vector_store_id IN ($placeholders) AND (post_id IS NOT NULL OR file_id IS NOT NULL OR indexed_content IS NOT NULL)",
                $params
            )
        );

        set_transient($cache_key, [
            'count' => $count,
        ], MINUTE_IN_SECONDS);

        wp_send_json_success([
            'count' => $count,
        ]);
    }

    /**
     * AJAX: Returns training source records for the active knowledge base.
     * @since NEXT_VERSION
     */
    public function ajax_get_chatbot_training_sources()
    {
        $permission_check = $this->check_module_access_permissions('chatbot');
        if (is_wp_error($permission_check)) {
            $this->send_wp_error($permission_check);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $bot_id = isset($_POST['bot_id']) ? absint($_POST['bot_id']) : 0;
        if (empty($bot_id)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(AdminSetup::class)) {
            wp_send_json_error(['message' => __('Internal server error.', 'gpt3-ai-content-generator')], 500);
            return;
        }

        if (get_post_type($bot_id) !== AdminSetup::POST_TYPE || !in_array(get_post_status($bot_id), ['publish', 'draft'], true)) {
            wp_send_json_error(['message' => __('Invalid Chatbot ID.', 'gpt3-ai-content-generator')], 400);
            return;
        }

        if (!class_exists(BotSettingsManager::class)) {
            $manager_path = WPAICG_PLUGIN_DIR . 'classes/chat/storage/class-aipkit_bot_settings_manager.php';
            if (file_exists($manager_path)) {
                require_once $manager_path;
            }
        }

        $settings_manager = class_exists(BotSettingsManager::class) ? new BotSettingsManager() : null;
        $settings = $settings_manager ? $settings_manager->get_chatbot_settings($bot_id) : [];

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $override_enable_vector_store = isset($_POST['enable_vector_store'])
            ? sanitize_text_field(wp_unslash($_POST['enable_vector_store']))
            : null;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $override_provider = isset($_POST['vector_store_provider'])
            ? sanitize_key(wp_unslash($_POST['vector_store_provider']))
            : null;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $has_openai_override = isset($_POST['openai_vector_store_ids']);
        $override_openai_ids = $has_openai_override
            ? (array) wp_unslash($_POST['openai_vector_store_ids'])
            : null;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $has_pinecone_override = isset($_POST['pinecone_index_name']);
        $override_pinecone_index = $has_pinecone_override
            ? sanitize_text_field(wp_unslash($_POST['pinecone_index_name']))
            : null;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $has_qdrant_override = isset($_POST['qdrant_collection_names']);
        $override_qdrant_names = $has_qdrant_override
            ? (array) wp_unslash($_POST['qdrant_collection_names'])
            : null;

        $vector_store_enabled = $settings['enable_vector_store'] ?? BotSettingsManager::DEFAULT_ENABLE_VECTOR_STORE;
        if ($override_enable_vector_store !== null) {
            $vector_store_enabled = in_array($override_enable_vector_store, ['0', '1'], true)
                ? $override_enable_vector_store
                : $vector_store_enabled;
        }
        if ($vector_store_enabled !== '1') {
            wp_send_json_success([
                'logs' => [],
                'pagination' => [
                    'total_logs' => 0,
                    'total_pages' => 0,
                    'current_page' => 1,
                ],
            ]);
            return;
        }

        $provider_key = $settings['vector_store_provider'] ?? BotSettingsManager::DEFAULT_VECTOR_STORE_PROVIDER;
        if ($override_provider && in_array($override_provider, ['openai', 'pinecone', 'qdrant'], true)) {
            $provider_key = $override_provider;
        }
        $provider_map = [
            'openai' => 'OpenAI',
            'pinecone' => 'Pinecone',
            'qdrant' => 'Qdrant',
        ];
        $provider_label = $provider_map[$provider_key] ?? '';
        if (!$provider_label) {
            wp_send_json_success([
                'logs' => [],
                'pagination' => [
                    'total_logs' => 0,
                    'total_pages' => 0,
                    'current_page' => 1,
                ],
            ]);
            return;
        }

        $store_ids = [];
        if ($provider_key === 'openai') {
            $store_ids = is_array($override_openai_ids)
                ? array_filter(array_map('sanitize_text_field', $override_openai_ids))
                : array_filter((array) ($settings['openai_vector_store_ids'] ?? []));
        } elseif ($provider_key === 'pinecone') {
            $store_id = $override_pinecone_index !== null
                ? $override_pinecone_index
                : ($settings['pinecone_index_name'] ?? '');
            if ($store_id) {
                $store_ids = [sanitize_text_field($store_id)];
            }
        } elseif ($provider_key === 'qdrant') {
            $store_ids = is_array($override_qdrant_names)
                ? array_filter(array_map('sanitize_text_field', $override_qdrant_names))
                : array_filter((array) ($settings['qdrant_collection_names'] ?? []));
        }

        if (empty($store_ids)) {
            wp_send_json_success([
                'logs' => [],
                'pagination' => [
                    'total_logs' => 0,
                    'total_pages' => 0,
                    'current_page' => 1,
                ],
            ]);
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $page = isset($_POST['page']) ? max(1, absint($_POST['page'])) : 1;
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 10;
        $per_page = min(50, max(1, $per_page));
        $offset = ($page - 1) * $per_page;

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification is handled in check_module_access_permissions method.
        $status_filter = isset($_POST['status']) ? sanitize_key(wp_unslash($_POST['status'])) : '';
        $allowed_statuses = ['indexed', 'failed', 'processing', 'queued', 'skipped_already_indexed'];
        if ($status_filter && !in_array($status_filter, $allowed_statuses, true)) {
            $status_filter = '';
        }

        global $wpdb;
        $table_name = $wpdb->prefix . 'aipkit_vector_data_source';

        $where_clauses = ['provider = %s'];
        $params = [$provider_label];

        $store_placeholders = implode(',', array_fill(0, count($store_ids), '%s'));
        $where_clauses[] = "vector_store_id IN ($store_placeholders)";
        $params = array_merge($params, $store_ids);

        if ($status_filter) {
            $where_clauses[] = 'status = %s';
            $params[] = $status_filter;
        }

        if ($search) {
            $like = '%' . $wpdb->esc_like($search) . '%';
            $where_clauses[] = '(message LIKE %s OR post_title LIKE %s OR file_id LIKE %s OR vector_store_name LIKE %s OR indexed_content LIKE %s)';
            $params = array_merge($params, array_fill(0, 5, $like));
        }

        $where_sql = implode(' AND ', $where_clauses);

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $table_name is safe.
        $total_logs = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table_name} WHERE {$where_sql}", $params));

        $logs_params = array_merge($params, [$per_page, $offset]);
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $table_name is safe.
        $logs = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT id, timestamp, status, message, indexed_content, post_id, post_title, file_id, batch_id, embedding_provider, embedding_model, vector_store_id, vector_store_name FROM {$table_name} WHERE {$where_sql} ORDER BY timestamp DESC LIMIT %d OFFSET %d",
                $logs_params
            ),
            ARRAY_A
        );

        $total_pages = $per_page > 0 ? (int) ceil($total_logs / $per_page) : 0;

        wp_send_json_success([
            'logs' => $logs ?: [],
            'pagination' => [
                'total_logs' => $total_logs,
                'total_pages' => $total_pages,
                'current_page' => $page,
            ],
            'provider' => $provider_label,
        ]);
    }
}
