# mxchat-basic/2.0.5/includes/class-mxchat-integrator.php

MxChat – AI Chatbot &amp; Content Generation for WordPress, version 2.0.5. 3,455 lines.

- Page: https://pluginprobe.com/plugins/mxchat-basic/2.0.5/code/includes/class-mxchat-integrator.php
- Raw: https://pluginprobe.com/plugins/mxchat-basic/2.0.5/raw/includes/class-mxchat-integrator.php
- Modified: 2025-02-21T18:16:54+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/mxchat-basic/2.0.5/code/includes/class-mxchat-integrator.php#L10-L20`.

````php
<?php
if (!defined('ABSPATH')) {
    exit;
}

class MxChat_Integrator {
    private $options;
    private $prompts_options;
    private $chat_count;
    private $fallbackResponse;
    private $productCardHtml;
    private $word_handler;

public function __construct() {
    $this->options = get_option('mxchat_options');
    $this->prompts_options = get_option('mxchat_prompts_options', array());

    $this->chat_count = get_option('mxchat_chat_count', 0);
    $this->word_handler = new MXChat_Word_Handler($this->options);

    add_action('wp_enqueue_scripts', array($this, 'mxchat_enqueue_scripts_styles'));
    add_action('wp_ajax_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request'));
    add_action('wp_ajax_nopriv_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request'));

    add_action('wp_ajax_mxchat_dismiss_pre_chat_message', array($this, 'mxchat_dismiss_pre_chat_message'));
    add_action('wp_ajax_nopriv_mxchat_dismiss_pre_chat_message', array($this, 'mxchat_dismiss_pre_chat_message'));
    // Add the AJAX actions for checking if the pre-chat message was dismissed
    add_action('wp_ajax_mxchat_check_pre_chat_message_status', array($this, 'mxchat_check_pre_chat_message_status'));
    add_action('wp_ajax_nopriv_mxchat_check_pre_chat_message_status', array($this, 'mxchat_check_pre_chat_message_status'));

    add_action('wp_ajax_mxchat_fetch_conversation_history', [$this, 'mxchat_fetch_conversation_history']);
    add_action('wp_ajax_nopriv_mxchat_fetch_conversation_history', [$this, 'mxchat_fetch_conversation_history']);

    add_action('wp_ajax_mxchat_add_to_cart', [$this, 'mxchat_add_to_cart']);
    add_action('wp_ajax_nopriv_mxchat_add_to_cart', [$this, 'mxchat_add_to_cart']);

    if (!wp_next_scheduled('mxchat_reset_rate_limits')) {
        wp_schedule_event(time(), 'daily', 'mxchat_reset_rate_limits');
    }

    // Add REST API routes registration
    add_action('rest_api_init', array($this, 'register_routes'));

    add_action('wp_ajax_mxchat_fetch_new_messages', array($this, 'mxchat_fetch_new_messages'));
    add_action('wp_ajax_nopriv_mxchat_fetch_new_messages', array($this, 'mxchat_fetch_new_messages'));

    add_action('mxchat_reset_rate_limits', array($this, 'mxchat_reset_rate_limits'));

    add_action('wp_ajax_mxchat_upload_pdf', [$this, 'handle_pdf_upload']);
    add_action('wp_ajax_nopriv_mxchat_upload_pdf', [$this, 'handle_pdf_upload']);
    add_action('wp_ajax_mxchat_remove_pdf', [$this, 'handle_pdf_remove']);
    add_action('wp_ajax_nopriv_mxchat_remove_pdf', [$this, 'handle_pdf_remove']);

    // Add these with your other add_action hooks
    add_action('wp_ajax_mxchat_upload_word', array($this, 'mxchat_handle_word_upload'));
    add_action('wp_ajax_nopriv_mxchat_upload_word', array($this, 'mxchat_handle_word_upload'));
    add_action('wp_ajax_mxchat_remove_word', array($this, 'mxchat_handle_word_remove'));
    add_action('wp_ajax_nopriv_mxchat_remove_word', array($this, 'mxchat_handle_word_remove'));
    add_action('wp_ajax_mxchat_check_word_status', array($this, 'mxchat_check_word_status'));
    add_action('wp_ajax_nopriv_mxchat_check_word_status', array($this, 'mxchat_check_word_status'));

    add_action('wp_ajax_nopriv_mxchat_handle_save_email_and_response', [$this, 'mxchat_handle_save_email_and_response']);
    add_action('wp_ajax_mxchat_handle_save_email_and_response', [$this, 'mxchat_handle_save_email_and_response']);
    add_action('wp_ajax_nopriv_mxchat_check_email_provided', [$this, 'mxchat_check_email_provided']);
    add_action('wp_ajax_mxchat_check_email_provided', [$this, 'mxchat_check_email_provided']);
}


    private function mxchat_increment_chat_count() {
        $chat_count = get_option('mxchat_chat_count', 0);
        $chat_count++;
        update_option('mxchat_chat_count', $chat_count);
    }

function mxchat_fetch_conversation_history() {
    if (empty($_POST['session_id'])) {
        wp_send_json_error(['message' => esc_html__('Session ID missing.', 'mxchat')]);
        wp_die();
    }

    $session_id = sanitize_text_field($_POST['session_id']);
    $history = get_option("mxchat_history_{$session_id}", []); // Retrieve stored history
    $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai'); // Get current chat mode

    if (empty($history)) {
        // Even if history is empty, return the chat mode
        wp_send_json_success([
            'conversation' => [],
            'chat_mode' => $chat_mode
        ]);
        wp_die();
    }

    wp_send_json_success([
        'conversation' => $history,
        'chat_mode' => $chat_mode
    ]);
    wp_die();
}
private function mxchat_fetch_conversation_history_for_ajax($session_id) {
    $history = get_option("mxchat_history_{$session_id}", []); // Retrieve stored history based on session ID
    $formatted_history = [];

    // Format the history to align with the expected structure for OpenAI
    foreach ($history as $entry) {
        $formatted_history[] = [
            'role' => $entry['role'],  // Ensure this matches 'user' or 'assistant'
            'content' => $entry['content']
        ];
    }

    return $formatted_history;
}


private function mxchat_fetch_conversation_history_for_ai($session_id) {
    $history = get_option("mxchat_history_{$session_id}", []);
    $formatted_history = [];

    // Adjusted for code-heavy conversations
    $max_tokens = 120000;    // Context window size
    $reserved_tokens = 5000; // Space for system prompts + current query
    $current_token_count = 0;

    // Allowed HTML tags for content sanitization
    $allowed_tags = [
        'pre' => ['class' => true],
        'code' => ['class' => true],
        'span' => ['class' => true],
        'div' => ['class' => true],
        'strong' => [],
        'em' => []
    ];

    foreach (array_reverse($history) as $entry) {
        // Preserve code blocks while sanitizing other HTML
        $clean_content = wp_kses($entry['content'], $allowed_tags);

        // Detect code blocks in content
        $has_code = false;
// Replace the HTML check with:
// Allow messages that contain code blocks or are plain text
if (strpos($clean_content, '<pre') === false &&
    strpos($clean_content, '<code') === false &&
    $clean_content !== strip_tags($entry['content'])) {
    continue;
}

        // Skip entries that lost significant content during sanitization
        if (!$has_code && $clean_content !== strip_tags($entry['content'])) {
            continue;
        }

        // More accurate token estimation (1 token ≈ 4 characters)
        $token_estimate = ceil(mb_strlen($clean_content, 'UTF-8') / 4);

        // Check token budget with the new estimate
        if (($current_token_count + $token_estimate + $reserved_tokens) > $max_tokens) {
            // Try to fit partial content if it's the first entry
            if (empty($formatted_history)) {
                $clean_content = mb_substr($clean_content, 0, ($max_tokens - $reserved_tokens) * 4);
                $token_estimate = ceil(mb_strlen($clean_content, 'UTF-8') / 4);
            } else {
                break;
            }
        }

        // Add to formatted history
        $formatted_history[] = [
            'role' => $entry['role'],
            'content' => $clean_content
        ];

        $current_token_count += $token_estimate;
    }

    // Reverse back to maintain chronological order
    $formatted_history = array_reverse($formatted_history);

    // Add system message about code context
    array_unshift($formatted_history, [
        'role' => 'system',
        'content' => 'Preserved code blocks are marked with [CODE BLOCK PRESERVED]. '
                    . 'Maintain formatting and syntax highlighting when referencing code.'
    ]);

    return $formatted_history;
}

public function register_routes() {
    //error_log(esc_html__('Registering MxChat REST routes', 'mxchat'));

    register_rest_route('mxchat/v1', '/stream', [
        'methods'  => 'GET',
        'callback' => [$this, 'mxchat_stream_events'],
        'permission_callback' => [$this, 'verify_chat_session'],
    ]);

    register_rest_route('mxchat/v1', '/agent-response', [
        'methods'  => 'POST',
        'callback' => [$this, 'mxchat_handle_agent_response'],
        'permission_callback' => [$this, 'verify_slack_request'],
    ]);

    register_rest_route('mxchat/v1', '/slack-interaction', [
        'methods'  => 'POST',
        'callback' => [$this, 'handle_slack_interaction'],
        'permission_callback' => [$this, 'verify_slack_request'],
    ]);

    //error_log(esc_html__('MxChat REST routes registered', 'mxchat'));
}

/**
 * Verify valid chat session
 */
public function verify_chat_session($request) {
    $session_id = $request->get_param('session_id');
    if (empty($session_id)) {
        //error_log(esc_html__('Empty session ID in chat request', 'mxchat'));
        return false;
    }

    $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai');
    return $chat_mode === 'agent';
}

/**
 * Verify request is coming from Slack.
 *
 * @param WP_REST_Request $request
 * @return bool True if valid, false otherwise.
 */
public function verify_slack_request($request) {
    // Get the Slack signing secret from your plugin options
    $valid_key = $this->options['live_agent_secret_key'] ?? '';

    if (empty($valid_key)) {
        //error_log(esc_html__('Slack signing secret not configured', 'mxchat'));
        return false;
    }

    $timestamp = $request->get_header('X-Slack-Request-Timestamp');
    $slack_signature = $request->get_header('X-Slack-Signature');

    // Verify timestamp to prevent replay attacks
    if (abs(time() - intval($timestamp)) > 300) {
        //error_log(esc_html__('Slack request timestamp too old', 'mxchat'));
        return false;
    }

    // Get raw request body
    $request_body = file_get_contents('php://input');

    // Create the signature base string
    $sig_basestring = "v0:{$timestamp}:{$request_body}";

    // Calculate expected signature
    $my_signature = 'v0=' . hash_hmac('sha256', $sig_basestring, $valid_key);

    // Compare signatures
    return hash_equals($my_signature, $slack_signature);
}
public function mxchat_stream_events(WP_REST_Request $request) {
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    header('Connection: keep-alive');

    $session_id = sanitize_text_field($request->get_param('session_id'));
    $last_seen_id = sanitize_text_field($request->get_param('last_seen_id')) ?: '';

    if (empty($session_id)) {
        echo esc_html__("event: error\ndata: ", 'mxchat') . esc_html__('Missing session_id', 'mxchat') . "\n\n";
        flush();
        exit;
    }

    $history = get_option("mxchat_history_{$session_id}", []);

    // Filter only new messages
    $new_messages = array_filter($history, function ($message) use ($last_seen_id) {
        return !empty($message['id']) && $message['id'] > $last_seen_id;
    });

    // Send new messages if available
    if (!empty($new_messages)) {
        echo esc_html__("event: newMessages\ndata: ", 'mxchat') . json_encode(array_values($new_messages)) . "\n\n";
    } else {
        // Keep the connection alive
        echo esc_html__("event: keepAlive\ndata: ", 'mxchat') . "{}\n\n";
    }
    flush();
    exit;
}




private function mxchat_save_chat_message($session_id, $role, $message) {
    global $wpdb;

    $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
    //error_log("[DEBUG] mxchat_save_chat_message -> START for session_id: {$session_id}, role: {$role}");

    // 1) Extract agent name if present
    $agent_name = '';
    if (preg_match('/^Agent: (.*?) - /', $message, $matches)) {
        $agent_name = $matches[1];
        $message    = str_replace("Agent: $agent_name - ", '', $message);

        $session_meta_key = "mxchat_agent_name_{$session_id}";
        if (empty(get_option($session_meta_key))) {
            update_option($session_meta_key, $agent_name);
            //error_log("[DEBUG] mxchat_save_chat_message -> Stored agent_name in option: {$session_meta_key} => {$agent_name}");
        }
    }

    // 2) Generate unique message_id
    $message_id = uniqid();
    //error_log("[DEBUG] mxchat_save_chat_message -> Generated message_id: {$message_id}");

    // 3) Determine user_id
    $user_id = is_user_logged_in() ? get_current_user_id() : 0;

    // 4) Determine user_identifier
    $user_identifier = $agent_name
        ? $agent_name
        : MxChat_User::mxchat_get_user_identifier();

    // 5) Determine displayed_name
    $user_email = MxChat_User::mxchat_get_user_email();
    $displayed_name = $agent_name ? $agent_name : ($user_email ?: $user_identifier);

    // 6) Check for a saved email in wp_options
    $email_option_key = "mxchat_email_{$session_id}";
    $saved_email = get_option($email_option_key);
    //error_log("[DEBUG] mxchat_save_chat_message -> Checking wp_options for email_option_key: {$email_option_key}, found: {$saved_email}");

    // If found, update DB user_email
    if ($saved_email) {
        $update_res = $wpdb->update(
            $table_name,
            ['user_email' => $saved_email],
            ['session_id' => $session_id],
            ['%s'],
            ['%s']
        );
        //error_log("[DEBUG] mxchat_save_chat_message -> Attempted DB user_email update for session_id {$session_id}. update_res: {$update_res}");
    }

    // 7) Save to session history in wp_options
    $history_key = "mxchat_history_{$session_id}";
    $history = get_option($history_key, []);
    $history[] = [
        'id' => $message_id,
        'role' => $role,
        'content' => $message,
        'timestamp' => round(microtime(true) * 1000),
        'agent_name' => $displayed_name,
    ];
    update_option($history_key, $history);
    //error_log("[DEBUG] mxchat_save_chat_message -> Updated session history in option: {$history_key}");

    // 8) Save the message to DB (INSERT)
    $insert_data = [
        'user_id'        => $user_id,
        'user_identifier'=> $user_identifier,
        'user_email'     => $saved_email ?: $user_email,
        'session_id'     => $session_id,
        'role'           => $role,
        'message'        => $message,
        'timestamp'      => current_time('mysql', 1),
    ];
    $wpdb->insert($table_name, $insert_data);
    //error_log("[DEBUG] mxchat_save_chat_message -> Inserted message into DB. row_id: {$wpdb->insert_id}, data: " . print_r($insert_data, true));

    //error_log("[DEBUG] mxchat_save_chat_message -> END for session_id: {$session_id}");
    return $message_id;
}

