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

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

- Page: https://pluginprobe.com/plugins/mxchat-basic/1.5.5/code/includes/class-mxchat-integrator.php
- Raw: https://pluginprobe.com/plugins/mxchat-basic/1.5.5/raw/includes/class-mxchat-integrator.php
- Modified: 2024-12-14T03:26:46+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/1.5.5/code/includes/class-mxchat-integrator.php#L10-L20`.

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

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

public function __construct() {
    $this->options = get_option('mxchat_options');
    $this->chat_count = get_option('mxchat_chat_count', 0);
    $this->word_handler = new MXChat_Word_Handler($this->options);

    // Add WooCommerce hooks
    add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3);

    // Ensure embeddings are removed when a product is moved to trash or permanently deleted
    add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete'));
    add_action('before_delete_post', array($this, 'mxchat_handle_product_delete'));

    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'));
}

public function mxchat_handle_product_change($post_id, $post, $update) {
    // Ensure this is a product post type
    if ($post->post_type !== 'product') {
        return;
    }

    // Only generate embeddings if the product is published
    if ($post->post_status === 'publish') {
        // Delay the embedding slightly to ensure all product data is available
        add_action('shutdown', function() use ($post_id) {
            $product = wc_get_product($post_id);
            if ($product && $product->get_price() !== '') {
                $this->mxchat_store_product_embedding($product);
            } else {
                // Optionally, log or handle the case where product data is incomplete
               // error_log("Product {$post_id} does not have complete data. Embedding not generated.");
            }
        });
    }
}

public function mxchat_handle_product_delete($post_id) {
    if (get_post_type($post_id) !== 'product') {
        return;
    }

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

    // Delete the embedding associated with this product
    $wpdb->delete($table_name, array('source_url' => get_permalink($post_id)), array('%s'));
}

private function mxchat_store_product_embedding($product) {
    if (isset($this->options['enable_woocommerce_integration']) && $this->options['enable_woocommerce_integration'] === '1') {

        $source_url = get_permalink($product->get_id());
        $regular_price = $product->get_regular_price();
        $sale_price = $product->get_sale_price();
        $price = $sale_price ?: $regular_price;

        $description = $product->get_description() . "\n\n" .
                       "Short Description: " . $product->get_short_description() . "\n" .
                       "Price: " . $regular_price . "\n" .
                       "Sale Price: " . ($sale_price ?: 'N/A') . "\n" .
                       "SKU: " . $product->get_sku();

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

        // Delete any existing embedding for this product
        $wpdb->delete($table_name, array('source_url' => $source_url), array('%s'));

        // Submit the new content and embedding to the database
        MxChat_Utils::submit_content_to_db($description, $source_url, $this->options['api_key']);
    }
}





    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' => 'Session ID missing.']);
        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 = [];
    $max_tokens = 120000; // Adjust based on your model's context window
    $reserved_tokens = 4000; // Reserve tokens for current query and system/context prompt
    $current_token_count = 0;

    foreach ($history as $entry) {
        // Skip messages containing HTML
        if ($entry['content'] !== strip_tags($entry['content'])) {
            continue;
        }

        // Calculate tokens for this entry
        $tokens_in_entry = str_word_count($entry['content']) * 1.5; // Rough estimate: ~1.5 tokens per word
        $current_token_count += $tokens_in_entry;

        // If adding this entry exceeds the limit, stop
        if ($current_token_count + $reserved_tokens > $max_tokens) {
            break;
        }

        $formatted_history[] = [
            'role' => $entry['role'],
            'content' => $entry['content']
        ];
    }

    return $formatted_history;
}


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

    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('MxChat REST routes registered');
}

/**
 * Verify valid chat session
 */
public function verify_chat_session($request) {
    $session_id = $request->get_param('session_id');
    if (empty($session_id)) {
        //error_log('Empty session ID in chat request');
        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('Slack signing secret not configured');
        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('Slack request timestamp too old');
        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 "event: error\ndata: Missing session_id\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 "event: newMessages\ndata: " . json_encode(array_values($new_messages)) . "\n\n";
    } else {
        // Keep the connection alive
        echo "event: keepAlive\ndata: {}\n\n";
    }
    flush();
    exit;
}



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

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

        // Store the agent name in session metadata if it's not already set
        $session_meta_key = "mxchat_agent_name_{$session_id}";
        if (empty(get_option($session_meta_key))) {
            update_option($session_meta_key, $agent_name);
        }
    }

    // Fetch the agent name from session metadata if available
    $agent_name = $agent_name ?: get_option("mxchat_agent_name_{$session_id}", 'Unknown Agent');

    // Define the table name for chat transcripts
    $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';

    // Generate a unique message ID
    $message_id = uniqid();

    // Use agent name as the user identifier if set
    $user_id = 0;
    $user_identifier = $agent_name ?: MxChat_User::mxchat_get_user_identifier();
    $user_email = MxChat_User::mxchat_get_user_email();

    // Save the message to the session history
    $history = get_option("mxchat_history_{$session_id}", []);
    $history[] = [
        'id' => $message_id,
        'role' => $role,
        'content' => $message,
        'timestamp' => round(microtime(true) * 1000),
        'agent_name' => $agent_name, // Add agent name to the message history
    ];
    update_option("mxchat_history_{$session_id}", $history);

    // Save the message to the database
    $wpdb->insert($table_name, [
        'user_id' => $user_id,
        'user_identifier' => $user_identifier,
        'user_email' => $user_email,
        'session_id' => $session_id,
        'role' => $role,
        'message' => $message,
        'timestamp' => current_time('mysql', 1),
    ]);

    return $message_id;
}