public function mxchat_handle_save_email_and_response() {
    //error_log('[DEBUG] ---------- mxchat_handle_save_email_and_response START ----------');

    // Validate nonce
    if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_chat_nonce')) {
        error_log(esc_html__('[ERROR] Invalid nonce in mxchat_handle_save_email_and_response', 'mxchat'));
        wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'mxchat')]);
        wp_die();
    }

    $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
    $email      = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';

    //error_log("[DEBUG] handle_save_email_and_response -> session_id: {$session_id}, email: {$email}");

    if (empty($session_id) || empty($email)) {
        //error_log("[ERROR] Missing session_id or email: session_id={$session_id}, email={$email}");
        wp_send_json_error(['message' => esc_html__('Session ID or email is missing.', 'mxchat')]);
        wp_die();
    }

    // 1) Always store in wp_options
    $option_key = "mxchat_email_{$session_id}";
    update_option($option_key, $email);
    //error_log("[DEBUG] handle_save_email_and_response -> updated option: {$option_key} => {$email}");

    // 2) (Optional) Also store in DB if a row already exists
    global $wpdb;
    $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';

    // Make sure we have a valid placeholder in prepare
    $sql = $wpdb->prepare("SELECT COUNT(*) FROM {$table_name} WHERE session_id = %s", $session_id);
    $session_count = $wpdb->get_var($sql);

    //error_log("[DEBUG] handle_save_email_and_response -> session_count for {$session_id}: {$session_count} (SQL: {$sql})");

    if ($session_count) {
        // Update user_email if row(s) exist
        $update_sql = $wpdb->prepare(
            "UPDATE {$table_name} SET user_email = %s WHERE session_id = %s",
            $email,
            $session_id
        );
        $wpdb->query($update_sql);
        //error_log("[DEBUG] handle_save_email_and_response -> DB updated: {$update_sql}");
    } else {
        //error_log("[INFO] handle_save_email_and_response -> No DB entry for {$session_id}, so email is only in wp_options.");
    }

    // Provide success response
    $bot_message = __('Thanks for providing your email! You can continue chatting now.', 'mxchat');
    //error_log("[DEBUG] handle_save_email_and_response -> success, returning bot_message: {$bot_message}");
    wp_send_json_success(['message' => $bot_message]);
    wp_die();
}

public function mxchat_check_email_provided() {
    //error_log('[DEBUG] ---------- mxchat_check_email_provided START ----------');

    if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_chat_nonce')) {
        //error_log('[ERROR] Invalid nonce in mxchat_check_email_provided');
        wp_send_json_error(['message' => esc_html__('Invalid nonce', 'mxchat')]);
    }

    $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
    if (empty($session_id)) {
        //error_log('[ERROR] No session ID provided in mxchat_check_email_provided');
        wp_send_json_error(['message' => esc_html__('No session ID provided', 'mxchat')]);
    }

    // Check if the user is logged in
    if (is_user_logged_in()) {
        $current_user = wp_get_current_user();
        //error_log("[DEBUG] User is logged in as {$current_user->user_email}");
        wp_send_json_success(['logged_in' => true, 'email' => $current_user->user_email]);
    }

    $option_key = "mxchat_email_{$session_id}";
    $stored_email = get_option($option_key, '');

    //error_log("[DEBUG] mxchat_check_email_provided -> Checking option: {$option_key}, found: {$stored_email}");

    if (!empty($stored_email)) {
        //error_log("[DEBUG] mxchat_check_email_provided -> Email found, returning success");
        wp_send_json_success(['email' => $stored_email]);
    } else {
        //error_log("[DEBUG] mxchat_check_email_provided -> No email found, returning error");
        wp_send_json_error(['message' => esc_html__('No email found', 'mxchat')]);
    }
}


// First, add this helper function to get the highest rate limit for a user's roles
private function get_user_role_rate_limit($user_id) {
    //error_log(esc_html__("Checking rate limit for user ID: ", 'mxchat') . $user_id);

    if (!$user_id) {
        //error_log(esc_html__("No user ID provided, using logged-out limit: ", 'mxchat') . ($this->options['rate_limit_logged_out'] ?? '10'));
        return $this->options['rate_limit_logged_out'] ?? '10';
    }

    $user = get_userdata($user_id);
    if (!$user || !$user->roles) {
        //error_log(esc_html__("No user data or roles found, using logged-out limit: ", 'mxchat') . ($this->options['rate_limit_logged_out'] ?? '10'));
        return $this->options['rate_limit_logged_out'] ?? '10';
    }

    //error_log(esc_html__("User roles: ", 'mxchat') . print_r($user->roles, true));
    //error_log(esc_html__("Available role rate limits: ", 'mxchat') . print_r($this->options['role_rate_limits'] ?? [], true));

    $max_limit = 0;
    foreach ($user->roles as $role) {
        //error_log(esc_html__("Checking limit for role: ", 'mxchat') . $role);
        if (isset($this->options['role_rate_limits'][$role])) {
            $role_limit = $this->options['role_rate_limits'][$role];
            //error_log(esc_html__("Found limit for role ", 'mxchat') . $role . esc_html__(": ", 'mxchat') . $role_limit);

            if ($role_limit === 'unlimited') {
                //error_log(esc_html__("Returning unlimited for role: ", 'mxchat') . $role);
                return 'unlimited';
            }

            $max_limit = max($max_limit, (int)$role_limit);
            //error_log(esc_html__("Current max limit: ", 'mxchat') . $max_limit);
        } else {
            //error_log(esc_html__("No limit found for role: ", 'mxchat') . $role);
        }
    }

    $final_limit = $max_limit > 0 ? (string)$max_limit : '100';
    //error_log(esc_html__("Final rate limit: ", 'mxchat') . $final_limit);
    return $final_limit;
}


// Add this to your plugin's main PHP file
public function mxchat_check_new_messages() {
    if (!isset($_POST['session_id']) || !isset($_POST['last_seen_id'])) {
        wp_send_json_error(['message' => 'Missing required parameters']);
        wp_die();
    }

    $session_id = sanitize_text_field($_POST['session_id']);
    $last_seen_id = sanitize_text_field($_POST['last_seen_id']);

    // Get chat history
    $history = get_option("mxchat_history_{$session_id}", []);

    if (empty($history)) {
        wp_send_json_success([
            'hasNewMessages' => false,
            'new_messages' => []
        ]);
        wp_die();
    }

    // Filter new messages
    $new_messages = array_filter($history, function($message) use ($last_seen_id) {
        return isset($message['id']) && $message['id'] > $last_seen_id;
    });

    // Sort by ID to ensure proper order
    usort($new_messages, function($a, $b) {
        return $a['id'] <=> $b['id'];
    });

    wp_send_json_success([
        'hasNewMessages' => !empty($new_messages),
        'new_messages' => array_values($new_messages),
        'latestMessageId' => end($new_messages)['id'] ?? $last_seen_id
    ]);
    wp_die();
}

public function mxchat_handle_chat_request() {
    global $wpdb;


        // Check if MX Chat Moderation is active
    if (class_exists('MX_Chat_Moderation')) {
        // Get user email and IP
        $user_email = '';
        $user_ip = $_SERVER['REMOTE_ADDR'];

        // If user is logged in, get their email
        if (is_user_logged_in()) {
            $current_user = wp_get_current_user();
            $user_email = $current_user->user_email;
        }

        // Create ban handler instance
        $ban_handler = new MX_Chat_Ban_Handler();

        // Check if user is banned by IP
        if ($ban_handler->check_ban($user_ip, 'ip')) {
            wp_send_json([
                'success' => false,
                'message' => esc_html__('Access denied. Your IP address has been banned.', 'mxchat'),
                'status' => 'banned'
            ]);
            wp_die();
        }

        // If user is logged in, also check email
        if (!empty($user_email) && $ban_handler->check_ban($user_email, 'email')) {
            wp_send_json([
                'success' => false,
                'message' => esc_html__('Access denied. Your email address has been banned.', 'mxchat'),
                'status' => 'banned'
            ]);
            wp_die();
        }
    }


        // Reset fallback response at the start of each request
    $this->fallbackResponse = ['text' => '', 'html' => '', 'images' => []];
    $this->productCardHtml = '';

     // Get the actual WordPress user ID if logged in
    $is_logged_in = is_user_logged_in();
    if ($is_logged_in) {
        $user_id = get_current_user_id(); // This will get the actual WordPress user ID
    } else {
        // For logged-out users, use your existing identifier method
        $user_id = $this->mxchat_get_user_identifier();
    }

    // Get and sanitize the user identifier
    $user_id = sanitize_key($user_id);

    // Determine if user is logged in
    $is_logged_in = is_user_logged_in();
        //error_log("User logged in status: " . ($is_logged_in ? 'true' : 'false'));

    // Get rate limit based on user status
 // Get rate limit based on user status
    $rate_limit = $is_logged_in
        ? $this->get_user_role_rate_limit($user_id)
        : ($this->options['rate_limit_logged_out'] ?? '10');

    //error_log("Selected rate limit: " . $rate_limit);

    // Rest of your code remains the same
    // If rate limit is 'unlimited', skip rate limiting checks
    if ($rate_limit !== 'unlimited') {
        // Convert rate limit to integer
        $rate_limit = intval($rate_limit);
        // Setup rate limiting
        $rate_limit_transient_key = 'mxchat_chat_limit_' . $user_id;
        $chat_count = get_transient($rate_limit_transient_key);
        if ($chat_count === false) {
            // Initialize new counter if none exists
            $chat_count = 0;
        }
        // Check if user has exceeded their rate limit
        if ($chat_count >= $rate_limit) {
            // Get custom rate limit message or use default
            $rate_limit_message = isset($this->options['rate_limit_message'])
                ? $this->options['rate_limit_message']
                : esc_html__('Rate limit exceeded. Please try again later.', 'mxchat');
            // Replace placeholder if it exists in the message
            $rate_limit_message = str_replace(
                array('{limit}', '{count}', '{remaining}'),
                array($rate_limit, $chat_count, max(0, $rate_limit - $chat_count)),
                $rate_limit_message
            );
            wp_send_json([
                'success' => false,
                'message' => $rate_limit_message,
                'status' => 'rate_limit_exceeded',
                'limit' => $rate_limit,
                'count' => $chat_count
            ]);
            wp_die();
        }
        // Increment the counter
        $chat_count++;
        // Store the updated count with 24-hour expiration
        set_transient($rate_limit_transient_key, $chat_count, DAY_IN_SECONDS);
    }

    // Rest of your existing code...
    $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
    //error_log("Session ID: $session_id");

    if (empty($session_id)) {
        //error_log("Error: Session ID is missing.");
        wp_send_json_error(esc_html__('Session ID is missing.', 'mxchat'));
        wp_die();
    }

    // Validate and sanitize the incoming message
    if (empty($_POST['message'])) {
        //error_log("Error: No message received.");
        wp_send_json_error(esc_html__('No message received.', 'mxchat'));
        wp_die();
    }


// Modify the message sanitization to preserve PHP tags in code blocks
$allowed_tags = [
    'pre' => [],
    'code' => ['class' => true],
    'span' => ['class' => true],
    'div' => ['class' => true],
];

// First preserve code blocks
$message = preg_replace_callback('/<pre><code.*?>.*?<\/code><\/pre>/s', function($matches) {
    return htmlspecialchars_decode($matches[0]);
}, $_POST['message']);

// Then apply sanitization
$message = wp_kses($message, $allowed_tags);

// Decode code blocks
$message = preg_replace_callback('/(&lt;pre&gt;&lt;code.*?&gt;.*?&lt;\/code&gt;&lt;\/pre&gt;)/s', function($matches) {
    return htmlspecialchars_decode($matches[1]);
}, $message);

$message = trim($message);

// Preserve code blocks from markdown conversion
$message = preg_replace('/```(\w+)?\s*([\s\S]+?)```/s', '<pre><code class="$1">$2</code></pre>', $message);

    // Save the user's message
    $this->mxchat_save_chat_message($session_id, 'user', $message);

    // Check if the message is an email address
    if (is_email($message)) {
        // Add the email to Loops
        $this->add_email_to_loops($message);

        // Send success response
        $response_message = $this->options['email_capture_response'] ??
                           esc_html__('Thank you! Your coupon is on the way!', 'mxchat');

        wp_send_json([
            'success' => true,
            'status' => 'email_captured',
            'message' => $response_message
        ]);
        wp_die();
    }

    $intent_info = '';

    // Check chat mode
    $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai');
    //error_log("Chat Mode: $chat_mode");

    // Handle agent mode
    if ($chat_mode === 'agent') {
        // First, check for switch intent before doing anything else
        $intent_matched = $this->mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id);

        // If we matched an intent and it's the switch intent, handle it
        if ($intent_matched && !empty($this->fallbackResponse['text'])) {
            //error_log("Switch to chatbot intent detected");

            // Update chat mode first
            update_option("mxchat_mode_{$session_id}", 'ai');

            // Clear any existing PDF context to start fresh
            $this->clear_pdf_transients($session_id);

            // Prepare clean switch response
            $response_data = [
                'text' => $this->fallbackResponse['text'],
                'html' => '',
                'session_id' => $session_id,
                'chat_mode' => 'ai'
            ];

            // Save the mode switch message
            $this->mxchat_save_chat_message($session_id, 'system', esc_html__('Switched to AI chat mode', 'mxchat'));
            $this->mxchat_save_chat_message($session_id, 'bot', $this->fallbackResponse['text']);

            // Send response and exit
            wp_send_json($response_data);
            wp_die();
        } elseif (!$intent_matched) {
            // No intent matched, handle live agent message
            try {
                $this->mxchat_send_user_message_to_agent($message, $user_id, $session_id);
                //error_log("Message sent to agent.");

                wp_send_json_success([
                    'status' => 'waiting_for_agent',
                    'message' => esc_html__('Message sent to live agent.', 'mxchat')
                ]);
            } catch (\Exception $e) {
                //error_log("Error sending message to agent: " . $e->getMessage());
                wp_send_json_error(esc_html__('Failed to send message to agent', 'mxchat'));
            }
            wp_die();
        }
    }

        // Step 1: Check for new PDF URL in the message
        if (preg_match('/https?:\/\/[^\s"]+/i', $message, $matches)) {
            $new_pdf_url = $matches[0];

            // Check if this is likely a PDF-related request
            $pdf_keywords = ['pdf', 'document', 'read', 'analyze'];
            $is_pdf_request = false;

            foreach ($pdf_keywords as $keyword) {
                if (stripos($message, $keyword) !== false) {
                    $is_pdf_request = true;
                    break;
                }
            }

            // If it looks like a PDF request or we're waiting for a PDF URL
            if ($is_pdf_request || get_transient('mxchat_waiting_for_pdf_url_' . $session_id)) {
                // Validate HTTPS
                if (wp_http_validate_url($new_pdf_url) && parse_url($new_pdf_url, PHP_URL_SCHEME) === 'https') {
                    // Extract filename from URL
                    $pdf_filename = basename(parse_url($new_pdf_url, PHP_URL_PATH));

                    // Clear previous PDF transients
                    $this->clear_pdf_transients($session_id);

                    // Process new PDF
                    $max_pages = $this->options['pdf_max_pages'] ?? 69;
                    $embeddings = $this->fetch_and_split_pdf_pages($new_pdf_url, $max_pages);

                    if ($embeddings === 'too_many_pages') {
                        $error_text = sprintf(
                            $this->options['pdf_intent_error_text'] ??
                            esc_html__("The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.", 'mxchat'),
                            $max_pages
                        );
                        $this->fallbackResponse['text'] = $error_text;
                    } elseif ($embeddings) {
                        // Store new PDF information
                        // Create a more meaningful filename from URL
                        $pdf_filename = basename(parse_url($new_pdf_url, PHP_URL_PATH));

                        // If the filename is generic (like results_download.php), create a more descriptive one
                        if (in_array($pdf_filename, ['results_download.php', 'download.php', 'view.php', 'pdf.php']) ||
                            strpos($pdf_filename, '.php') !== false) {
                            // Create a timestamp-based name
                            $pdf_filename = 'Document_' . date('Y-m-d_H-i') . '.pdf';
                        }

                        set_transient('mxchat_pdf_url_' . $session_id, $new_pdf_url, HOUR_IN_SECONDS);
                        set_transient('mxchat_pdf_filename_' . $session_id, $pdf_filename, HOUR_IN_SECONDS);
                        set_transient('mxchat_pdf_embeddings_' . $session_id, $embeddings, HOUR_IN_SECONDS);
                        set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS);

                        $success_text = $this->options['pdf_intent_success_text'] ??
                            esc_html__("I've processed the new PDF '{$pdf_filename}'. What questions do you have about it?", 'mxchat');

                        // Return success with filename for UI update
                        wp_send_json([
                            'success' => true,
                            'message' => $success_text,
                            'data' => [
                                'filename' => $pdf_filename
                            ]
                        ]);
                        wp_die();
                    } else {
                        $error_text = $this->options['pdf_intent_error_text'] ??
                            esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
                        $this->fallbackResponse['text'] = $error_text;
                    }

                    wp_send_json([
                        'success' => false,
                        'message' => $this->fallbackResponse['text']
                    ]);
                    wp_die();
                }
            }
        }

    // Step 2: Detect intent and handle intent-based responses
    $intent_matched = $this->mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id);
    //error_log("Intent Matched: " . ($intent_matched ? "Yes" : "No"));

    // Step 3: If intent is matched and handled, respond immediately
    if ($intent_matched && (!empty($this->fallbackResponse['text']) || !empty($this->fallbackResponse['html']))) {
        //error_log("Intent response triggered.");
        $response_data = [
            'text' => $this->fallbackResponse['text'],
            'html' => $this->fallbackResponse['html'],
            'session_id' => $session_id
        ];
        $this->mxchat_save_chat_message($session_id, 'bot', $this->fallbackResponse['text'] . $this->fallbackResponse['html']);
        wp_send_json($response_data);
        wp_die();
    }

    // If no intent matched or product not found, proceed with AI response
    //error_log("No matching intent or fallback. Generating AI response.");

    // Step 4: Generate AI response
    $conversation_history = $this->mxchat_fetch_conversation_history_for_ai($session_id);
    $this->mxchat_increment_chat_count();

    // Generate embedding for the user's query
    $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
    if (!is_array($user_message_embedding)) {
        //error_log("Failed to generate message embedding for session $session_id");
        wp_send_json_error(esc_html__('Error processing your message.', 'mxchat'));
        wp_die();
    }

    // Build context with both knowledge base and PDF content if available
    $context_content = "User asked: '{$message}'\n\n";

    // Get relevant content from knowledge base
    $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding);
    if (!empty($relevant_content)) {
        $context_content .= "Relevant content from knowledge database:\n" . $relevant_content . "\n\n";
    }


        // Check for and include PDF content
        $pdf_url = get_transient('mxchat_pdf_url_' . $session_id);
        $pdf_embeddings = get_transient('mxchat_pdf_embeddings_' . $session_id);
        $pdf_filename = get_transient('mxchat_pdf_filename_' . $session_id);
        if ($pdf_url && $pdf_embeddings && get_transient('mxchat_include_pdf_in_context_' . $session_id)) {
            $relevant_pdf_pages = $this->find_relevant_pdf_pages($user_message_embedding, $pdf_embeddings);
            if (!empty($relevant_pdf_pages)) {
                $context_content .= "Relevant content from PDF document '{$pdf_filename}':\n";
                foreach ($relevant_pdf_pages as $page_data) {
                    $context_content .= "Page {$page_data['page_number']} of '{$pdf_filename}': {$page_data['text']}\n";
                }
                $context_content .= "\n";
            }
        }

        // Check for and include Word content
        $word_url = get_transient('mxchat_word_url_' . $session_id);
        $word_embeddings = get_transient('mxchat_word_embeddings_' . $session_id);
        $word_filename = get_transient('mxchat_word_filename_' . $session_id);
        if ($word_url && $word_embeddings && get_transient('mxchat_include_word_in_context_' . $session_id)) {
            $relevant_word_chunks = $this->word_handler->mxchat_find_relevant_word_chunks($user_message_embedding, $word_embeddings);
            if (!empty($relevant_word_chunks)) {
                $context_content .= "Relevant content from Word document '{$word_filename}':\n";
                foreach ($relevant_word_chunks as $chunk_data) {
                    $context_content .= "Section {$chunk_data['chunk_number']} of '{$word_filename}': {$chunk_data['text']}\n";
                }
                $context_content .= "\n";
            }
        }
    // Generate the response using the full context
    $response = $this->mxchat_generate_response(
        $context_content,
        $this->options['api_key'],
        $this->options['xai_api_key'],
        $this->options['claude_api_key'],
        $this->options['deepseek_api_key'],
        $conversation_history
    );

    $this->mxchat_save_chat_message($session_id, 'bot', $response);

    // Step 5: Save additional content if available
    if (!empty($this->productCardHtml)) {
        $this->mxchat_save_chat_message($session_id, 'bot', $this->productCardHtml);
    }

    if (!empty($this->fallbackResponse['html'])) {
        $this->mxchat_save_chat_message($session_id, 'bot', $this->fallbackResponse['html']);
    }

    // Step 6: Return the response
    $response_data = [
        'text' => $response,
        'html' => !empty($this->productCardHtml) ? $this->productCardHtml : ($this->fallbackResponse['html'] ?? ''),
        'session_id' => $session_id
    ];

    wp_send_json($response_data);
    wp_die();
}

// New function to check intents and invoke the callback function
private function mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id) {
    global $wpdb;
    $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai');
    
    //error_log('🔍 MXCHAT DEBUG: Intent Check Started ==================');
    //error_log("🔍 MXCHAT DEBUG: Message: '$message'");
    //error_log("🔍 MXCHAT DEBUG: Chat Mode: $chat_mode");
    
    // Generate the user embedding
    //error_log('🔄 MXCHAT DEBUG: Generating user embedding');
    $user_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
    if (!is_array($user_embedding)) {
        //error_log('❌ MXCHAT DEBUG: Failed to generate user embedding');
        return false;
    }
    //error_log('✅ MXCHAT DEBUG: User embedding generated successfully');
    
    // Fetch intents from the database
    $table_name = $wpdb->prefix . 'mxchat_intents';
    if ($chat_mode === 'agent') {
        //error_log('🔍 MXCHAT DEBUG: Agent mode - fetching only chatbot switch intent');
        $query = $wpdb->prepare(
            "SELECT * FROM $table_name WHERE callback_function = %s",
            'mxchat_handle_switch_to_chatbot_intent'
        );
        $intents = $wpdb->get_results($query);
    } else {
        //error_log('🔍 MXCHAT DEBUG: AI mode - fetching all intents');
        $intents = $wpdb->get_results("SELECT * FROM $table_name");
    }
    
    //error_log('🔍 MXCHAT DEBUG: Found ' . count($intents) . ' intents to check');
    
    if (empty($intents)) {
        //error_log('❌ MXCHAT DEBUG: No intents found in database');
        return false;
    }
    
    $highest_similarity = -INF;
    $matched_intent = null;
    
    //error_log('📊 MXCHAT DEBUG: Intent Similarity Scores ==================');
    foreach ($intents as $intent) {
        //error_log("🔄 MXCHAT DEBUG: Checking intent: '{$intent->intent_label}' (callback: {$intent->callback_function})");
        
        $intent_embedding_serialized = $intent->embedding_vector;
        $intent_embedding = $intent_embedding_serialized
            ? unserialize($intent_embedding_serialized, ['allowed_classes' => false])
            : null;
            
        if (!is_array($intent_embedding)) {
            //error_log("❌ MXCHAT DEBUG: Invalid embedding for intent: {$intent->intent_label}");
            continue;
        }
        
        $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $intent_embedding);
        $intent_threshold = isset($intent->similarity_threshold) ? $intent->similarity_threshold : 0.85;
        
        
        if ($similarity >= $intent_threshold && $similarity > $highest_similarity) {
            $highest_similarity = $similarity;
            $matched_intent = $intent;
            //error_log("✅ MXCHAT DEBUG: New best match: '{$intent->intent_label}' with similarity {$similarity}");
        }
    }
    //error_log('📊 MXCHAT DEBUG: End Intent Scores ==================');
    
    if ($matched_intent) {
        //error_log("🎯 MXCHAT DEBUG: Final Intent Match: '{$matched_intent->intent_label}'");
        //error_log("🔄 MXCHAT DEBUG: Invoking callback: {$matched_intent->callback_function}");
        
        // If the callback is a method on this instance (core callback), call it directly
        if (method_exists($this, $matched_intent->callback_function)) {
            //error_log('🔍 MXCHAT DEBUG: Using direct method call for core callback');
            $callback_result = call_user_func(
                        [$this, $matched_intent->callback_function],
                        $message,
                        $user_id,
                        $session_id,
                        $matched_intent,
                        $user_context // Add user context
                    );
        } else {
            //error_log('🔍 MXCHAT DEBUG: Using apply_filters for add-on callback');
            // Otherwise, use apply_filters for add-on callbacks
            $callback_result = apply_filters(
                $matched_intent->callback_function, 
                false,  // default return value
                $message, 
                $user_id, 
                $session_id,
                $matched_intent
            );
        }
        
        //error_log('🔍 MXCHAT DEBUG: Callback result type: ' . gettype($callback_result));
        if ($callback_result !== false) {
            //error_log('✅ MXCHAT DEBUG: Intent handled successfully');
            $this->fallbackResponse = $callback_result;
            return true;
        }
        //error_log('❌ MXCHAT DEBUG: Callback returned false');
    } else {
        //error_log('❌ MXCHAT DEBUG: No matching intent found');
    }
    
    //error_log('🔍 MXCHAT DEBUG: Intent Check Completed ==================');
    return false;
}


// Helper function to clear PDF and Word document related transients
private function clear_pdf_transients($session_id) {
    // PDF transients
    delete_transient('mxchat_pdf_url_' . $session_id);
    delete_transient('mxchat_pdf_embeddings_' . $session_id);
    delete_transient('mxchat_include_pdf_in_context_' . $session_id);
    delete_transient('mxchat_waiting_for_pdf_url_' . $session_id);

    // Word document transients
    delete_transient('mxchat_word_url_' . $session_id);
    delete_transient('mxchat_word_filename_' . $session_id);
    delete_transient('mxchat_word_embeddings_' . $session_id);
    delete_transient('mxchat_include_word_in_context_' . $session_id);
    delete_transient('mxchat_waiting_for_word_' . $session_id);
}



//verified good
public function mxchat_handle_email_capture($message, $user_id, $session_id) {
    // Log the message safely
    //error_log("Triggered email capture intent for message: " . sanitize_text_field($message));

    // Initiate email capture flow
    $response = esc_html($this->options['triggered_phrase_response'] ?? esc_html__("Would you like to join our mailing list? Please provide your email below.", 'mxchat'));

    set_transient('mxchat_email_capture_' . $user_id, true, 5 * MINUTE_IN_SECONDS);
    $this->mxchat_save_chat_message($session_id, 'bot', $response);

    // Respond to the user
    wp_send_json(['message' => $response]);
    wp_die();
}