public function mxchat_handle_chat_request() {
    global $wpdb;

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

    // Determine if user is logged in
    $is_logged_in = is_user_logged_in();

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

    // 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']
                : 'Rate limit exceeded. Please try again later.';

            // 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('Session ID is missing.');
        wp_die();
    }

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


    $message = wp_strip_all_tags($_POST['message'], false);
    $message = trim($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'] ??
                           'Thank you! Your coupon is on the way!';

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

    // Initialize response variables
    $this->fallbackResponse = ['text' => '', 'html' => '', 'images' => []];
    $this->productCardHtml = '';
    $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', 'Switched to AI chat mode');
            $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' => 'Message sent to live agent.'
                ]);
            } catch (\Exception $e) {
                //error_log("Error sending message to agent: " . $e->getMessage());
                wp_send_json_error('Failed to send message to agent');
            }
            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'] ??
                            "The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.",
                            $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'] ??
                            "I've processed the new PDF '{$pdf_filename}'. What questions do you have about it?";

                        // 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'] ??
                            "Sorry, I couldn't process the PDF. Please ensure it's a valid file.";
                        $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('Error processing your message.');
        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'],
        $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();
}


// 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);
}

// 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;

    // Check chat mode
    $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai');
    //error_log("Checking intents for mode: " . $chat_mode);

    // Generate the user embedding
    $user_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
    if (!is_array($user_embedding)) {
        //error_log("Failed to generate user embedding");
        return false;
    }

    // Fetch intents from the database
    $table_name = $wpdb->prefix . 'mxchat_intents';

    if ($chat_mode === 'agent') {
        // Only fetch the switch intent when in agent mode
        $query = $wpdb->prepare(
            "SELECT * FROM $table_name WHERE callback_function = %s",
            'mxchat_handle_switch_to_chatbot_intent'
        );
        //error_log("Searching for switch intent with query: " . $query);
        $intents = $wpdb->get_results($query);
        //error_log("Found " . count($intents) . " switch intents");
    } else {
        $intents = $wpdb->get_results("SELECT * FROM $table_name");
    }

    if (empty($intents)) {
        //error_log("No intents found in database");
        return false;
    }

    $highest_similarity = -INF;
    $matched_intent = null;

    foreach ($intents as $intent) {
        //error_log("Checking intent: " . $intent->intent_label);
        $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("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;
        //error_log("Similarity for " . $intent->intent_label . ": " . $similarity . " (threshold: " . $intent_threshold . ")");

        if ($similarity >= $intent_threshold && $similarity > $highest_similarity) {
            $highest_similarity = $similarity;
            $matched_intent = $intent;
            //error_log("New best match: " . $intent->intent_label . " with similarity " . $similarity);
        }
    }

    if ($matched_intent && method_exists($this, $matched_intent->callback_function)) {
        //error_log("Calling callback function: " . $matched_intent->callback_function);
        call_user_func([$this, $matched_intent->callback_function], $message, $user_id, $session_id);
        return true;
    }

    //error_log("No matching intent found");
    return false;
}

//verified good
public function mxchat_handle_order_history($message, $user_id, $session_id) {
    if (!class_exists('WooCommerce')) {
        $this->fallbackResponse['text'] = "I can't access order information right now. The order system seems to be unavailable.";
        return true;
    }

    $orderDetails = MxChat_WooCommerce::mxchat_fetch_user_orders_details('all');

    if (empty($orderDetails)) {
        $this->fallbackResponse['text'] = "I don't see any orders associated with your account. Are you logged in?";
        return true;
    }

    // Generate AI prompt with context
    $prompt = "User asked about their orders: '{$message}'\n\n";
    $prompt .= "Order information:\n";
    foreach ($orderDetails as $order) {
        $items_list = array_map(function($item) {
            return "{$item['name']} ({$item['quantity']})";
        }, $order['items']);

        $prompt .= "Order #{$order['order_id']}: {$order['formatted_total']} on {$order['date']}\n";
        $prompt .= "Items: " . implode(', ', $items_list) . "\n";
    }

    $prompt .= "\nProvide a natural, conversational response focusing on the specific information the user asked about. ";
    $prompt .= "If they ask about a specific order or detail, provide just that information. ";
    $prompt .= "If they ask about license keys or sensitive information, inform them to check their email or contact support.";

    // Get AI response
    $ai_response = $this->mxchat_call_ai_api($prompt);
    $this->fallbackResponse['text'] = $ai_response['text'];

    return true;
}