//very good
public function mxchat_generate_image($message, $user_id, $session_id) {
    // Prepare a prompt for DALL-E
    $prompt = esc_html__('Create an image of ', 'mxchat') . sanitize_text_field($message);

    // Use the existing OpenAI API key
    $openai_api_key = sanitize_text_field($this->options['api_key']);

    // Call DALL-E to generate an image
    $image_response = $this->mxchat_generate_dalle_image($prompt, $openai_api_key);

    // Check if the response contains an image URL
    if (isset($image_response['imageUrl'])) {
        $image_url = esc_url_raw($image_response['imageUrl']);

        // Construct the HTML with a CSS class instead of inline styles
        $response_html = '<img src="' . esc_url($image_url) . '" alt="' . esc_attr__('Generated Image', 'mxchat') . '" class="mxchat-generated-image" />';

        $response_text = esc_html__('Here is the image I generated:', 'mxchat');
    } else {
        $response_text = esc_html__("I'm sorry, but I couldn't generate an image based on your request.", 'mxchat');
        $response_html = '';
        //error_log("DALL-E image generation error: " . esc_html($image_response['error'] ?? 'Unknown error.'));
    }

    // Save both text and HTML responses
    $this->mxchat_save_chat_message($session_id, 'bot', $response_text . "\n" . $response_html);

    // Prepare the response data
    $response_data = [
        'message' => $response_text,
        'html'    => $response_html,
        'image_url' => $image_url ?? '',
    ];

    // Send the JSON response
    header('Content-Type: application/json; charset=' . get_option('blog_charset'));
    echo json_encode($response_data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    wp_die();
}
private function mxchat_generate_dalle_image($prompt, $api_key, $model = 'dall-e-3', $timeout = 60) {
    $api_url = 'https://api.openai.com/v1/images/generations';
    $body = json_encode([
        'prompt' => sanitize_text_field($prompt),
        'n' => 1,
        'size' => '1024x1024',
        'model' => sanitize_text_field($model),
    ]);

    $args = [
        'body' => $body,
        'headers' => [
            'Content-Type' => 'application/json',
            'Authorization' => 'Bearer ' . sanitize_text_field($api_key),
        ],
        'method' => 'POST',
        'timeout' => absint($timeout),
    ];

    $response = wp_remote_post($api_url, $args);

    if (is_wp_error($response)) {
        //error_log("DALL-E request failed: " . $response->get_error_message());
        return ['error' => esc_html__('Error generating image: ', 'mxchat') . $response->get_error_message()];
    }

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

    if (isset($response_body['data'][0]['url'])) {
        return ['imageUrl' => esc_url_raw($response_body['data'][0]['url'])];
    } else {
        //error_log("DALL-E response error: " . wp_remote_retrieve_body($response));
        return ['error' => esc_html__('Failed to generate image.', 'mxchat')];
    }
}

/**
 * Handle web search requests.
 *
 * Sends the refined search query to the Brave Search API and displays neatly formatted,
 * styled search results. Results are cached for performance.
 *
 * @since 1.0.0
 * @param string $message    The user's search query.
 * @param string $user_id    The user identifier.
 * @param string $session_id The current session ID.
 * @return void
 */
public function mxchat_handle_search_request( $message, $user_id, $session_id ) {
    // Step 1: Interpret and refine the search query
    $refined_search_query = $this->mxchat_interpret_search_query( $message );

    if ( empty( $refined_search_query ) ) {
        $this->fallbackResponse = array(
            'text' => esc_html__( 'I apologize, but could you please rephrase your search request?', 'mxchat' ),
        );
        return;
    }

    // Retrieve and validate API settings
    $options       = get_option( 'mxchat_options' );
    $api_key       = isset( $options['brave_api_key'] ) ? sanitize_text_field( $options['brave_api_key'] ) : '';
    $results_count = isset( $options['brave_results_count'] ) ? absint( $options['brave_results_count'] ) : 5;

    if ( empty( $api_key ) ) {
        $this->fallbackResponse = array(
            'text' => esc_html__( 'Search functionality is temporarily unavailable. Please try again later.', 'mxchat' ),
        );
        return;
    }

    // Build the API request URL
    $api_url = add_query_arg(
        array(
            'q'                 => rawurlencode( $refined_search_query ),
            'count'             => $results_count,
            'text_decorations'  => 'true',
            'rich_data'         => 'true',
        ),
        'https://api.search.brave.com/res/v1/web/search'
    );

    // Attempt to retrieve cached results first
    $transient_key = 'mxchat_search_' . md5( $refined_search_query );
    $results       = get_transient( $transient_key );

    if ( false === $results ) {
        // Fetch new results from the Brave Search API
        $response = wp_remote_get(
            $api_url,
            array(
                'headers' => array(
                    'Accept'              => 'application/json',
                    'Accept-Encoding'     => 'gzip',
                    'X-Subscription-Token'=> $api_key,
                ),
                'timeout' => 10,
            )
        );

        if ( is_wp_error( $response ) ) {
            $this->fallbackResponse = array(
                'text' => esc_html__( 'I encountered an error while searching. Please try again.', 'mxchat' ),
            );
            return;
        }

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

        if ( json_last_error() !== JSON_ERROR_NONE ) {
            $this->fallbackResponse = array(
                'text' => esc_html__( 'I received an invalid response from the search service.', 'mxchat' ),
            );
            return;
        }

        // Cache results for one hour
        set_transient( $transient_key, $results, HOUR_IN_SECONDS );
    }

    // Process and display results
    if ( ! empty( $results['web']['results'] ) && is_array( $results['web']['results'] ) ) {
        $html = $this->generate_search_results_html( $results['web']['results'], $refined_search_query );

        // Only return HTML (no large text summary)
        $this->fallbackResponse = array(
            'html' => $html,
        );

        // Save to chat history
        $this->mxchat_save_chat_message( $session_id, 'bot', $html );
    } else {
        $this->fallbackResponse = array(
            'text' => sprintf(
                esc_html__( 'I couldn\'t find any relevant results for "%s". Would you like to try different search terms?', 'mxchat' ),
                esc_html( $refined_search_query )
            ),
        );
    }
}


/**
 * Format search results into a natural text summary.
 *
 * @since 1.0.0
 * @param array  $results The search results from the API.
 * @param string $query   The original search query.
 * @return string The text summary of the top results.
 */
private function format_search_results( $results, $query ) {
    $summary = sprintf(
        esc_html__( 'Here are the most relevant results for "%s":', 'mxchat' ),
        esc_html( $query )
    ) . "\n\n";

    $max_results = min( count( $results ), 3 );
    for ( $i = 0; $i < $max_results; $i++ ) {
        $result      = $results[ $i ];
        $title       = isset( $result['title'] ) ? wp_strip_all_tags( $result['title'] ) : '';
        $description = isset( $result['description'] ) ? wp_strip_all_tags( $result['description'] ) : '';

        // Append title and description to the summary
        $summary .= sprintf(
            "%s\n%s\n\n",
            esc_html( $title ),
            esc_html( $description )
        );
    }

    return $summary;
}

/**
 * Generate HTML markup for search results.
 *
 * @since 1.0.0
 * @param array  $results The search results from the API.
 * @param string $query   The user-refined query.
 * @return string The HTML markup for displaying the results.
 */
private function generate_search_results_html( $results, $query ) {
    ob_start();
    ?>
    <div class="mxchat-search-results">
        <?php foreach ( $results as $result ) :
            $title       = isset( $result['title'] ) ? wp_strip_all_tags( $result['title'] ) : '';
            $url         = isset( $result['url'] ) ? esc_url( $result['url'] ) : '#';
            $description = isset( $result['description'] ) ? wp_strip_all_tags( $result['description'] ) : '';
            $favicon     = isset( $result['meta_url']['favicon'] ) ? esc_url( $result['meta_url']['favicon'] ) : '';
            $thumbnail   = isset( $result['thumbnail']['src'] ) ? esc_url( $result['thumbnail']['src'] ) : '';
            $domain      = parse_url( $url, PHP_URL_HOST );
            ?>
            <div class="mxchat-search-item">
                <div class="mxchat-search-header">
                    <?php if ( $favicon ) : ?>
                        <img
                            src="<?php echo esc_url( $favicon ); ?>"
                            class="mxchat-site-icon"
                            alt="<?php echo esc_attr__( 'Site icon', 'mxchat' ); ?>"
                            width="16"
                            height="16"
                        />
                    <?php endif; ?>
                    <div class="mxchat-site-url"><?php echo esc_html( $domain ); ?></div>
                </div>

                <div class="mxchat-search-content">
                    <h3 class="mxchat-search-title">
                        <a href="<?php echo esc_url( $url ); ?>"
                           target="_blank"
                           rel="noopener noreferrer"
                        >
                            <?php echo esc_html( $title ); ?>
                        </a>
                    </h3>

                    <?php if ( $thumbnail ) : ?>
                        <div class="mxchat-search-thumbnail">
                            <img
                                src="<?php echo esc_url( $thumbnail ); ?>"
                                alt="<?php echo esc_attr__( 'Thumbnail image', 'mxchat' ); ?>"
                                loading="lazy"
                            />
                        </div>
                    <?php endif; ?>

                    <div class="mxchat-search-description">
                        <?php echo esc_html( $description ); ?>
                    </div>
                </div>
            </div>
        <?php endforeach; ?>
    </div>
    <?php
    return ob_get_clean();
}


//very good
public function mxchat_handle_image_search_request($message, $user_id, $session_id) {

    // Step 1: Interpret the search query for better results
    $refined_search_query = $this->mxchat_interpret_search_query($message);


    // If no query was interpreted, return a fallback message
    if (empty($refined_search_query)) {
        $this->fallbackResponse = [
            'text' => __("I'm sorry, I couldn't interpret your search query. Please specify what you'd like to see images of.", 'mxchat'),
            'html' => "",
        ];
        return;
    }

    // Brave API URL
    $api_url = 'https://api.search.brave.com/res/v1/images/search';

    // Retrieve Brave API settings
    $options = get_option('mxchat_options');
    $api_key = isset($options['brave_api_key']) ? sanitize_text_field($options['brave_api_key']) : '';

    if (empty($api_key)) {
/*
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log("Brave API key is missing.");
        }
*/

        $this->fallbackResponse = [
            'text' => __("API key is not configured. Please set it in the Brave Search Settings.", 'mxchat'),
            'html' => "",
        ];
        return;
    }

    $image_count = isset($options['brave_image_count']) ? intval($options['brave_image_count']) : 4;
    $safe_search = isset($options['brave_safe_search']) ? sanitize_text_field($options['brave_safe_search']) : 'strict';

    // Append query parameters based on settings
    $api_url = add_query_arg([
        'q' => rawurlencode($refined_search_query),
        'count' => $image_count,
        'safesearch' => $safe_search,
    ], $api_url);

/*
    // Log the final API URL for the search
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log("Final API URL for Brave Image Search: " . esc_url_raw($api_url));
    }
*/


    // Implement caching
    $transient_key = 'mxchat_image_search_' . md5($refined_search_query);
    $body = get_transient($transient_key);

    if (false === $body) {
        $args = [
            'headers' => [
                'Accept' => 'application/json',
                'Accept-Encoding' => 'gzip',
                'X-Subscription-Token' => $api_key,
            ],
            'timeout' => 10,
        ];

        $response = wp_remote_get($api_url, $args);

        if (is_wp_error($response)) {
/*
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log("Brave Image API request failed: " . $response->get_error_message());
            }
*/

            $this->fallbackResponse = [
                'text' => __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat'),
                'html' => "",
            ];
            return;
        }

        $body = json_decode(wp_remote_retrieve_body($response), true);
        set_transient($transient_key, $body, HOUR_IN_SECONDS);
    }

    // Process the API response
    if (isset($body['results']) && is_array($body['results']) && count($body['results']) > 0) {
        $html_output = '<div class="mxchat-image-gallery">';

        foreach ($body['results'] as $image) {
            $image_url = isset($image['url']) ? esc_url($image['url']) : '';
            $thumbnail_url = isset($image['thumbnail']['src']) ? esc_url($image['thumbnail']['src']) : '';
            $title = isset($image['title']) ? esc_html($image['title']) : esc_html__('Image', 'mxchat');

            if ($image_url && $thumbnail_url) {
                $html_output .= '<div class="mxchat-image-item">';
                $html_output .= '<strong class="mxchat-image-title">' . $title . '</strong>';
                $html_output .= '<a href="' . $image_url . '" target="_blank" rel="noopener noreferrer" class="mxchat-image-link">';
                $html_output .= '<img src="' . $thumbnail_url . '" alt="' . $title . '" class="mxchat-image-thumbnail">';
                $html_output .= '</a></div>';
            }
        }

        $html_output .= '</div>';

        $this->fallbackResponse = [
            'text' => "",
            'html' => $html_output,
        ];

        // Save response in chat history
        $this->mxchat_save_chat_message($session_id, 'bot', $html_output);

    } else {
/*
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log("Brave Image API response did not contain expected data structure or was empty: " . print_r($body, true));
        }
*/

        $this->fallbackResponse = [
            'text' => __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat'),
            'html' => "",
        ];
    }
}
public function mxchat_interpret_search_query($user_query) {
    $system_prompt = esc_html__("Interpret the user's request to provide only the essential keywords or phrases for image searching. Remove conversational language, politeness, or extra context. Return a concise search query that doesn't lose any of the original meaning.", 'mxchat');

    // Retrieve OpenAI API key using 'api_key' as the option key
    $api_key = isset($this->options['api_key']) ? sanitize_text_field($this->options['api_key']) : sanitize_text_field(get_option('mxchat_options')['api_key']);

    /*
    // Log the API key check, without exposing the key
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log("Retrieved OpenAI API Key: " . ($api_key ? "Present" : "Missing"));
    }
    */

    if (empty($api_key)) {
        //error_log("OpenAI API key is missing.");
        return sanitize_text_field($user_query); // Default to the original query if API key is missing
    }

    $url = 'https://api.openai.com/v1/chat/completions';
    $args = [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type' => 'application/json',
        ],
        'body' => wp_json_encode([
            'model' => 'gpt-3.5-turbo',
            'messages' => [
                ['role' => 'system', 'content' => $system_prompt],
                ['role' => 'user', 'content' => sanitize_text_field($user_query)],
            ],
            'temperature' => 0.2,
            'max_tokens' => 20,
        ]),
        'method' => 'POST',
    ];

    $response = wp_remote_post($url, $args);

    if (is_wp_error($response)) {
        //error_log("OpenAI request failed: " . $response->get_error_message());
        return sanitize_text_field($user_query); // Fallback to the original query if there's an error
    }

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

    // Check for a valid response and sanitize output
    if (isset($body['choices'][0]['message']['content'])) {
        $interpreted_query = sanitize_text_field(trim($body['choices'][0]['message']['content']));

        /*
        // Log the interpreted query for debugging
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log("Interpreted search query: " . $interpreted_query);
        }
        */

        return $interpreted_query;
    } else {
        //error_log("Unexpected API response format: " . print_r($body, true));
        return sanitize_text_field($user_query);
    }
}



private function find_product_in_message($message) {
    global $wpdb;

    // Get embedding for the search query
    $query_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
    if (!is_array($query_embedding)) {
        return null;
    }

    // Get relevant content as string
    $relevant_content = $this->mxchat_find_relevant_products($query_embedding);
    if (empty($relevant_content)) {
        // Return null to indicate no results and set fallback response
        $this->fallbackResponse['text'] = esc_html__("I couldn't find any relevant products based on your query. Could you please be more specific about the product you're looking for?", 'mxchat');
        return null;
    }

    // Extract product URLs from the content
    preg_match_all('/https?:\/\/[^\s<>"\']+?\/product\/[^\s<>"\']+/', $relevant_content, $matches);

    if (!empty($matches[0])) {
        // Try each URL found
        foreach ($matches[0] as $url) {
            // Clean the URL
            $url = rtrim($url, '/."\']');

            // Get the product slug
            $path = parse_url($url, PHP_URL_PATH);
            $slug = basename(rtrim($path, '/'));

            // Find product by slug
            $args = array(
                'post_type' => 'product',
                'post_status' => 'publish',
                'name' => $slug,
                'posts_per_page' => 1
            );

            $products = get_posts($args);

            if (!empty($products)) {
                $product_id = $products[0]->ID;
                $product = wc_get_product($product_id);

                if ($product && $product->is_purchasable()) {
                    return $product_id;
                }
            }
        }
    }

    // Fallback: Look for product names in the content
    $products = wc_get_products([
        'status' => 'publish',
        'limit' => -1,
        'return' => 'all'
    ]);

    foreach ($products as $product) {
        $name = $product->get_name();
        if (stripos($relevant_content, $name) !== false) {
            if ($product->is_purchasable()) {
                return $product->get_id();
            }
        }
    }

    // If no product is found after all checks, set the fallback response
    $this->fallbackResponse['text'] = esc_html__("I couldn't find any relevant products based on your query. Try to be more specific", 'mxchat');
    return null;
}

// New method to handle intent responses
private function generate_intent_response($context_content, $session_id) {
    // Convert the context array to a structured string for the AI
    $context_string = $this->format_intent_context($context_content);

    // Generate AI response using the context
    $response = $this->mxchat_generate_response(
        $context_string,
        $this->options['api_key'],
        $this->options['xai_api_key'],
        $this->options['claude_api_key'],
        $this->options['deepseek_api_key'],
        $this->mxchat_fetch_conversation_history_for_ai($session_id)
    );

    $this->fallbackResponse['text'] = $response;
    return true;
}
// Helper method to format intent context
private function format_intent_context($context) {
    $context_string = esc_html__("INTENT CONTEXT:\n", 'mxchat');

    switch ($context['intent']) {
        case 'add_to_cart':
            if ($context['status'] === 'success') {
                $context_string .= esc_html__("Action: Successfully added product to cart\n", 'mxchat');
                $context_string .= sprintf(esc_html__("Product: %s\n", 'mxchat'), $context['product']['name']);
                $context_string .= esc_html__("Available actions: ", 'mxchat') . implode(', ', $context['available_actions']) . "\n";
                $context_string .= sprintf(esc_html__("Cart URL: %s\n", 'mxchat'), $context['cart_url']);
                $context_string .= esc_html__("\nPlease inform the user of the successful addition and their available options.", 'mxchat');
            } else {
                $context_string .= esc_html__("Action: Failed to add product to cart\n", 'mxchat');
                $context_string .= sprintf(esc_html__("Reason: %s\n", 'mxchat'), $context['reason']);
                switch ($context['reason']) {
                    case 'woocommerce_not_available':
                        $context_string .= esc_html__("\nPlease inform the user that shopping features are not available.", 'mxchat');
                        break;
                    case 'no_product_context':
                        $context_string .= esc_html__("\nPlease ask the user to specify which product they want to add.", 'mxchat');
                        break;
                    case 'product_not_found':
                        $context_string .= esc_html__("\nPlease inform the user that the product couldn't be found and ask them to try again.", 'mxchat');
                        break;
                    case 'add_to_cart_failed':
                        $context_string .= esc_html__("\nPlease apologize to the user and suggest they try again or ask for assistance.", 'mxchat');
                        break;
                }
            }
            break;
    }

    return $context_string;
}


//very good
private function add_email_to_loops($email) {
    // Sanitize the email
    $email = sanitize_email($email);

    // Retrieve and sanitize options
    $api_key = isset($this->options['loops_api_key']) ? sanitize_text_field($this->options['loops_api_key']) : '';
    $mailing_list_id = isset($this->options['loops_mailing_list']) ? sanitize_text_field($this->options['loops_mailing_list']) : '';

    // Check for missing API key or mailing list ID
    if (empty($api_key) || empty($mailing_list_id)) {
        //error_log(esc_html__('Loops API key or mailing list ID is missing.', 'mxchat'));
        return;
    }

    $data = array(
        'email'        => $email,
        'subscribed'   => true,
        'source'       => __('MxChat AI Chatbot', 'mxchat'),
        'mailingLists' => array($mailing_list_id => true),
    );

    $url = 'https://app.loops.so/api/v1/contacts/create';
    $args = array(
        'body'    => wp_json_encode($data),
        'headers' => array(
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ),
        'method'  => 'POST',
        'timeout' => 45,
    );

    $response = wp_remote_post($url, $args);

    // Handle errors in the API request
    if (is_wp_error($response)) {
        //error_log(esc_html__('Error adding email to Loops: ', 'mxchat') . $response->get_error_message());
        return;
    }

    // Check for non-200 HTTP responses
    $response_code = wp_remote_retrieve_response_code($response);
    if ($response_code != 200) {
        $response_body = wp_remote_retrieve_body($response);
        //error_log(esc_html__('Loops API responded with code ', 'mxchat') . $response_code . ': ' . $response_body);
    }
}

public function mxchat_handle_pdf_discussion($message, $user_id, $session_id) {
    // Get the maximum number of pages allowed from admin settings
    $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;

    // Retrieve options for dynamic texts
    $trigger_text = $this->options['pdf_intent_trigger_text'] ?? __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
    $success_text = $this->options['pdf_intent_success_text'] ?? __("I've processed the PDF. What questions do you have about it?", 'mxchat');
    $error_text = $this->options['pdf_intent_error_text'] ?? __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');

    // Check for explicit request for new PDF
    $new_pdf_requested = stripos($message, 'new') !== false ||
                        stripos($message, 'another') !== false ||
                        stripos($message, 'different') !== false;

    // If user mentions adding/reading a PDF, set waiting flag
    if (stripos($message, 'pdf') !== false ||
        stripos($message, 'document') !== false ||
        stripos($message, 'read') !== false) {
        set_transient('mxchat_waiting_for_pdf_url_' . $session_id, true, HOUR_IN_SECONDS);
        $this->fallbackResponse['text'] = $trigger_text;
        return;
    }

    // If we're waiting for a URL or user requested new PDF
    if ($new_pdf_requested || get_transient('mxchat_waiting_for_pdf_url_' . $session_id)) {
        if (preg_match('/https?:\/\/[^\s"]+/i', $message, $matches)) {
            // Process URL... (rest of your existing URL processing code)
        } else {
            $this->fallbackResponse['text'] = $trigger_text;
        }
        return;
    }

    // Default to proceeding with conversation if no specific PDF action is needed
    $this->fallbackResponse['text'] = '';
}
private function fetch_and_split_pdf_pages($pdf_source, $max_pages) {
    $upload_dir = wp_upload_dir();
    $temp_file = null;

    try {
        // Handle URL vs local file
        if (filter_var($pdf_source, FILTER_VALIDATE_URL)) {
            // Validate and download the file from URL
            $temp_file = wp_tempnam($pdf_source); // Safe temporary file name
            $response = wp_remote_get($pdf_source, ['timeout' => 60]);

            if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
                //error_log(esc_html__("Failed to download PDF. Error: ", 'mxchat') . print_r($response, true));
                return false;
            }

            file_put_contents($temp_file, wp_remote_retrieve_body($response));

            // Validate that the downloaded file is a PDF
            $mime_type = mime_content_type($temp_file);
            if ($mime_type !== 'application/pdf') {
                //error_log(esc_html__("Invalid MIME type detected for PDF: ", 'mxchat') . $mime_type);
                unlink($temp_file);
                return false;
            }
        } else {
            // For local files, use the provided path directly
            $temp_file = $pdf_source;
        }

        // Parse and process the PDF
        $parser = new \Smalot\PdfParser\Parser();
        $pdf = $parser->parseFile($temp_file);
        $pages = $pdf->getPages();

        if (count($pages) > $max_pages) {
            //error_log(esc_html__("PDF exceeds the maximum allowed pages: ", 'mxchat') . count($pages));
            if (filter_var($pdf_source, FILTER_VALIDATE_URL)) {
                unlink($temp_file);
            }
            return esc_html__('too_many_pages', 'mxchat');
        }

        $embeddings = [];
        foreach ($pages as $page_number => $page) {
            $text = $page->getText();

            // Ensure text is non-empty before generating embeddings
            if (empty(trim($text))) {
                //error_log(esc_html__("Skipping empty page: ", 'mxchat') . ($page_number + 1));
                continue;
            }

            $embedding = $this->mxchat_generate_embedding(
                esc_html__("Page ", 'mxchat') . ($page_number + 1) . ": " . $text,
                $this->options['api_key']
            );

            if ($embedding) {
                $embeddings[] = [
                    'page_number' => $page_number + 1,
                    'embedding' => $embedding,
                    'text' => $text,
                ];
            } else {
                //error_log(esc_html__("Failed to generate embedding for page ", 'mxchat') . ($page_number + 1));
            }
        }

        // Clean up downloaded file if it was from URL
        if (filter_var($pdf_source, FILTER_VALIDATE_URL) && $temp_file) {
            unlink($temp_file);
        }

        return $embeddings;

    } catch (\Exception $e) {
       // error_log(esc_html__("Error parsing or processing PDF: ", 'mxchat') . $e->getMessage());

        // Cleanup in case of exception
        if (filter_var($pdf_source, FILTER_VALIDATE_URL) && $temp_file && file_exists($temp_file)) {
            unlink($temp_file);
        }

        return false;
    }
}
private function find_relevant_pdf_pages($query_embedding, $embeddings) {
    //error_log(esc_html__("find_relevant_pdf_pages called.", 'mxchat'));

    $most_relevant = null;
    $highest_similarity = -INF;

    foreach ($embeddings as $page_data) {
        $similarity = $this->mxchat_calculate_cosine_similarity($query_embedding, $page_data['embedding']);

        if ($similarity > $highest_similarity) {
            $highest_similarity = $similarity;
            $most_relevant = $page_data['page_number'];
        }
    }

    if (!is_null($most_relevant)) {
        $page_numbers = range(max(1, $most_relevant - 1), min(count($embeddings), $most_relevant + 1));
        return array_filter($embeddings, function ($page) use ($page_numbers) {
            return in_array($page['page_number'], $page_numbers);
        });
    }

    return [];
}
// Add this to your class
public function handle_pdf_upload() {
    check_ajax_referer('mxchat_chat_nonce', 'nonce');

    if (!isset($_FILES['pdf_file']) || !isset($_POST['session_id'])) {
        wp_send_json_error(esc_html__('Missing required parameters.', 'mxchat'));
        return;
    }

    $file = $_FILES['pdf_file'];
    $session_id = sanitize_text_field($_POST['session_id']);
    $original_filename = sanitize_text_field($file['name']);

    $file_type = wp_check_filetype($file['name'], ['pdf' => 'application/pdf']);
    if ($file_type['type'] !== 'application/pdf') {
        wp_send_json_error(esc_html__('Invalid file type. Only PDF files are allowed.', 'mxchat'));
        return;
    }

    $upload_dir = wp_upload_dir();
    $pdf_filename = 'mxchat_' . $session_id . '_' . time() . '.pdf';
    $pdf_path = $upload_dir['path'] . '/' . $pdf_filename;

    if (!move_uploaded_file($file['tmp_name'], $pdf_path)) {
        wp_send_json_error(esc_html__('Failed to upload file.', 'mxchat'));
        return;
    }

    $this->clear_pdf_transients($session_id);

    $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
    $embeddings = $this->fetch_and_split_pdf_pages($pdf_path, $max_pages);

    if ($embeddings === 'too_many_pages') {
        unlink($pdf_path);
        $error_message = sprintf(
            $this->options['pdf_intent_error_text'] ??
            esc_html__("The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.", 'mxchat'),
            $max_pages
        );
        wp_send_json_error($error_message);
        return;
    }

    if ($embeddings === false || empty($embeddings)) {
        unlink($pdf_path);
        $error_message = $this->options['pdf_intent_error_text'] ??
            esc_html__('The uploaded PDF appears to be empty or contains unsupported content.', 'mxchat');
        wp_send_json_error($error_message);
        return;
    }

    if (!empty($embeddings)) {
        set_transient('mxchat_pdf_url_' . $session_id, $pdf_path, HOUR_IN_SECONDS);
        set_transient('mxchat_pdf_filename_' . $session_id, $original_filename, HOUR_IN_SECONDS);
        set_transient('mxchat_pdf_embeddings_' . $session_id, $embeddings, HOUR_IN_SECONDS);
        set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS);

        $success_message = $this->options['pdf_intent_success_text'] ??
            esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat');

        wp_send_json_success([
            'message' => $success_message,
            'filename' => $original_filename
        ]);
        return;
    }

    unlink($pdf_path);
    $error_message = $this->options['pdf_intent_error_text'] ??
        esc_html__('Sorry, I couldn\'t process the PDF. Please ensure it\'s a valid file.', 'mxchat');
    wp_send_json_error($error_message);
    return;
}
public function handle_pdf_remove() {
    check_ajax_referer('mxchat_chat_nonce', 'nonce');

    if (empty($_POST['session_id'])) {
        wp_send_json_error(esc_html__('Session ID missing.', 'mxchat'));
        wp_die();
    }

    $session_id = sanitize_text_field($_POST['session_id']);
    $pdf_path = get_transient('mxchat_pdf_url_' . $session_id);

    if ($pdf_path && file_exists($pdf_path)) {
        unlink($pdf_path);
    }

    $this->clear_pdf_transients($session_id);

    wp_send_json_success([
        'message' => esc_html__('PDF removed successfully.', 'mxchat')
    ]);
    wp_die();
}