//verified good
public function mxchat_handle_product_inquiry($message, $user_id, $session_id) {
   // Attempt to extract product ID from the message
   $product_id = MxChat_WooCommerce::mxchat_extract_product_id_from_message($message);

   // Use last discussed product if no product ID is found
   if (!$product_id) {
       $product_id = get_transient('mxchat_last_discussed_product_' . $user_id);
   }

   // Handle specific product inquiries
   if ($product_id && class_exists('WooCommerce')) {
       $product = wc_get_product($product_id);
       if ($product) {
           // Prepare product details
           $product_name = esc_html($product->get_name());
           $product_price = $product->get_price_html();
           $product_image_url = esc_url(wp_get_attachment_url($product->get_image_id()));
           $product_url = esc_url(get_permalink($product_id));
           $product_id_attr = esc_attr($product_id);

           // Get product description
           $product_description = $product->get_description() ?: $product->get_short_description();

           // Get relevant content based on user query
           $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
           $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding);

           // Build AI prompt with context and product details
           $ai_prompt = "You are a knowledgeable product assistant. ";
           $ai_prompt .= "Respond to this user query: '{$message}'\n\n";

           // Add relevant content if available
           if (!empty($relevant_content)) {
               $ai_prompt .= "Relevant information from our knowledge base:\n{$relevant_content}\n\n";
           }

           // Add product details
           $ai_prompt .= "Product details:\n";
           $ai_prompt .= "Name: {$product_name}\n";
           $ai_prompt .= "Price: " . strip_tags($product_price) . "\n";
           if ($product_description) {
               $ai_prompt .= "Description: {$product_description}\n";
           }

           // Add instructions for response format
           $ai_prompt .= "\nInstructions:\n";
           $ai_prompt .= "1. Address the user's specific question or concern about the product\n";
           $ai_prompt .= "2. Incorporate relevant information from our knowledge base if provided\n";
           $ai_prompt .= "3. Highlight key product features that relate to their query\n";
           $ai_prompt .= "4. Include a natural suggestion to check out the product\n";
           $ai_prompt .= "5. Keep the response conversational and helpful\n";

           // Get AI response
           $ai_response = $this->mxchat_call_ai_api($ai_prompt);

           // Generate product card HTML
           $product_card_html = <<<HTML
<div class="mxchat-product-card">
   <a href="{$product_url}" target="_blank">
       <img src="{$product_image_url}" alt="{$product_name}" class="mxchat-product-image" />
       <h3 class="mxchat-product-name">{$product_name}</h3>
   </a>
   <div class="mxchat-product-price">{$product_price}</div>
   <button class="mxchat-add-to-cart-button" data-product-id="{$product_id_attr}">Add to Cart</button>
</div>
HTML;

           // Save the response and product card
           $this->productCardHtml = $product_card_html;
           $this->fallbackResponse = [
               'text' => $ai_response['text'],
               'html' => $this->productCardHtml,
           ];

           // Save the last discussed product
           set_transient('mxchat_last_discussed_product_' . $user_id, $product_id, HOUR_IN_SECONDS);
           return true;
       }
   }

   // If no product found, skip intent handling
   return false;
}

//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'] ?? "Would you like to join our mailing list? Please provide your email below.");

    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 = "Create an image of " . 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="Generated Image" class="mxchat-generated-image" />';

        $response_text = "Here is the image I generated:";
    } else {
        $response_text = "I'm sorry, but I couldn't generate an image based on your request.";
        $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' => "Error generating image: " . $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' => "Failed to generate image."];
    }
}