function mxchat_fetch_new_messages() {
    $session_id = sanitize_text_field($_POST['session_id']);
    $last_seen_id = sanitize_text_field($_POST['last_seen_id']);
    $persistence_enabled = $_POST['persistence_enabled'] === 'true';
    $initial_timestamp = isset($_POST['initial_timestamp']) ? intval($_POST['initial_timestamp']) : 0;

    if (empty($session_id)) {
        //error_log(esc_html__('Fetch new messages error: Session ID missing.', 'mxchat'));
        wp_send_json_error(['message' => esc_html__('Session ID missing.', 'mxchat')]);
        wp_die();
    }

    $history = get_option("mxchat_history_{$session_id}", []);

    $new_messages = array_filter($history, function ($message) use ($last_seen_id, $persistence_enabled, $initial_timestamp) {
        // If persistence is enabled, show all new messages
        if ($persistence_enabled) {
            return !empty($message['id']) &&
                   strcmp($message['id'], $last_seen_id) > 0 &&
                   $message['role'] === 'agent';
        }

        // If persistence is disabled, only show messages after initial timestamp
        return !empty($message['id']) &&
               $message['role'] === 'agent' &&
               $message['timestamp'] > $initial_timestamp;
    });

    //error_log(esc_html__("New agent messages fetched for session $session_id. Last seen ID: $last_seen_id", 'mxchat'));

    wp_send_json_success([
        'new_messages' => array_values($new_messages)
    ]);
    wp_die();
}


public function mxchat_live_agent_handover($message, $user_id, $session_id) {
    // First check if live agents are available
    $live_agent_available = $this->options['live_agent_status'] ?? 'off';
    if ($live_agent_available !== 'on') {
        $away_message = $this->options['live_agent_away_message'] ?? 'Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.';
        $this->fallbackResponse = [
            'text' => $away_message,
            'html' => '',
            'images' => [],
            'chat_mode' => 'ai'
        ];
        wp_send_json([
            'text' => $away_message,
            'html' => '',
            'chat_mode' => 'ai',
            'session_id' => $session_id
        ]);
        wp_die();
    }

    $slack_webhook_url = $this->options['live_agent_webhook_url'] ?? '';
    if (empty($slack_webhook_url)) {
        return false;
    }

    // Get recent chat history (last 5 messages)
    $history = get_option("mxchat_history_{$session_id}", []);
    $recent_history = array_slice($history, -5); // Get last 5 messages

    // Format conversation history
    $conversation_context = "";
    if (!empty($recent_history)) {
        $conversation_context = "*Recent Conversation:*\n";
        foreach ($recent_history as $hist_message) {
            $role_display = $hist_message['role'] === 'user' ? 'User' : 'AI';
            $conversation_context .= ">{$role_display}: {$hist_message['content']}\n";
        }
        $conversation_context .= "\n";
    }

    update_option("mxchat_mode_{$session_id}", 'agent');

    $webhook_data = [
        'blocks' => [
            [
                'type' => 'header',
                'text' => [
                    'type' => 'plain_text',
                    'text' => '🔔 New Live Agent Request',
                    'emoji' => true
                ]
            ],
            [
                'type' => 'section',
                'fields' => [
                    [
                        'type' => 'mrkdwn',
                        'text' => sprintf('*User ID:*\n`%s`', $user_id)
                    ],
                    [
                        'type' => 'mrkdwn',
                        'text' => sprintf('*Session ID:*\n`%s`', $session_id)
                    ]
                ]
            ]
        ]
    ];

    // Add conversation history if exists
    if (!empty($conversation_context)) {
        $webhook_data['blocks'][] = [
            'type' => 'section',
            'text' => [
                'type' => 'mrkdwn',
                'text' => $conversation_context
            ]
        ];
    }

    // Add the current message
    $webhook_data['blocks'][] = [
        'type' => 'section',
        'text' => [
            'type' => 'mrkdwn',
            'text' => sprintf('*Current Message:*\n%s', $message)
        ]
    ];

    // Add the reply button
    $webhook_data['blocks'][] = [
        'type' => 'actions',
        'elements' => [
            [
                'type' => 'button',
                'text' => [
                    'type' => 'plain_text',
                    'text' => '✍️ Reply',
                    'emoji' => true
                ],
                'value' => $session_id,
                'action_id' => 'reply_to_user',
                'style' => 'primary'
            ]
        ]
    ];

    $response = wp_remote_post($slack_webhook_url, [
        'body' => json_encode($webhook_data),
        'headers' => [
            'Content-Type' => 'application/json',
        ],
    ]);

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

    $success_message = $this->options['live_agent_notification_message'] ?? 'Live agent has been notified.';
    $this->mxchat_save_chat_message($session_id, 'bot', $success_message);

    $this->fallbackResponse = [
        'text' => $success_message,
        'html' => '',
        'images' => [],
        'chat_mode' => 'agent'
    ];

    wp_send_json([
        'success' => true,
        'text' => $success_message,
        'html' => '',
        'chat_mode' => 'agent',
        'session_id' => $session_id,
        'fallbackResponse' => $this->fallbackResponse
    ]);
    wp_die();
}
public function mxchat_send_user_message_to_agent($message, $user_id, $session_id) {
    $slack_webhook_url = $this->options['live_agent_webhook_url'] ?? '';

    if (empty($slack_webhook_url)) {
        //error_log(esc_html__('Slack Webhook URL is not configured.', 'mxchat'));
        return false;
    }

    $webhook_data = [
        'blocks' => [
            [
                'type' => 'header',
                'text' => [
                    'type' => 'plain_text',
                    'text' => esc_html__('📩 New Chat Message', 'mxchat'),
                    'emoji' => true
                ]
            ],
            [
                'type' => 'section',
                'fields' => [
                    [
                        'type' => 'mrkdwn',
                        'text' => sprintf(esc_html__('*User ID:*\n`%s`', 'mxchat'), $user_id)
                    ],
                    [
                        'type' => 'mrkdwn',
                        'text' => sprintf(esc_html__('*Session ID:*\n`%s`', 'mxchat'), $session_id)
                    ]
                ]
            ],
            [
                'type' => 'section',
                'text' => [
                    'type' => 'mrkdwn',
                    'text' => sprintf(esc_html__('*Message:*\n%s', 'mxchat'), $message)
                ]
            ],
            [
                'type' => 'actions',
                'elements' => [
                    [
                        'type' => 'button',
                        'text' => [
                            'type' => 'plain_text',
                            'text' => esc_html__('✍️ Reply', 'mxchat'),
                            'emoji' => true
                        ],
                        'value' => $session_id,
                        'action_id' => 'reply_to_user',
                        'style' => 'primary'
                    ]
                ]
            ]
        ]
    ];

    $response = wp_remote_post($slack_webhook_url, [
        'body' => json_encode($webhook_data),
        'headers' => [
            'Content-Type' => 'application/json',
        ],
    ]);

    if (is_wp_error($response)) {
        //error_log(esc_html__('Error sending message to Slack: ', 'mxchat') . $response->get_error_message());
        return false;
    }

    //error_log(esc_html__('Message sent to Slack successfully.', 'mxchat'));
    return true;
}
public function handle_slack_interaction(WP_REST_Request $request) {
    //error_log('Received Slack interaction');

    $payload = json_decode($request->get_param('payload'), true);
    //error_log('Payload: ' . print_r($payload, true));

    // Handle button click
    if ($payload['type'] === 'block_actions' && $payload['actions'][0]['action_id'] === 'reply_to_user') {
        $session_id = $payload['actions'][0]['value'];
        $trigger_id = $payload['trigger_id'];

        // Get Bot Token from settings
        $slack_token = $this->options['live_agent_bot_token'] ?? '';

        if (empty($slack_token)) {
            //error_log('Slack Bot Token not configured');
            return new WP_REST_Response(['error' => esc_html__('Bot token not configured', 'mxchat')], 400);
        }
        $response = wp_remote_post('https://slack.com/api/views.open', [
            'headers' => [
                'Content-Type' => 'application/json',
                'Authorization' => 'Bearer ' . $slack_token
            ],
            'body' => json_encode([
                'trigger_id' => $trigger_id,
                'view' => [
                    'type' => 'modal',
                    'callback_id' => 'reply_modal',
                    'title' => [
                        'type' => 'plain_text',
                        'text' => __('Reply to User', 'mxchat')
                    ],
                    'submit' => [
                        'type' => 'plain_text',
                        'text' => __('Send', 'mxchat')
                    ],
                    'close' => [
                        'type' => 'plain_text',
                        'text' => __('Cancel', 'mxchat')
                    ],
                    'blocks' => [
                        [
                            'type' => 'input',
                            'block_id' => 'reply_block',
                            'label' => [
                                'type' => 'plain_text',
                                'text' => sprintf(__('Reply to session: %s', 'mxchat'), $session_id)
                            ],
                            'element' => [
                                'type' => 'plain_text_input',
                                'action_id' => 'message',
                                'multiline' => true,
                                'placeholder' => [
                                    'type' => 'plain_text',
                                    'text' => __('Type your message here...', 'mxchat')
                                ]
                            ]
                        ]
                    ],
                    'private_metadata' => $session_id
                ]
            ])
        ]);

        //error_log('Views.open response: ' . print_r($response, true));

        // Return immediate acknowledgment
        return new WP_REST_Response(['ok' => true]);
    }

    // Handle modal submission
// Handle modal submission
if ($payload['type'] === 'view_submission') {
    $session_id = $payload['view']['private_metadata'];
    $message = $payload['view']['state']['values']['reply_block']['message']['value'];

    // Save the message (keep the message_id but don't include in response)
    $this->mxchat_save_chat_message($session_id, 'agent', $message);

    // Keep the original response format for Slack
    return new WP_REST_Response([
        'response_action' => 'clear'
    ]);
}

    // Default acknowledgment
    return new WP_REST_Response(['ok' => true]);
}

public function mxchat_handle_agent_response(WP_REST_Request $request) {
    //error_log('Received agent response request');
    //error_log('Request data: ' . print_r($request->get_params(), true));
   // error_log('Raw body: ' . file_get_contents('php://input'));

    // Get the data from Slack's slash command format
    $command_text = $request->get_param('text');
   // error_log('Command text: ' . $command_text);

    if (empty($command_text)) {
        //error_log(esc_html__('Agent response error: No command text received', 'mxchat'));
        return new WP_REST_Response([
            'error' => esc_html__('Command text is required. Format: /reply session_id message', 'mxchat')
        ], 400);
    }

    // Split the command text into session_id and message
    $parts = explode(' ', $command_text, 2);
    if (count($parts) !== 2) {
        //error_log('Agent response error: Invalid command format');
        return new WP_REST_Response([
            'error' => esc_html__('Invalid format. Use: /reply session_id message', 'mxchat')
        ], 400);
    }

    $session_id = sanitize_text_field($parts[0]);
    $message = sanitize_text_field($parts[1]);

    //error_log("Processing agent response - Session ID: $session_id, Message: $message");

    // Save the message
    $message_id = $this->mxchat_save_chat_message($session_id, 'agent', $message);

    if (!$message_id) {
       // error_log('Failed to save agent message');
        return new WP_REST_Response([
            'error' => esc_html__('Failed to save message', 'mxchat')
        ], 500);
    }

    // Return success response in Slack's expected format
    return new WP_REST_Response([
        'response_type' => 'in_channel',
        'text' => esc_html__("Message sent successfully to session $session_id", 'mxchat')
    ], 200);
}


public function mxchat_handle_switch_to_chatbot_intent($message, $user_id, $session_id) {
    //error_log(esc_html__("Switching back to chatbot mode via intent.", 'mxchat'));

    // Just update mode to AI
    update_option("mxchat_mode_{$session_id}", 'ai');

    // Initialize states
    $this->fallbackResponse = ['text' => '', 'html' => '', 'images' => []];
    $this->productCardHtml = '';

    // Set the response message
    $this->fallbackResponse['text'] = esc_html__('You are now chatting with the AI chatbot.', 'mxchat');

    return true; // Intent was handled
}




// For the word upload handler
public function mxchat_handle_word_upload() {
    // Delegate to word handler
    $this->word_handler->mxchat_handle_word_upload();
}

// For the word removal handler
public function mxchat_handle_word_remove() {
    // Delegate to word handler
    $this->word_handler->mxchat_handle_word_remove();
}

// For the word status check
public function mxchat_check_word_status() {
    // Delegate to word handler
    $this->word_handler->mxchat_check_word_status();
}


private function mxchat_get_user_identifier() {
    return MxChat_User::mxchat_get_user_identifier();
}

private function mxchat_generate_embedding($text, $api_key) {
        $endpoint = 'https://api.openai.com/v1/embeddings';

        $body = wp_json_encode([
            'input' => $text,
            'model' => 'text-embedding-ada-002'
        ]);

        $args = [
            'body' => $body,
            'headers' => [
                'Content-Type' => 'application/json',
                'Authorization' => 'Bearer ' . $api_key,
            ],
            'timeout' => 60,
            'redirection' => 5,
            'blocking' => true,
            'httpversion' => '1.0',
            'sslverify' => true,
        ];

        $response = wp_remote_post($endpoint, $args);

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

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

        if (isset($response_body['data'][0]['embedding']) && is_array($response_body['data'][0]['embedding'])) {
            return $response_body['data'][0]['embedding'];
        } else {
            return null;
        }
    }