//very good
public function mxchat_handle_search_request($message, $user_id, $session_id) {
    // Sanitize user input
    $search_query = preg_replace('/^search the web for\s+/i', '', $message);
    $search_query = preg_replace('/^show me the latest news about\s+/i', '', $message);
    $search_query = preg_replace('/^what\'?s the news in\s+/i', '', $message);
    $search_query = preg_replace('/^news about\s+/i', '', $message);
    $search_query = trim(sanitize_text_field($search_query));

    if (empty($search_query)) {
        $this->fallbackResponse = [
            'text' => __("Please provide a valid search query.", 'mxchat'),
        ];
        return;
    }

    // Check if the query is likely related to news
    $is_news_search = preg_match("/\bnews\b/i", $message);

    // Retrieve settings
    $options = get_option('mxchat_options');

    $api_key = isset($options['brave_api_key']) ? sanitize_text_field($options['brave_api_key']) : '';
    $news_count = isset($options['brave_news_count']) ? intval($options['brave_news_count']) : 3;
    $country = isset($options['brave_country']) ? sanitize_text_field($options['brave_country']) : 'us';
    $language = isset($options['brave_language']) ? sanitize_text_field($options['brave_language']) : 'en';

    if (empty($api_key)) {

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

    // Set API endpoint and parameters based on search type
    $api_url = $is_news_search
        ? 'https://api.search.brave.com/res/v1/news/search'
        : 'https://api.search.brave.com/res/v1/web/search';

    $query_args = [
        'q' => urlencode($search_query),
    ];

    if ($is_news_search) {
        $query_args['count'] = $news_count;
        $query_args['country'] = $country;
        $query_args['search_lang'] = $language;
    }

    $api_url = add_query_arg($query_args, $api_url);

    // Implement caching
    $transient_key = 'mxchat_search_' . md5($api_url);
    $body = get_transient($transient_key);

    if (false === $body) {
        $args = [
            'headers' => [
                'Accept' => 'application/json',
                'Accept-Encoding' => 'gzip',
                'Authorization' => 'Bearer ' . $api_key,
                'x-subscription-token' => $api_key,
            ],
            'timeout' => 10,
        ];

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

        if (is_wp_error($response)) {

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

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

  // Process the API response and build a more informative summary
    $text_summary = "";

    if ($is_news_search && isset($body['results']) && is_array($body['results']) && count($body['results']) > 0) {
        $text_summary = "Here are some recent news articles:\n\n";

        foreach ($body['results'] as $news) {
            $title = isset($news['title']) ? esc_html($news['title']) : __('No Title', 'mxchat');
            $url = isset($news['url']) ? esc_url($news['url']) : '#';
            $description = isset($news['description']) ? esc_html(strip_tags($news['description'])) : __('No description available.', 'mxchat');
            $age = isset($news['age']) ? esc_html($news['age']) : '';
            $hostname = isset($news['meta_url']['hostname']) ? esc_html($news['meta_url']['hostname']) : '';
            $thumbnail = isset($news['thumbnail']['src']) ? esc_url($news['thumbnail']['src']) : '';

            // Build text-only news item summary
            $text_summary .= "Title: {$title}\nURL: {$url}\nDescription: {$description}\nPublished: {$age}\n";
            if (!empty($hostname)) {
                $text_summary .= "Source: {$hostname}\n";
            }
            if (!empty($thumbnail)) {
                $text_summary .= "Thumbnail: ![Image]({$thumbnail})\n";
            }
            if (!empty($news['extra_snippets'])) {
                $extra_snippet_text = implode(" ", $news['extra_snippets']);
                $text_summary .= "Additional Info: {$extra_snippet_text}\n";
            }
            $text_summary .= "\n"; // Separate entries
        }
    } elseif (!$is_news_search && isset($body['web']['results']) && is_array($body['web']['results']) && count($body['web']['results']) > 0) {
        $text_summary = "Here are some relevant articles based on your query:\n\n";

        foreach ($body['web']['results'] as $result) {
            $title = isset($result['title']) ? esc_html($result['title']) : __('No Title', 'mxchat');
            $url = isset($result['url']) ? esc_url($result['url']) : '#';
            $description = isset($result['description']) ? esc_html(strip_tags($result['description'])) : __('No description available.', 'mxchat');
            $hostname = isset($result['meta_url']['hostname']) ? esc_html($result['meta_url']['hostname']) : '';
            $thumbnail = isset($result['thumbnail']['src']) ? esc_url($result['thumbnail']['src']) : '';

            // Build text-only web item summary
            $text_summary .= "Title: {$title}\nURL: {$url}\nDescription: {$description}\n";
            if (!empty($hostname)) {
                $text_summary .= "Source: {$hostname}\n";
            }
            if (!empty($thumbnail)) {
                $text_summary .= "Thumbnail: ![Image]({$thumbnail})\n";
            }
            if (!empty($result['extra_snippets'])) {
                $extra_snippet_text = implode(" ", $result['extra_snippets']);
                $text_summary .= "Additional Info: {$extra_snippet_text}\n";
            }
            $text_summary .= "\n"; // Separate entries
        }
    } else {

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

    $this->fallbackResponse = [
        'text' => $text_summary,
    ];


}

//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']) : __('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 = "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.";

    // 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);
    }
}

public function mxchat_handle_add_to_cart_intent($message, $user_id, $session_id) {
    if (!class_exists('WooCommerce')) {
        return $this->generate_intent_response([
            'intent' => 'add_to_cart',
            'status' => 'error',
            'reason' => 'woocommerce_not_available'
        ], $session_id);
    }

    // First try to find product in current message
    $product_id = $this->find_product_in_message($message);

    // If no product found in message, check previously mentioned product
    if (!$product_id) {
        $sanitized_user_id = sanitize_key($user_id);
        $product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
    }

    // If still no product found
    if (!$product_id) {
        return $this->generate_intent_response([
            'intent' => 'add_to_cart',
            'status' => 'error',
            'reason' => 'no_product_context',
            'action_needed' => 'request_product_name',
            'searched_message' => $message
        ], $session_id);
    }

    $product = wc_get_product($product_id);
    if (!$product) {
        return $this->generate_intent_response([
            'intent' => 'add_to_cart',
            'status' => 'error',
            'reason' => 'product_not_found',
            'product_id' => $product_id
        ], $session_id);
    }

    $added = WC()->cart->add_to_cart($product_id);
    if ($added) {
        return $this->generate_intent_response([
            'intent' => 'add_to_cart',
            'status' => 'success',
            'product' => [
                'name' => $product->get_name(),
                'id' => $product_id
            ],
            'cart_url' => wc_get_cart_url(),
            'available_actions' => [
                'view_cart',
                'checkout',
                'continue_shopping'
            ]
        ], $session_id);
    } else {
        return $this->generate_intent_response([
            'intent' => 'add_to_cart',
            'status' => 'error',
            'reason' => 'add_to_cart_failed',
            'product' => [
                'name' => $product->get_name(),
                'id' => $product_id
            ]
        ], $session_id);
    }
}

private function find_product_in_message($message) {
    // Clean and prepare the message
    $search_query = strip_tags(strtolower($message));

    // Get all products
    $args = array(
        'post_type' => 'product',
        'post_status' => 'publish',
        'posts_per_page' => -1,
        'fields' => 'ids'
    );

    $products = get_posts($args);
    $best_match = null;
    $highest_similarity = 0;

    foreach ($products as $product_id) {
        $product = wc_get_product($product_id);
        if (!$product) continue;

        $product_name = strtolower($product->get_name());
        $sku = strtolower($product->get_sku());

        // Check for exact matches first
        if (strpos($search_query, $product_name) !== false) {
            return $product_id;
        }

        // Check SKU if available
        if ($sku && strpos($search_query, $sku) !== false) {
            return $product_id;
        }

        // Calculate similarity for fuzzy matching
        $similarity = 0;
        similar_text($search_query, $product_name, $similarity);

        // If this product name is more similar than previous matches
        if ($similarity > $highest_similarity && $similarity > 70) { // 70% threshold
            $highest_similarity = $similarity;
            $best_match = $product_id;
        }
    }

    return $best_match;
}
// 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->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 = "INTENT CONTEXT:\n";

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

    return $context_string;
}


public function mxchat_handle_checkout_intent($message, $user_id, $session_id) {
    if (!class_exists('WooCommerce')) {
        $this->fallbackResponse['text'] = "I apologize, but the checkout feature isn't available at the moment.";
        return true;
    }

    // Check if cart has items
    if (WC()->cart->is_empty()) {
        $this->fallbackResponse['text'] = "Your cart is empty at the moment. Would you like to see our products?";
        return true;
    }

    // Get cart summary
    $cart_count = WC()->cart->get_cart_contents_count();
    $cart_total = WC()->cart->get_total();

    // Get and validate checkout URL
    $checkout_url = wc_get_checkout_url();
    if (!$checkout_url) {
        $this->fallbackResponse['text'] = "I'm having trouble accessing the checkout page. Please try again in a moment.";
        return true;
    }

    wp_send_json([
        'text' => sprintf(
            "You have %d item%s in your cart totaling %s. I'll redirect you to checkout now.",
            $cart_count,
            $cart_count > 1 ? 's' : '',
            strip_tags($cart_total)
        ),
        'redirect_url' => esc_url_raw($checkout_url)
    ]);
    wp_die();
}


//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('Loops API key or mailing list ID is missing.');
        return;
    }

    $data = array(
        'email'        => $email,
        'subscribed'   => true,
        'source'       => 'MxChat AI Chatbot',
        '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('Error adding email to Loops: ' . $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('Loops API responded with code ' . $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.";
    $success_text = $this->options['pdf_intent_success_text'] ?? "I've processed the PDF. What questions do you have about it?";
    $error_text = $this->options['pdf_intent_error_text'] ?? "Sorry, I couldn't process the PDF. Please ensure it's a valid file.";

    // 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("Failed to download PDF. Error: " . 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("Invalid MIME type detected for PDF: $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("PDF exceeds the maximum allowed pages: " . count($pages));
            if (filter_var($pdf_source, FILTER_VALIDATE_URL)) {
                unlink($temp_file);
            }
            return 'too_many_pages';
        }

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

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

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

            if ($embedding) {
                $embeddings[] = [
                    'page_number' => $page_number + 1,
                    'embedding' => $embedding,
                    'text' => $text,
                ];
            } else {
                //error_log("Failed to generate embedding for page " . ($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("Error parsing or processing PDF: " . $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("find_relevant_pdf_pages called.");

    $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('Missing required parameters.');
        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('Invalid file type. Only PDF files are allowed.');
        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('Failed to upload file.');
        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'] ??
            "The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.",
            $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'] ??
            'The uploaded PDF appears to be empty or contains unsupported content.';
        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'] ??
            "I've processed the PDF. What questions do you have about it?";

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

    unlink($pdf_path);
    $error_message = $this->options['pdf_intent_error_text'] ??
        'Sorry, I couldn\'t process the PDF. Please ensure it\'s a valid file.';
    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('Session ID missing.');
        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' => 'PDF removed successfully.'
    ]);
    wp_die();
}