private function mxchat_find_relevant_content($user_embedding) {
    //error_log('MXChat Vector Search: Starting content search...');

    // Retrieve the add-on settings from the database.
    $addon_options = get_option('mxchat_pinecone_addon_options', array());

    // Determine whether Pinecone is enabled.
    // We expect the sanitized setting to be a string '1' if enabled, otherwise '0'.
    $use_pinecone = (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1') ? 1 : 0;

    //error_log('Pinecone enabled flag: ' . $use_pinecone);

    if ($use_pinecone === 1) {
        //error_log('MXChat Vector Search: Using Pinecone database');
        return $this->find_relevant_content_pinecone($user_embedding);
    } else {
        //error_log('MXChat Vector Search: Using WordPress database');
        return $this->find_relevant_content_wordpress($user_embedding);
    }
}

private function find_relevant_content_wordpress($user_embedding) {
    global $wpdb;
    $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
    $cache_key = 'mxchat_system_prompt_embeddings';
    $batch_size = 500;

    // Retrieve embeddings from cache or database
    $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
    if ($embeddings === false) {
        $embeddings = [];
        $offset = 0;

        // Load in batches and build cache
        do {
            $query = $wpdb->prepare(
                "SELECT id, embedding_vector
                FROM {$system_prompt_table}
                LIMIT %d OFFSET %d",
                $batch_size,
                $offset
            );

            $batch = $wpdb->get_results($query);
            if (empty($batch)) {
                break;
            }

            $embeddings = array_merge($embeddings, $batch);
            $offset += $batch_size;

            // Free memory
            unset($batch);

        } while (true);

        if (empty($embeddings)) {
            return ''; // Return an empty string if no embeddings found
        }
        wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600);
    }

    // Initialize array to store relevant results with similarity scores
    $relevant_results = [];
    // Iterate through embeddings to calculate similarity
    foreach ($embeddings as $embedding) {
        $database_embedding = $embedding->embedding_vector
            ? unserialize($embedding->embedding_vector, ['allowed_classes' => false])
            : null;
        if (is_array($database_embedding) && is_array($user_embedding)) {
            $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding);
            $relevant_results[] = [
                'id' => $embedding->id,
                'similarity' => $similarity
            ];
        }
        // Free memory
        unset($database_embedding);
    }

    // Retrieve the similarity threshold
    $similarity_threshold = ((int) get_option('mxchat_similarity_threshold', 80)) / 100;

    // Filter and sort relevant results by similarity
    $relevant_results = array_filter($relevant_results, function ($result) use ($similarity_threshold) {
        return $result['similarity'] >= $similarity_threshold;
    });
    usort($relevant_results, function ($a, $b) {
        return $b['similarity'] <=> $a['similarity'];
    });

    // Limit to the top 5 results
    $top_results = array_slice($relevant_results, 0, 5);

    // Initialize the final content
    $content = '';

    // Fetch and combine content for the top results
    foreach ($top_results as $result) {
        $chunk_content = $this->fetch_content_with_product_links($result['id']);
        // Check if the content is PDF-related and add surrounding pages
        if (strpos($chunk_content, '{"document_type":"pdf"') !== false) {
            $surrounding_content = $wpdb->get_results($wpdb->prepare(
                "SELECT article_content FROM {$system_prompt_table}
                WHERE id IN (
                    (SELECT id FROM {$system_prompt_table} WHERE id < %d ORDER BY id DESC LIMIT 1),
                    (SELECT id FROM {$system_prompt_table} WHERE id > %d ORDER BY id ASC LIMIT 1)
                )",
                $result['id'],
                $result['id']
            ));
            // Add previous content if it exists
            if (!empty($surrounding_content[0])) {
                $content .= $surrounding_content[0]->article_content . "\n\n";
            }
            // Add the main chunk content
            $content .= $chunk_content . "\n\n";
            // Add next content if it exists
            if (!empty($surrounding_content[1])) {
                $content .= $surrounding_content[1]->article_content . "\n\n";
            }
        } else {
            // For non-PDF content, add directly
            $content .= $chunk_content . "\n\n";
        }
    }

    return trim($content);
}
/**
 * Find relevant content in Pinecone vector database
 */
private function find_relevant_content_pinecone($user_embedding) {
    $options = get_option('mxchat_pinecone_addon_options', array());
    $api_key = $options['mxchat_pinecone_api_key'] ?? '';
    $host = $options['mxchat_pinecone_host'] ?? '';

    if (empty($host) || empty($api_key)) {
        //error_log('Pinecone credentials not properly configured');
        return '';
    }

    // Get similarity threshold from WordPress settings
    $similarity_threshold = ((int) get_option('mxchat_similarity_threshold', 80)) / 100;

    // Prepare the query request for Pinecone
    $api_endpoint = "https://{$host}/query";

    $request_body = array(
        'vector' => $user_embedding,
        'topK' => 5,
        'includeMetadata' => true,
        'includeValues' => true
    );

    $response = wp_remote_post($api_endpoint, array(
        'headers' => array(
            'Api-Key' => $api_key,
            'accept' => 'application/json',
            'content-type' => 'application/json'
        ),
        'body' => wp_json_encode($request_body),
        'timeout' => 30
    ));

    if (is_wp_error($response)) {
        //error_log('Pinecone query error: ' . $response->get_error_message());
        return '';
    }

    $response_code = wp_remote_retrieve_response_code($response);
    if ($response_code !== 200) {
        //error_log('Pinecone API error: ' . wp_remote_retrieve_body($response));
        return '';
    }

    $results = json_decode(wp_remote_retrieve_body($response), true);
    if (empty($results['matches'])) {
        return '';
    }

    // Initialize the final content
    $content = '';

    // Process each match
    foreach ($results['matches'] as $match) {
        // Skip if similarity is below threshold
        if ($match['score'] < $similarity_threshold) {
            continue;
        }

        if (!empty($match['metadata']['text']) && !empty($match['metadata']['source_url'])) {
            // Add content with citation
            $content .= $match['metadata']['text'] . "\n";
            $content .= "Source: " . $match['metadata']['source_url'] . "\n\n";
        }
    }

    return trim($content);
}


private function mxchat_find_relevant_products($user_embedding) {
    //error_log('MXChat Vector Search: Starting product search...');

    // Retrieve the add-on settings from the database
    $addon_options = get_option('mxchat_pinecone_addon_options', array());

    // Determine whether Pinecone is enabled
    $use_pinecone = (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1') ? 1 : 0;

    //error_log('Pinecone enabled flag: ' . $use_pinecone);

    if ($use_pinecone === 1) {
        //error_log('MXChat Vector Search: Using Pinecone database for products');
        return $this->find_relevant_products_pinecone($user_embedding);
    } else {
        //error_log('MXChat Vector Search: Using WordPress database for products');
        return $this->find_relevant_products_wordpress($user_embedding);
    }
}

private function find_relevant_products_wordpress($user_embedding) {
    global $wpdb;
    $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
    $cache_key = 'mxchat_system_prompt_embeddings';
    $batch_size = 500;

    // Original WordPress database search logic
    // [Previous implementation remains the same]
    $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
    if ($embeddings === false) {
        $embeddings = [];
        $offset = 0;

        do {
            $query = $wpdb->prepare(
                "SELECT id, embedding_vector
                FROM {$system_prompt_table}
                LIMIT %d OFFSET %d",
                $batch_size,
                $offset
            );

            $batch = $wpdb->get_results($query);
            if (empty($batch)) {
                break;
            }

            $embeddings = array_merge($embeddings, $batch);
            $offset += $batch_size;

            unset($batch);

        } while (true);

        if (empty($embeddings)) {
            return '';
        }
        wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600);
    }

    $relevant_results = [];
    foreach ($embeddings as $embedding) {
        $database_embedding = $embedding->embedding_vector
            ? unserialize($embedding->embedding_vector, ['allowed_classes' => false])
            : null;
        if (is_array($database_embedding) && is_array($user_embedding)) {
            $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding);
            $relevant_results[] = [
                'id' => $embedding->id,
                'similarity' => $similarity
            ];
        }
        unset($database_embedding);
    }

    // Use fixed threshold for products
    $similarity_threshold = 0.85;

    $relevant_results = array_filter($relevant_results, function ($result) use ($similarity_threshold) {
        return $result['similarity'] >= $similarity_threshold;
    });
    usort($relevant_results, function ($a, $b) {
        return $b['similarity'] <=> $a['similarity'];
    });

    $top_results = array_slice($relevant_results, 0, 5);
    $content = '';

    foreach ($top_results as $result) {
        $chunk_content = $this->fetch_content_with_product_links($result['id']);
        $content .= $chunk_content . "\n\n";
    }

    return trim($content);
}

// Modified search function with correct filter syntax
private function find_relevant_products_pinecone($user_embedding) {
    //error_log('Starting Pinecone product search...');

    $options = get_option('mxchat_pinecone_addon_options', array());
    $api_key = $options['mxchat_pinecone_api_key'] ?? '';
    $host = $options['mxchat_pinecone_host'] ?? '';

    if (empty($host) || empty($api_key)) {
        //error_log('Pinecone credentials not properly configured for product search');
        return '';
    }

    $similarity_threshold = 0.85;
    $api_endpoint = "https://{$host}/query";

    $request_body = array(
        'vector' => $user_embedding,
        'topK' => 5,
        'includeMetadata' => true,
        'includeValues' => true,
        'filter' => array(
            'type' => 'product'
        )
    );

    //error_log('Sending request to Pinecone with body: ' . wp_json_encode($request_body));

    $response = wp_remote_post($api_endpoint, array(
        'headers' => array(
            'Api-Key' => $api_key,
            'accept' => 'application/json',
            'content-type' => 'application/json'
        ),
        'body' => wp_json_encode($request_body),
        'timeout' => 30
    ));

    if (is_wp_error($response)) {
        //error_log('Pinecone product query error: ' . $response->get_error_message());
        return '';
    }

    $response_code = wp_remote_retrieve_response_code($response);
    //error_log('Pinecone response code: ' . $response_code);

    if ($response_code !== 200) {
        //error_log('Pinecone API error during product search: ' . wp_remote_retrieve_body($response));
        return '';
    }

    $results = json_decode(wp_remote_retrieve_body($response), true);
    //error_log('Pinecone raw response: ' . wp_remote_retrieve_body($response));

    if (empty($results['matches'])) {
        //error_log('No matches found in Pinecone response');
        return '';
    }

    $content = '';
    foreach ($results['matches'] as $match) {
        if ($match['score'] < $similarity_threshold) {
            //error_log("Match below threshold: " . $match['score']);
            continue;
        }

        if (!empty($match['metadata']['text'])) {
            $content .= $match['metadata']['text'];
            if (!empty($match['metadata']['source_url'])) {
                $content .= "\n\nFor more details, check out this product: " . esc_url($match['metadata']['source_url']);
            }
            $content .= "\n\n";
        }
    }

    return trim($content);
}


private function fetch_content_with_product_links($most_relevant_id) {
    global $wpdb;
    $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';

    // Fetch the article content and associated product URL
    $query = $wpdb->prepare("SELECT article_content, source_url FROM {$system_prompt_table} WHERE id = %d", $most_relevant_id);
    $result = $wpdb->get_row($query);

    if ($result) {
        // Append the product link to the content if available
        $content = $result->article_content;
        if (!empty($result->source_url)) {
            $content .= "\n\nFor more details, check out this product: " . esc_url($result->source_url);
        }
        return $content;
    }

    return null;
}

// Function definition
private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $deepseek_api_key, $conversation_history) {
    try {
        if (!$relevant_content) {
            return esc_html__("I'm sorry, I couldn't find relevant information on that topic.", 'mxchat');
        }

        // Ensure conversation_history is an array
        if (!is_array($conversation_history)) {
            $conversation_history = array();
        }

        // Get selected model with default fallback
        $selected_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-4o';

        // Extract model prefix to determine the provider
        $model_parts = explode('-', $selected_model);
        $provider = strtolower($model_parts[0]);

        // Handle model selection based on provider prefix
        switch ($provider) {
            case 'claude':
                if (empty($claude_api_key)) {
                    throw new Exception(esc_html__('Claude API key is not configured', 'mxchat'));
                }
                return $this->mxchat_generate_response_claude(
                    $selected_model,
                    $claude_api_key,
                    $conversation_history,
                    $relevant_content
                );

            case 'grok':
                if (empty($xai_api_key)) {
                    throw new Exception(esc_html__('X.AI API key is not configured', 'mxchat'));
                }
                return $this->mxchat_generate_response_xai(
                    $selected_model,
                    $xai_api_key,
                    $conversation_history,
                    $relevant_content
                );

            case 'deepseek':
                if (empty($deepseek_api_key)) {
                    throw new Exception(esc_html__('DeepSeek API key is not configured', 'mxchat'));
                }
                return $this->mxchat_generate_response_deepseek(
                    $selected_model,
                    $deepseek_api_key,
                    $conversation_history,
                    $relevant_content
                );

            case 'gpt':
                if (empty($api_key)) {
                    throw new Exception(esc_html__('OpenAI API key is not configured', 'mxchat'));
                }
                return $this->mxchat_generate_response_openai(
                    $selected_model,
                    $api_key,
                    $conversation_history,
                    $relevant_content
                );

            default:
                // Default to OpenAI for custom models or unrecognized prefixes
                if (empty($api_key)) {
                    throw new Exception(esc_html__('OpenAI API key is not configured', 'mxchat'));
                }
                return $this->mxchat_generate_response_openai(
                    $selected_model,
                    $api_key,
                    $conversation_history,
                    $relevant_content
                );
        }
    } catch (Exception $e) {
        //error_log('MXChat Error: ' . $e->getMessage());
        return sprintf(
            esc_html__('An error occurred: %s', 'mxchat'),
            esc_html($e->getMessage())
        );
    }
}