/**
 * Calls the AI API with the provided prompt.
 *
 * @param string $prompt The prompt to send to the AI.
 * @return array An array containing the AI's response text.
 */
private function mxchat_call_ai_api( $prompt ) {
    //error_log( 'Calling AI API with the provided prompt.' );

    $api_key = $this->options['api_key'];
    if ( empty( $api_key ) ) {
        //error_log( 'API key is not set.' );
        return [ 'text' => 'API key is not set.' ];
    }

    $url      = 'https://api.openai.com/v1/chat/completions';
    $messages = [
        [
            'role'    => 'system',
            'content' => 'You are a helpful assistant that provides concise and personalized product recommendations. Someone has asked you for some recommendations please respond very concisely appropriate for an AI chatbot.',
        ],
        [
            'role'    => 'user',
            'content' => $prompt,
        ],
    ];

    $args = [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ],
        'body'    => wp_json_encode(
            [
                'model'       => 'gpt-4o',
                'messages'    => $messages,
                'temperature' => 0.7,
            ]
        ),
        'timeout' => 10,
        'method'  => 'POST',
    ];

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

    if ( is_wp_error( $response ) ) {
        //error_log( 'Error communicating with AI API: ' . $response->get_error_message() );
        return [ 'text' => 'Error communicating with AI API.' ];
    }

    $body = wp_remote_retrieve_body( $response );
    //error_log( 'API response body: ' . $body );

    $decoded_body = json_decode( $body, true );
    if ( isset( $decoded_body['choices'][0]['message']['content'] ) ) {
        return [ 'text' => $decoded_body['choices'][0]['message']['content'] ];
    } else {
        //error_log( 'Unexpected API response format: ' . wp_json_encode( $decoded_body ) );
        return [ 'text' => 'No response received from AI.' ];
    }
}