private function mxchat_generate_response_deepseek($selected_model, $deepseek_api_key, $conversation_history, $relevant_content) {
    // Ensure conversation_history is an array
    if (!is_array($conversation_history)) {
        $conversation_history = array();
    }

    // Get system prompt instructions from options
    $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';

    // Create a new array for the formatted conversation
    $formatted_conversation = array();

    // Add system message first
    $formatted_conversation[] = array(
        'role' => 'system',
        'content' => $system_prompt_instructions . " " . $relevant_content
    );

    // Add the rest of the conversation history
    foreach ($conversation_history as $message) {
        if (is_array($message) && isset($message['role']) && isset($message['content'])) {
            $role = $message['role'];

            // Convert roles to supported format
            if ($role === 'bot' || $role === 'agent') {
                $role = 'assistant';
            }
            if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) {
                $role = 'user';
            }

            $formatted_conversation[] = array(
                'role' => $role,
                'content' => $message['content']
            );
        }
    }

    $body = json_encode([
        'model' => $selected_model,
        'messages' => $formatted_conversation,
        'temperature' => 0.8,
        'stream' => false
    ]);

    $args = [
        'body'        => $body,
        'headers'     => [
            'Content-Type' => 'application/json',
            'Authorization' => 'Bearer ' . $deepseek_api_key,
        ],
        'timeout'     => 60,
        'redirection' => 5,
        'blocking'    => true,
        'httpversion' => '1.0',
        'sslverify'   => true,
    ];

    $response = wp_remote_post('https://api.deepseek.com/v1/chat/completions', $args);

    if (is_wp_error($response)) {
        //error_log('DeepSeek API Error: ' . $response->get_error_message());
        return "Sorry, there was an error processing your request.";
    }

    $response_body = wp_remote_retrieve_body($response);
    $decoded_response = json_decode($response_body, true);

    if (isset($decoded_response['choices'][0]['message']['content'])) {
        return trim($decoded_response['choices'][0]['message']['content']);
    } else {
        //error_log('DeepSeek API Response Format Error: ' . print_r($decoded_response, true));
        return "Sorry, I couldn't process that request.";
    }
}

private function mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content) {
    // Ensure conversation_history is an array
    if (!is_array($conversation_history)) {
        $conversation_history = array();
    }

    // Get system prompt instructions from options
    $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';

    // Create a new array for the formatted conversation
    $formatted_conversation = array();

    // Add system message first
    $formatted_conversation[] = array(
        'role' => 'system',
        'content' => $system_prompt_instructions . " " . $relevant_content
    );

    // Add the rest of the conversation history
    foreach ($conversation_history as $message) {
        if (is_array($message) && isset($message['role']) && isset($message['content'])) {
            $role = $message['role'];

            // Convert roles to supported format
            if ($role === 'bot' || $role === 'agent') {
                $role = 'assistant';
            }
            if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) {
                $role = 'user';
            }

            $formatted_conversation[] = array(
                'role' => $role,
                'content' => $message['content']
            );
        }
    }

    $body = json_encode([
        'model' => $selected_model,
        'messages' => $formatted_conversation,
        'temperature' => 0.8,
        'stream' => false
    ]);

    $args = [
        'body'        => $body,
        'headers'     => [
            'Content-Type' => 'application/json',
            'Authorization' => 'Bearer ' . $api_key,
        ],
        'timeout'     => 60,
        'redirection' => 5,
        'blocking'    => true,
        'httpversion' => '1.0',
        'sslverify'   => true,
    ];

    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', $args);

    if (is_wp_error($response)) {
        //error_log('OpenAI API Error: ' . $response->get_error_message());
        return "Sorry, there was an error processing your request.";
    }

    $response_body = wp_remote_retrieve_body($response);
    $decoded_response = json_decode($response_body, true);

    if (isset($decoded_response['choices'][0]['message']['content'])) {
        return trim($decoded_response['choices'][0]['message']['content']);
    } else {
        //error_log('OpenAI API Response Format Error: ' . print_r($decoded_response, true));
        return "Sorry, I couldn't process that request.";
    }
}
private function mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content) {
    // Get system prompt instructions from options
    $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';

    // Add system prompt to relevant content
    $content_with_instructions = $system_prompt_instructions . " " . $relevant_content;

    // Prepend system instructions to the conversation history
    array_unshift($conversation_history, [
        'role' => 'system',
        'content' => "Here are your instructions: " . $content_with_instructions
    ]);

    // Ensure consistency: Replace 'bot' and 'agent' roles with supported values
    foreach ($conversation_history as &$message) {
        if ($message['role'] === 'bot') {
            $message['role'] = 'assistant';
        } elseif ($message['role'] === 'agent') {
            // Tag the message as coming from a live agent
            $message['role'] = 'assistant';
            if (!isset($message['metadata'])) {
                $message['metadata'] = ['source' => 'live_agent'];
            }
        }

        // Ensure all roles are valid
        if (!in_array($message['role'], ['system', 'assistant', 'user', 'function', 'tool'])) {
            $message['role'] = 'user'; // Default to 'user'
        }
    }


    // Build the request body
    $body = json_encode([
        'model' => $selected_model,
        'messages' => $conversation_history,
        'temperature' => 0.8,
        'stream' => false
    ]);

    // Set up the API request
    $args = [
        'body'        => $body,
        'headers'     => [
            'Content-Type' => 'application/json',
            'Authorization' => 'Bearer ' . $xai_api_key,
        ],
        'timeout'     => 60,
        'redirection' => 5,
        'blocking'    => true,
        'httpversion' => '1.0',
        'sslverify'   => true,
    ];

    // Make the API request
    $response = wp_remote_post('https://api.x.ai/v1/chat/completions', $args);

    // Process the response
    if (is_wp_error($response)) {
        return "Sorry, there was an error processing your request.";
    }

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

    if (isset($response_body['choices'][0]['message']['content'])) {
        return trim($response_body['choices'][0]['message']['content']);
    } else {
        return "Sorry, I couldn't process that request.";
    }
}

private function mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content) {
    // Get system prompt instructions from options
    $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';

    // Clean and validate conversation history
    foreach ($conversation_history as &$message) {
        // Convert bot and agent roles to assistant
        if ($message['role'] === 'bot' || $message['role'] === 'agent') {
            $message['role'] = 'assistant';
        }
        
        // Remove unsupported roles - Claude only supports 'assistant' and 'user'
        if (!in_array($message['role'], ['assistant', 'user'])) {
            $message['role'] = 'user';
        }

        // Ensure content field exists
        if (!isset($message['content']) || empty($message['content'])) {
            $message['content'] = '';
        }

        // Remove any unsupported fields
        $message = array_intersect_key($message, array_flip(['role', 'content']));
    }

    // Add relevant content as the latest user message
    $conversation_history[] = [
        'role' => 'user',
        'content' => $relevant_content
    ];

    // Build request body
    $body = json_encode([
        'model' => $selected_model,
        'max_tokens' => 1000,
        'temperature' => 0.8,
        'messages' => $conversation_history,
        'system' => $system_prompt_instructions
    ]);

    // Set up API request
    $args = [
        'body' => $body,
            'headers' => [
                'Content-Type' => 'application/json',
                'x-api-key' => $claude_api_key,
                'anthropic-version' => '2023-06-01'
            ],
        'timeout' => 60,
        'redirection' => 5,
        'blocking' => true,
        'httpversion' => '1.0',
        'sslverify' => true,
    ];

    // Make API request
    $response = wp_remote_post('https://api.anthropic.com/v1/messages', $args);

    // Check for WordPress errors
    if (is_wp_error($response)) {
        //error_log("Claude API request error: " . $response->get_error_message());
        return "Sorry, there was an error connecting to the API.";
    }

    // Check HTTP response code
    $http_code = wp_remote_retrieve_response_code($response);
    if ($http_code !== 200) {
        $error_body = wp_remote_retrieve_body($response);
        //error_log("Claude API HTTP error: " . $http_code . " - " . $error_body);
        
        // Try to extract error message from response
        $error_data = json_decode($error_body, true);
        $error_message = isset($error_data['error']['message']) ? 
            $error_data['error']['message'] : 
            "HTTP error " . $http_code;
            
        return "Sorry, the API returned an error: " . $error_message;
    }

    // Parse response
    $response_body = json_decode(wp_remote_retrieve_body($response), true);
    
    // Check for JSON decode errors
    if (json_last_error() !== JSON_ERROR_NONE) {
        //error_log("Claude API JSON decode error: " . json_last_error_msg());
        return "Sorry, there was an error processing the API response.";
    }

    // Extract and validate response content
    if (isset($response_body['content']) && 
        is_array($response_body['content']) && 
        !empty($response_body['content']) && 
        isset($response_body['content'][0]['text'])) {
        return trim($response_body['content'][0]['text']);
    }

    // Log unexpected response format
    //error_log("Claude API unexpected response format: " . print_r($response_body, true));
    return "Sorry, I received an unexpected response format from the API.";
}
public function mxchat_dismiss_pre_chat_message() {
    // Get and sanitize the user identifier
    $user_id = $this->mxchat_get_user_identifier();
    $user_id = sanitize_key($user_id);

    // Set a transient to track that the user has dismissed the pre-chat message
    $transient_key = 'mxchat_pre_chat_message_dismissed_' . $user_id;
    set_transient($transient_key, true, DAY_IN_SECONDS);

    wp_send_json_success();
}

public function mxchat_check_pre_chat_message_status() {
    // Get and sanitize the user identifier
    $user_id = $this->mxchat_get_user_identifier();
    $user_id = sanitize_key($user_id);

    // Check if the transient exists (i.e., if the message was dismissed)
    $transient_key = 'mxchat_pre_chat_message_dismissed_' . $user_id;
    $dismissed = get_transient($transient_key);

    // Log the result to see if it's being set correctly
    //error_log("Check pre-chat message dismissed for $user_id: " . ($dismissed ? 'Yes' : 'No'));

    if ($dismissed) {
        wp_send_json_success(['dismissed' => true]);
    } else {
        wp_send_json_success(['dismissed' => false]);
    }

    wp_die();
}

private function mxchat_calculate_cosine_similarity($vectorA, $vectorB) {
        if (!is_array($vectorA) || !is_array($vectorB) || empty($vectorA) || empty($vectorB)) {
            return 0;
        }

        $dotProduct = array_sum(array_map(function ($a, $b) {
            return $a * $b;
        }, $vectorA, $vectorB));
        $normA = sqrt(array_sum(array_map(function ($a) {
            return $a * $a;
        }, $vectorA)));
        $normB = sqrt(array_sum(array_map(function ($b) {
            return $b * $b;
        }, $vectorB)));

        if ($normA == 0 || $normB == 0) {
            return 0;
        }

        return $dotProduct / ($normA * $normB);
    }

public function mxchat_enqueue_scripts_styles() {
    // Define version numbers for the styles and scripts
    $chat_style_version = '2.0.5'; // Replace with your actual version
    $chat_script_version = '2.0.5'; // Replace with your actual version

    // Enqueue the script
    wp_enqueue_script(
        'mxchat-chat-js',
        plugin_dir_url(__FILE__) . '../js/chat-script.js',
        array('jquery'),
        $chat_script_version,
        true
    );

    // Enqueue the CSS
    wp_enqueue_style(
        'mxchat-chat-css',
        plugin_dir_url(__FILE__) . '../css/chat-style.css',
        array(),
        $chat_style_version
    );

    // Fetch options from the database
    $this->options = get_option('mxchat_options');
    $prompts_options = get_option('mxchat_prompts_options', array());

    // Prepare settings for JavaScript
    $style_settings = array(
        'ajax_url' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce('mxchat_chat_nonce'),
        'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
        'rate_limit_message' => $this->options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.',
        'complianz_toggle' => isset($this->options['complianz_toggle']) && $this->options['complianz_toggle'] === 'on',
        'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
        'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
        'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
        'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
        'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
        'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
        'close_button_color' => $this->options['close_button_color'] ?? '#fff',
        'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
        'chatbot_bg_color' => $this->options['chatbot_bg_color'] ?? '#fff',
        'icon_color' => $this->options['icon_color'] ?? '#fff',
        'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
        'chat_persistence_toggle' => $this->options['chat_persistence_toggle'] ?? 'off',
        'appendWidgetToBody' => $this->options['append_to_body'] ?? 'off', // Example for consistency

        'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
        'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
        'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
        'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
        'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
        'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',

        'use_pinecone' => $prompts_options['mxchat_use_pinecone'] ?? '0',
        'pinecone_enabled' => isset($prompts_options['mxchat_use_pinecone']) && $prompts_options['mxchat_use_pinecone'] === '1'
    );

    // Pass the settings to the script
    wp_localize_script('mxchat-chat-js', 'mxchatChat', $style_settings);
}


public function mxchat_reset_rate_limits() {
        global $wpdb;

        // Define a cache key pattern for rate limits
        $cache_key_pattern = 'mxchat_chat_limit_%';

        // Retrieve all option names matching the pattern
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
        $option_names = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'");

        // db call ok; no-cache ok
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- db call ok
        $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'");

        // Clear the relevant cache entries
        foreach ($option_names as $option_name) {
            wp_cache_delete($option_name, 'options');
        }

        // Optionally, clear a general cache if you have one
        wp_cache_delete('mxchat_all_chat_limits', 'options');
    }

private function mxchat_fetch_woocommerce_products() {
    // Ensure WooCommerce is active
    if (!class_exists('WooCommerce')) {
        return [];
    }

    $args = array(
        'post_type' => 'product',
        'post_status' => 'publish',
        'posts_per_page' => -1,
    );

    $products = get_posts($args);
    $product_data = [];

    foreach ($products as $product) {
        $product_id = $product->ID;
        $product_obj = wc_get_product($product_id);

        $product_data[] = array(
            'id' => $product_id,
            'name' => $product_obj->get_name(),
            'description' => $product_obj->get_description(),
            'short_description' => $product_obj->get_short_description(),
            'url' => get_permalink($product_id),
            'price' => $product_obj->get_regular_price(),
            'sale_price' => $product_obj->get_sale_price(),
            'stock_status' => $product_obj->get_stock_status(),
            'sku' => $product_obj->get_sku(),
            'in_stock' => $product_obj->is_in_stock(),
            'total_sales' => $product_obj->get_total_sales(),
        );
    }

    return $product_data;
}

}
?>

````