/**
 * Fetches the AI response for the given prompt.
 *
 * @param string $prompt The prompt to send to the AI.
 * @return string|null The AI's response text or null if not available.
 */
private function mxchat_fetch_ai_response( $prompt ) {
    $response = $this->mxchat_call_ai_api( $prompt );
    return isset( $response['text'] ) ? $response['text'] : null;
}

/**
 * Generates the AI prompt for product recommendations.
 *
 * @param array $recommendations An array of product recommendations.
 * @return string The generated AI prompt.
 */
private function mxchat_generate_ai_recommendation_prompt( $recommendations ) {
    //error_log( 'Generating AI recommendation prompt.' );

    $recommendation_list = '';
    foreach ( $recommendations as $index => $rec ) {
        $number                 = $index + 1;
        $name                   = $rec['name'];
        $price                  = $rec['price'];
        $url                    = $rec['url'];
        $image                  = $rec['image'];
        $recommendation_list   .= "{$number}. Product: {$name} (Price: \${$price})\n";
        $recommendation_list   .= "   [Link]({$url})\n";
        $recommendation_list   .= "   ![Image]({$image})\n\n";
    }

    $prompt  = "Based on the following list of products, generate a unique, friendly, and personalized response to a user who asked for a recommendation ";
    $prompt .= "Then, for each product, provide a brief justification of why it's relevant to the user. ";
    $prompt .= "Please number your responses to match the product numbers.\n\n";
    $prompt .= "Products:\n\n{$recommendation_list}";
    $prompt .= "Please ensure that the number of each product matches the order in which the products are listed.";

    //error_log( 'Generated AI prompt: ' . $prompt );

    return $prompt;
}

/**
 * Handles product recommendations by generating and formatting the AI response.
 *
 * @param string $message    The user's message.
 * @param int    $user_id    The user's ID.
 * @param int    $session_id The session ID.
 */
public function mxchat_handle_product_recommendations( $message, $user_id, $session_id ) {
    try {
        //error_log( "Starting product recommendations for user: $user_id, session: $session_id." );

        $recommendation_data = $this->mxchat_generate_recommendations( $user_id );
        //error_log( 'Generated recommendation data: ' . wp_json_encode( $recommendation_data ) );

        if ( empty( $recommendation_data['recommendations'] ) ) {
            //error_log( "No recommendations found for user: $user_id." );
            $this->fallbackResponse = [
                'text' => __( "I couldn't find any product recommendations for you right now. Please try again later!", 'mxchat' ),
            ];
            return;
        }

        // Remove duplicates and limit to top 4 recommendations
        $unique_recommendations = [];
        foreach ( $recommendation_data['recommendations'] as $rec ) {
            $unique_recommendations[ $rec['url'] ] = $rec;
        }
        //error_log( 'Unique recommendations: ' . wp_json_encode( $unique_recommendations ) );

        $unique_recommendations = array_slice( $unique_recommendations, 0, 4 );
        //error_log( 'Top 4 recommendations: ' . wp_json_encode( $unique_recommendations ) );

        $recommendations_summary = [];
        foreach ( $unique_recommendations as $rec ) {
            $recommendations_summary[] = [
                'name'  => $rec['name'],
                'price' => strip_tags( $rec['price'] ),
                'url'   => $rec['url'],
                'image' => $rec['image'],
            ];
        }

        // Generate AI prompt and fetch response
        $ai_prompt    = $this->mxchat_generate_ai_recommendation_prompt( $recommendations_summary );
        $ai_response  = $this->mxchat_fetch_ai_response( $ai_prompt );
        //error_log( 'AI response received: ' . wp_json_encode( $ai_response ) );

        if ( empty( $ai_response ) ) {
            $this->fallbackResponse = [
                'text' => __( 'An unexpected error occurred while generating recommendations. Please try again later.', 'mxchat' ),
            ];
            return;
        }

        // Split the AI's response into lines
        $ai_lines = preg_split( '/\r\n|\r|\n/', $ai_response );

        // Initialize variables
        $formatted_response = '';
        $justifications     = [];
        $current_number     = 0;
        $in_introduction    = true;
        $introduction       = '';

        // Parse the AI response to separate the introduction and the justifications
        foreach ( $ai_lines as $line ) {
            if ( preg_match( '/^\s*(\d+)\.\s*(.*)$/', $line, $matches ) ) {
                // This line is a numbered justification
                $current_number                 = intval( $matches[1] ) - 1;
                $justifications[ $current_number ] = $matches[2];
                $in_introduction                = false;
            } elseif ( $in_introduction ) {
                // This line is part of the introduction
                $introduction .= $line . ' ';
            } else {
                // This line is a continuation of the current justification
                if ( isset( $justifications[ $current_number ] ) ) {
                    $justifications[ $current_number ] .= ' ' . $line;
                }
            }
        }

        // Build the formatted response
        if ( ! empty( $introduction ) ) {
            $formatted_response .= esc_html( trim( $introduction ) ) . "<br><br>";
        }

        foreach ( $recommendations_summary as $index => $rec ) {
            $name  = esc_html( $rec['name'] );
            $price = esc_html( $rec['price'] );
            $url   = esc_url( $rec['url'] );
            $image = esc_url( $rec['image'] );

            $formatted_response .= ( $index + 1 ) . ". <strong>{$name}</strong> - <strong>\${$price}</strong><br>";
            $formatted_response .= "<img src=\"{$image}\" alt=\"{$name}\" style=\"max-width: 200px; height: auto; display: block; margin: 10px 0;\" /><br>";

            if ( isset( $justifications[ $index ] ) ) {
                $formatted_response .= "<em>" . esc_html( trim( $justifications[ $index ] ) ) . "</em><br><br>";
            }
        }

        $this->fallbackResponse = [
            'text' => $formatted_response,
        ];
        //error_log( 'Final formatted response set.' );
    } catch ( Exception $e ) {
        //error_log( 'Error in mxchat_handle_product_recommendations: ' . $e->getMessage() );
        $this->fallbackResponse = [
            'text' => __( 'An unexpected error occurred while generating recommendations. Please try again later.', 'mxchat' ),
        ];
    }
}






// Inside MxChat_Integrator class
private function mxchat_generate_recommendations($user_id) {
    $recommendations = [];
    $recommendation_sources = [];
    $added_product_ids = []; // To track unique products

    // 1. Recommendations based on order history
    if (is_user_logged_in() && $user_id) {
        $order_recommendations = $this->mxchat_get_recommendations_from_order_history($user_id);
        foreach ($order_recommendations as $product) {
            if (!in_array($product->get_id(), $added_product_ids)) {
                $recommendations[] = $product;
                $added_product_ids[] = $product->get_id();
                $recommendation_sources[] = "Order history";
            }
        }
    }

    // 2. Recommendations based on cart contents
    if (WC()->cart && WC()->cart->get_cart_contents_count() > 0) {
        $cart_recommendations = $this->mxchat_get_recommendations_from_cart();
        foreach ($cart_recommendations as $product) {
            if (!in_array($product->get_id(), $added_product_ids)) {
                $recommendations[] = $product;
                $added_product_ids[] = $product->get_id();
                $recommendation_sources[] = "Cart contents";
            }
        }
    }

    // 3. General recommendations (bestsellers or sale items)
    $general_recommendations = $this->mxchat_get_general_recommendations();
    foreach ($general_recommendations as $product) {
        if (!in_array($product->get_id(), $added_product_ids)) {
            $recommendations[] = $product;
            $added_product_ids[] = $product->get_id();
            $recommendation_sources[] = "General recommendations";
        }
    }

    // Format for output
    $formatted_recommendations = [];
    foreach ($recommendations as $product) {
        $formatted_recommendations[] = [
            'name'  => $product->get_name(),
            'price' => $product->get_price_html(),
            'url'   => get_permalink($product->get_id()),
            'image' => wp_get_attachment_url($product->get_image_id()),
        ];
    }

    return [
        'recommendations' => $formatted_recommendations,
        'sources' => array_unique($recommendation_sources),
    ];
}
private function mxchat_get_recommendations_from_order_history($user_id) {
    $args = [
        'customer_id' => $user_id,
        'limit'       => -1,
    ];
    $orders = wc_get_orders($args);

    $purchased_products = [];
    foreach ($orders as $order) {
        foreach ($order->get_items() as $item) {
            $purchased_products[] = $item->get_product_id();
        }
    }

    $related_product_ids = wc_get_related_products($purchased_products, 5); // Get up to 5 related products
    return wc_get_products(['include' => $related_product_ids]);
}
private function mxchat_get_recommendations_from_cart() {
    $cart = WC()->cart->get_cart();
    $cart_product_ids = array_map(function ($cart_item) {
        return $cart_item['product_id'];
    }, $cart);

    $related_product_ids = wc_get_related_products($cart_product_ids, 5); // Get up to 5 related products
    return wc_get_products(['include' => $related_product_ids]);
}
private function mxchat_get_general_recommendations() {
    $args = [
        'status'  => 'publish',
        'limit'   => 5,
        'orderby' => 'popularity',
        'meta_query' => [
            'relation' => 'OR',
            [
                'key'     => '_sale_price',
                'compare' => '>',
                'value'   => 0,
            ]
        ],
    ];

    return wc_get_products($args);
}





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('Fetch new messages error: Session ID missing.');
        wp_send_json_error(['message' => 'Session ID missing.']);
        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("New agent messages fetched for session $session_id. Last seen ID: $last_seen_id");

    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'] ?? 'offline';

    if ($live_agent_available !== 'online') {
        $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'
        ];

        //error_log('Live agent handover attempted but agents are offline');

        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'] ?? '';
    //error_log('Slack Webhook URL retrieved: ' . $slack_webhook_url);

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

    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' => "*User ID:*\n`$user_id`"
                    ],
                    [
                        'type' => 'mrkdwn',
                        'text' => "*Session ID:*\n`$session_id`"
                    ]
                ]
            ],
            [
                'type' => 'section',
                'text' => [
                    'type' => 'mrkdwn',
                    'text' => "*Initial Message:*\n$message"
                ]
            ],
            [
                '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)) {
        //error_log('Error sending live agent handover: ' . $response->get_error_message());
        return false;
    }

    //error_log('Live agent handover triggered successfully.');

    $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('Slack Webhook URL is not configured.');
        return false;
    }

    $webhook_data = [
        'blocks' => [
            [
                'type' => 'header',
                'text' => [
                    'type' => 'plain_text',
                    'text' => '📩 New Chat Message',
                    'emoji' => true
                ]
            ],
            [
                'type' => 'section',
                'fields' => [
                    [
                        'type' => 'mrkdwn',
                        'text' => "*User ID:*\n`$user_id`"
                    ],
                    [
                        'type' => 'mrkdwn',
                        'text' => "*Session ID:*\n`$session_id`"
                    ]
                ]
            ],
            [
                'type' => 'section',
                'text' => [
                    'type' => 'mrkdwn',
                    'text' => "*Message:*\n$message"
                ]
            ],
            [
                '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)) {
        //error_log('Error sending message to Slack: ' . $response->get_error_message());
        return false;
    }

    //error_log('Message sent to Slack successfully.');
    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' => 'Bot token not configured'], 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'
                    ],
                    'submit' => [
                        'type' => 'plain_text',
                        'text' => 'Send'
                    ],
                    'close' => [
                        'type' => 'plain_text',
                        'text' => 'Cancel'
                    ],
                    'blocks' => [
                        [
                            'type' => 'input',
                            'block_id' => 'reply_block',
                            'label' => [
                                'type' => 'plain_text',
                                'text' => "Reply to session: $session_id"
                            ],
                            'element' => [
                                'type' => 'plain_text_input',
                                'action_id' => 'message',
                                'multiline' => true,
                                'placeholder' => [
                                    'type' => 'plain_text',
                                    'text' => 'Type your message here...'
                                ]
                            ]
                        ]
                    ],
                    '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
    if ($payload['type'] === 'view_submission') {
        $session_id = $payload['view']['private_metadata'];
        $message = $payload['view']['state']['values']['reply_block']['message']['value'];

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

        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('Agent response error: No command text received');
        return new WP_REST_Response([
            'error' => 'Command text is required. Format: /reply session_id message'
        ], 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' => 'Invalid format. Use: /reply session_id message'
        ], 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' => 'Failed to save message'
        ], 500);
    }

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


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

    // 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'] = 'You are now chatting with the AI chatbot.';

    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) {
    global $wpdb;
    $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
    $cache_key = 'mxchat_system_prompt_embeddings';

    // Retrieve embeddings from cache or database
    $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
    if ($embeddings === false) {
        $query = "SELECT id, embedding_vector FROM {$system_prompt_table}";
        $embeddings = $wpdb->get_results($query);
        if ($embeddings === null || 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
            ];
        }
    }

    // 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); // Return the combined 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;
}

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

    // Check the selected model
    $selected_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-3.5-turbo';

    // Call the appropriate function based on the selected model
    if (strpos($selected_model, 'claude') !== false) {
        return $this->mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content);
    } elseif ($selected_model === 'grok-beta') {
        return $this->mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content);
    } else {
        return $this->mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content);
    }
}

private function mxchat_generate_response_openai($selected_model, $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
    ]);

    // Log the system prompt and relevant content
    error_log("System Prompt Instructions: " . $system_prompt_instructions);
    error_log("Relevant Content: " . substr($relevant_content, 0, 500)); // Log first 500 characters for brevity

    // 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'
        }
    }

    // Log the formatted conversation history
    error_log("Conversation History: " . json_encode($conversation_history, JSON_PRETTY_PRINT));

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

    // Log the full API request body
    error_log("OpenAI API Request Body: " . $body);

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

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

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

    // Log the raw API response
    error_log("OpenAI API Raw Response: " . print_r($response, true));

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

    // Log the decoded API response
    error_log("OpenAI API Decoded Response: " . json_encode($decoded_response, JSON_PRETTY_PRINT));

    if (isset($decoded_response['choices'][0]['message']['content'])) {
        $response_text = trim($decoded_response['choices'][0]['message']['content']);
        error_log("OpenAI API Final Response: " . $response_text);
        return $response_text;
    } else {
        // Log an error if the expected response format is missing
        error_log("OpenAI API Response Format Error: Expected 'choices[0][message][content]' not found.");
        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 for Claude's top-level system parameter
    $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_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'
        }
    }

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

    // Build the request body with Claude's expected structure, using system instructions as a top-level parameter
    $body = json_encode([
        'model' => $selected_model,
        'max_tokens' => 1000,
        'temperature' => 0.8,
        'system' => $system_prompt_instructions, // Set the system prompt at the top level as required
        'messages' => $conversation_history
    ]);

    // Set up the API request with the necessary headers
    $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 the API request
    $response = wp_remote_post('https://api.anthropic.com/v1/messages', $args);

/*
    // Check for errors and log the response for debugging
    if (is_wp_error($response)) {
        error_log("Claude API request error: " . print_r($response->get_error_message(), true));
        return "Sorry, there was an error processing your request.";
    }
*/


    // Decode the response and parse according to the expected Claude response structure
    $response_body = json_decode(wp_remote_retrieve_body($response), true);
    //error_log("Claude API response: " . print_r($response_body, true));

    // Check if the response has the expected 'content' array with 'text' blocks
    if (isset($response_body['content'][0]['text'])) {
        return trim($response_body['content'][0]['text']);
    } else {
        return "Sorry, I couldn't process that request.";
    }
}

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 = '1.5.5'; // Replace with your actual version
    $chat_script_version = '1.5.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');

    // 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',
    );

    // 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;
}

}
?>

```
