<?php
/**
 * Plugin Name: TWX-Chat
 * Description: Let your website speak for itself. Show 3D animated talking chatbots on your website. Chatbot builder, Site Crawler and Knowledge Base included, plus loads more!
 * Version: 1.12.0
 * Plan: free
 * Author: TWXLabs.au
 * Author URI: https://twxchat.twxlabs.au
 * Requires PHP: 8.1
 * License: GPLv2 or later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Source: wordpress
 * Text Domain: twx-chat
 * Domain Path: /languages
*/

// New Release? Dont forget to update Version above and also in readme.txt (Stable tag)
// Leave Plan as 'pro', this will be modified by build script as needed

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

$twxchat_max_chatlog_rows = 5000; // may allow Admins to change this, hence is a variable

// Initialise Client and DB Timezones 
twxchat_init_timezone_options();

// Include the Composer autoloader
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
    require_once __DIR__ . '/vendor/autoload.php';
}

require_once plugin_dir_path(__FILE__) . 'includes/ajax_handler.php';
require_once plugin_dir_path(__FILE__) . 'includes/ajax_handler_admin.php';
require_once plugin_dir_path(__FILE__) . 'includes/twx_spider.php';

require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php';
require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';

$GLOBALS['twxchat_chatbot_rendered'] = false; // can be referenced in this script using: global $twxchat_chatbot_rendered;

if ( ! defined( 'TWXCHAT_DEFAULT_MIN_SIMILARITY' ) ) {
	define( 'TWXCHAT_DEFAULT_MIN_SIMILARITY', 75 ); // for text-embedding-ada-002 model (see default below)
}

if ( ! defined( 'TWXCHAT_DEFAULT_OPENAI_EMBEDDING_MODEL' ) ) {
    // Use cheapest embedding model. However if website content has unsupported languages (ie: non latin-based) Admins will need to change this in Settings
	define( 'TWXCHAT_DEFAULT_OPENAI_EMBEDDING_MODEL', 'text-embedding-ada-002' );
}

if ( ! defined( 'TWXCHAT_DEFAULT_MAX_OUTPUT_TOKENS' ) ) {
	define( 'TWXCHAT_DEFAULT_MAX_OUTPUT_TOKENS', '800');
}

// tiktoken cache dir, already exists
//putenv('TIKTOKEN_CACHE_DIR=' . plugin_dir_path(__FILE__) . 'assets/tiktoken');

function twxchat_init_timezone_options(){

    // Gets Client and DB timezones and stores in separate options for ready access by other scripts such as spider, Chat Log html generator etc
    // These needs to be in separate options otherwise they seem to get overwritten by admin_init scripts.

    // set client timezone each time in case changed in WP settings
    $client_timezone = get_option('timezone_string');
    if(empty($client_timezone)){
        // could be Offset is selected in General Settings instead of timezone name
        $client_timezone = twxchat_get_etc_gmt_timezone(get_option('gmt_offset'));  // Will be something like Etc/GMT-10 which is actually equivalent to UTC+10  (POSIX standard)
    }
    if(!empty($client_timezone)) {
        update_option('twx_chat_client_timezone', $client_timezone);
    }

    // DB timezone - normally only need to set this once, unlikely DB/Server will change it's timezone - not true, twice a year (Daylight Savings) !
    update_option('twx_chat_db_timezone', twxchat_get_etc_gmt_timezone(twxchat_get_db_utc_offset()));
}

function twxchat_get_etc_gmt_timezone($offset) {
    // Get POSIX standard timezone, eg: Etc/GMT-10 (for UTC+10), or Etc/GMT+6 (for UTC-6) etc
    if (!is_numeric($offset)) {
        return 'Etc/GMT';
    }
    // Reverse the sign to match PHP's "Etc/GMT" format
    $etc_offset = (int) $offset * -1;
    // Construct and return the Etc/GMT timezone string
    return 'Etc/GMT' . ($etc_offset == 0 ? '' : ($etc_offset > 0 ? "+$etc_offset" : $etc_offset));
}

function twxchat_get_db_utc_offset() {
    // Get WP Database Timezone Offset
    global $wpdb;
    $db_timezone = $wpdb->get_var("SELECT @@global.time_zone");

    if ($db_timezone == 'SYSTEM') {
        // Get the offset as signed integer (correctly handles negative values)
        $var = $wpdb->get_var("SELECT TIME_FORMAT(TIMEDIFF(NOW(), UTC_TIMESTAMP), '%H')");
        return (int) $var;
    }else{
        return 0;
    }
}

register_activation_hook(__FILE__, 'twxchat_activation_hook');
function twxchat_activation_hook() {

    // tiggered on plugin activation/update
    global $wpdb;

    // Get the correct table prefix
    $table_vectors = $wpdb->prefix . 'twxchat_vectors';
    $table_chatlog = $wpdb->prefix . 'twxchat_chatlog';

    // SQL statement to create the twxchat_vectors table
    $sql_vectors = "CREATE TABLE IF NOT EXISTS $table_vectors (
        id INT(11) NOT NULL AUTO_INCREMENT,
        source LONGTEXT NOT NULL,
        orig_text LONGTEXT NOT NULL,
        vector LONGTEXT NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;";

    // SQL statement to create the twxchat_chatlog table
    $sql_chatlog = "CREATE TABLE IF NOT EXISTS $table_chatlog (
        id INT(11) NOT NULL AUTO_INCREMENT,
        thread_id VARCHAR(255) NOT NULL,
        properties LONGTEXT NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;";

    // Include the upgrade file for dbDelta function
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');

    // Run the SQL statements using dbDelta
    dbDelta($sql_vectors);
    dbDelta($sql_chatlog);

    // Create upload dirs
    twxchat_createUploadsDirSpiderResults();
	twxchat_createUploadsDirGoogleDrive();

    // Schedule daily check of ChatLog
    if (!wp_next_scheduled('twx_chat_cleanup_chatlog_event')) {
        $time = current_time('timestamp');
        wp_schedule_event($time, 'daily', 'twx_chat_cleanup_chatlog_event');
        TWXChatUtil::adminLog("Scheduled daily chatlog cleanup starting at " . current_time('mysql'));
    } else {
        //$scheduled = wp_next_scheduled('twx_chat_cleanup_chatlog_event');
        //$scheduled_date = date('Y-m-d H:i:s', $scheduled);
        //TWXChatUtil::adminLog("Scheduled daily chatlog cleanup already exists at " . $scheduled_date);
    }

    // Restore any previously saved twxchat jobs
    twxchat_restore_site_crawler_scheduled_jobs();
    twxchat_restore_reporting_scheduled_jobs();

    // Generate Server Token
    if (!get_option('twx_chat_server_token')) {
        add_option('twx_chat_server_token', wp_generate_password(32, true, true));
    }
    
    // Get Language Files (if needed)
    delete_option('twx_chat_pending_language_files');
    load_plugin_textdomain('twx-chat', false, dirname(plugin_basename(__FILE__)) . '/languages/');
    twxchat_downloadLanguageFilesMaybe();

}

register_deactivation_hook(__FILE__, 'twxchat_deactivation_hook');
function twxchat_deactivation_hook() {

    // tiggered on plugin deactivation
    TWXChatUtil::adminLog("twx_chat_deactivation_hook:: start");

    twxchat_clear_all_site_crawler_scheduled_jobs();
    twxchat_clear_all_reporting_jobs();

    // Allows Admin to reset Site Crawler Running Status
    update_option('twx_chat_site_crawler_running',false);
}

// If Plugin downloaded from twxlabs, check for plugin updates. Otherwise Wordpress.org will handle this automatically.
// This runs every time WordPress checks for plugin updates, typically twice daily, or when forced with wp_update_plugins()
add_filter('pre_set_site_transient_update_plugins', 'twxchat_check_for_plugin_update');
function twxchat_check_for_plugin_update($transient) {
    $pluginInfo = TWXChatUtil::getPluginInfo();
    $source = $pluginInfo['Source'] ?? '';
    if(!empty($source) && $source === 'twxlabs'){
        return TWXChatUtil::getPluginUpdateTransient($transient);
    }else{
        return $transient; // Wordpres.org hosted. Do nothing
    }
}

// TODO Paid Plugin information requests
/*
add_filter('plugins_api', 'twxchat_plugin_info', 10, 3);
function twxchat_plugin_info($res, $action, $args) {
    if ($action !== 'plugin_information') return $res;

    $pluginInfo = TWXChatUtil::getPluginInfo();
    if ($args->slug === $pluginInfo['slug'] && class_exists('TWXChatPlus')) {
        $new_res = TWXChatPlus::getPluginInfoRequestResponse($res);
        return $new_res;
    }
    return $res;
}
*/

// Check if we have just updated the plugin
add_action('admin_init', function() {

    $pluginInfo = TWXChatUtil::getPluginInfo();
    $ver = $pluginInfo['Version'] ?? '0.0.0';
    $plan = $pluginInfo['Plan'] ?? 'free';
    $prevVer = get_option('twx_chat_plugin_ver','0.0.0');
    $prevPlan = get_option('twx_chat_plugin_plan','free');

    if($ver === $prevVer && $plan === $prevPlan) return;

    if(TWXChatUtil::isVersionUpgrade($prevVer, $prevPlan, $ver, $plan)){
        $source = $pluginInfo['Source'] ?? 'unknown';
        TWXChatUtil::adminLog("plugin version updated:: prevVer: $prevVer, prevPlan: $prevPlan, newVer: $ver, newPlan: $plan, source: $source",'w'); // clears admin log first
        update_option('twx_chat_plugin_ver', $ver);
        update_option('twx_chat_plugin_plan', $plan);
        twxchat_on_plugin_ver_change();
    }

});

// Verify customer
function twxchat_verifyCustomer(){

    if(!class_exists('TWXChatPlus')) {
        // Free version
        update_option('twx_chat_customer_plan', 'free');
        return;
    }
    $options = get_option('twx_chat_options');
    $customerKey = $options['twx_customer_key'] ?? '';
    if(!empty($customerKey)) {
        TWXChatPlus::verifyCustomer($customerKey);
    }
}
add_action('admin_init', 'twxchat_verifyCustomer');

function twxchat_on_plugin_ver_change(){

    // Triggers on Plugin Version Change - useful for future updates

    //TWXChatUtil::adminLog('twxchat_on_plugin_ver_change:: start');

    // Other stuff for this (or previous) versions
    twxchat_createUploadsDirSpiderResults();
	twxchat_createUploadsDirGoogleDrive();

    // v1.3.4
    twxchat_updateTableColumnTypeVectorSource();

    // v1.4.0
    twxchat_addTitleColumnToVectorsTable();
    twxchat_createChatbotsTable();

    // v1.4.1
    twxchat_createUploadsDirAvatarAssets();

    // v1.9.2
    twxchat_updateChatbotsAvatarVoiceAtts();

    // v1.9.24 - Multi Lingual support
    twxchat_addEmbeddingModelToVectorsTable();

    // v1.10.4 - frontpage_shortcode deprecate for frontpage_chatbot_id
    twxchat_deprecate_frontpage_shortcode_option();

    // Always run this every version change - update Language files (if needed)
    load_plugin_textdomain('twx-chat', false, dirname(plugin_basename(__FILE__)) . '/languages/');
    twxchat_downloadLanguageFilesMaybe();
}

function twxchat_createUploadsDirSpiderResults(){
    // only if not exists

    $dest_dir = TWXChatUtil::getUploadsDir('spider_results');
    TWXChatUtil::makeDir($dest_dir);
}

function twxchat_createUploadsDirGoogleDrive() {
    // Get the directory path
    $dest_dir = TWXChatUtil::getUploadsDir('google_drive');
    TWXChatUtil::makeDir($dest_dir);
}

function twxchat_createUploadsDirAvatarAssets(){
    
    $dest_dir = TWXChatUtil::getAvatarAssetsRootPath();
    TWXChatUtil::makeDir($dest_dir);
}

function twxchat_updateTableColumnTypeVectorSource() {
    global $wpdb;

    $table_name = $wpdb->prefix . 'twxchat_vectors';
    $column_name = 'source';

    // Validate identifiers (basic safeguard)
    if (!preg_match('/^[a-zA-Z0-9_]+$/', $table_name) || !preg_match('/^[a-zA-Z0-9_]+$/', $column_name)) {
        TWXChatUtil::adminLog("twxchat_updateTableColumnTypeVectorSource:: Invalid table or column name: $table_name / $column_name", 'a', true);
        return;
    }

    // Check current column data type
    $column_type = $wpdb->get_var($wpdb->prepare(
        "SELECT DATA_TYPE
         FROM INFORMATION_SCHEMA.COLUMNS
         WHERE TABLE_NAME = %s
           AND COLUMN_NAME = %s
           AND TABLE_SCHEMA = DATABASE()",
        $table_name,
        $column_name
    ));

    // If not already LONGTEXT, modify column
    if (strtolower($column_type) !== 'longtext') {
        $table_escaped = esc_sql($table_name);
        $column_escaped = esc_sql($column_name);

        $sql = "ALTER TABLE `$table_escaped` MODIFY COLUMN `$column_escaped` LONGTEXT NOT NULL";

        $result = $wpdb->query($sql); // Plugin Checker complains not using prepare(), however we cannot use prepare() safely with ALTER TABLE sql

        if ($result === false) {
            TWXChatUtil::adminLog("twxchat_updateTableColumnTypeVectorSource:: SQL failed: $sql. Error: " . $wpdb->last_error, 'a', true);
        }
    }
}

function twxchat_addEmbeddingModelToVectorsTable() {
    global $wpdb;

    $table_name = $wpdb->prefix . 'twxchat_vectors';
    $column_name = 'embedding_model';
    $default_value = 'text-embedding-ada-002';

    // Validate identifiers (basic safeguard)
    if (!preg_match('/^[a-zA-Z0-9_]+$/', $table_name) || !preg_match('/^[a-zA-Z0-9_]+$/', $column_name)) {
        TWXChatUtil::adminLog("twxchat_addEmbeddingModelToVectorsTable:: Invalid table or column name: $table_name / $column_name");
        return;
    }

    // Check if the column already exists
    $column_exists = $wpdb->get_results(
        $wpdb->prepare("SHOW COLUMNS FROM `$table_name` LIKE %s", $column_name)
    );

    if (!empty($column_exists)) {
        TWXChatUtil::adminLog("twxchat_addEmbeddingModelToVectorsTable:: Column '$column_name' already exists in '$table_name'.");
        return;
    }

    // Add the column (nullable, no default)
    $sql_add = "ALTER TABLE `$table_name` ADD `$column_name` VARCHAR(255) NULL";
    $result = $wpdb->query($sql_add);

    if ($result === false) {
        $error = $wpdb->last_error ?: 'Unknown database error';
        TWXChatUtil::adminLog("twxchat_addEmbeddingModelToVectorsTable:: Failed to add column '$column_name': $error");
        return;
    }

    // Update all existing rows with the initial embedding model value
    $sql_update = $wpdb->prepare("UPDATE `$table_name` SET `$column_name` = %s", $default_value);
    $update_result = $wpdb->query($sql_update);

    if ($update_result === false) {
        $error = $wpdb->last_error ?: 'Unknown database error';
        TWXChatUtil::adminLog("twxchat_addEmbeddingModelToVectorsTable:: Failed to update existing rows: $error");
    } else {
        TWXChatUtil::adminLog("twxchat_addEmbeddingModelToVectorsTable:: Added column '$column_name' and updated $update_result rows to '$default_value'.");
    }
}

function twxchat_deprecate_frontpage_shortcode_option(){

    $options = get_option('twx_chat_options');
    $frontpage_chatbot_id = $options['frontpage_chatbot_id'] ?? '';
    $frontpage_shortcode = $options['frontpage_shortcode'] ?? '';
    if(!empty($frontpage_shortcode)){
        if(empty($frontpage_chatbot_id)){
            // get id from shortcode and put in new option
            if (preg_match('/\[twxchat\s+id="(\d+)"\]/', $frontpage_shortcode, $m)) {
                $id = $m[1];
                $atts = TWXChatUtil::getChatbotAttributesById(['id'=>$id],true);
                if(isset($atts['style']) && $atts['style'] === 'floating') {
                    $options['frontpage_chatbot_id'] = $id;
                    TWXChatUtil::adminLog("twxchat_deprecate_frontpage_shortcode_option:: updated frontpage_chatbot_id to $id");
                }
            }
        }
        unset($options['frontpage_shortcode']);
        update_option('twx_chat_options',$options);
    }

}

function twxchat_downloadLanguageFilesMaybe($locale = null){

    // If existing locale's language files don't exist, attempt dowload from twxlabs server. 
    // If they dont exist yet on the twxlabs server, a job will be scheduled on the twxlabs server to generate them automatically
    // If that is the case, schedule a cron job to run this function again

    $textdomain = 'twx-chat';
    if(empty($locale)) $locale = TWXChatUtil::getSiteLocale();

    //TWXChatUtil::adminLog("twxchat_downloadLanguageFilesMaybe:: start. locale: $locale");

    // Test file is the .mo file
    $mo_file_name = $textdomain . '-' . $locale . '.mo';
    $langs_root_path = plugin_dir_path(__FILE__) . 'languages/';
    //$langs_root_path = WP_LANG_DIR . '/plugins/';
    $mo_file_path = $langs_root_path.$mo_file_name;

    if(file_exists($mo_file_path)) {
        TWXChatUtil::adminLog("twxchat_downloadLanguageFilesMaybe:: $locale mo file already exists");
        delete_option('twx_chat_pending_language_files');
        return;
    }

    // URL to get language files in ZIP format
    $remote_url = TWXChatUtil::getTwxlabsUrl()."api/get_language_files.php?locale=".rawurlencode($locale);

    // Temporary file so we don’t overwrite a valid .mo
    $tmp_zip_file = wp_tempnam($remote_url);        

    // Allow long execution and ignore client disconnects
    ignore_user_abort(true);

    // Use WordPress HTTP API to stream and save ZIP
    $response = TWXChatUtil::getHttpResponse($remote_url, [
        'timeout'  => 60,
        'stream'   => true,
        'filename' => $tmp_zip_file,
    ]);
    //TWXChatUtil::adminLog("twxchat_downloadLanguageFilesMaybe:: remote response: ".var_export($response,true));

    if (is_wp_error($response)) {
        wp_delete_file($tmp_zip_file);
        $error = "Download error: " . $response->get_error_message();
        TWXChatUtil::adminLog("twxchat_downloadLanguageFilesMaybe:: $error");
        return;
    }

    $http_code = wp_remote_retrieve_response_code($response);

    // Reject anything not 200
    if ($http_code !== 200) {
        wp_delete_file($tmp_zip_file);
        $error = "Download failed with HTTP code $http_code. Skipping file.";
        TWXChatUtil::adminLog("twxchat_downloadLanguageFilesMaybe:: $error");
        return;
    }

    // Check if metadata received - ie: no ZIP file ready yet on twxlabs
    $ctype = wp_remote_retrieve_header($response, 'content-type');
    if (strpos($ctype, 'application/json') !== false) {

        // It's metadata, not a ZIP
        $json = json_decode(file_get_contents($tmp_zip_file), true);
        TWXChatUtil::adminLog("twxchat_downloadLanguageFilesMaybe:: twxlabs response:".var_export($json,true));

        wp_delete_file($tmp_zip_file);

        if(isset($json['status'])) {
            if($json['status'] === 'pending_generation' || $json['status'] === 'now_generating'){

                // Language files are being generated currently on the twxlabs server
                // set admin notice option to be displayed in Admin pages until language files installed
                $admin_text = get_option('twx_chat_pending_language_files',null);
                if(empty($admin_text)){
                    $apiKey = get_option('twx_chat_options')['openai_apikey'] ?? '';
                    $openai = new TWXChatOpenAIResponses($apiKey);
                    //$completionsObject = new TWXChatOpenAICompletions($apiKey);
                    $textToTranslate = "Language translations for your locale ($locale) are being prepared. They should be available soon.";
                    $query = "Translate the following text into locale: $locale. Return only the translated text.\n\n$textToTranslate";
                    $result = $openai->query($query);
                    $text = $result['answer'] ?? '';
                    if(!empty($text)) update_option('twx_chat_pending_language_files',$text);
                }
            }
        }
        return;
    }

    // Check valid ZIP file
    if (!twxchat_is_valid_langs_zip_file($tmp_zip_file)) {
        wp_delete_file($tmp_zip_file);
        TWXChatUtil::adminLog("twxchat_downloadLanguageFilesMaybe:: ZIP Download failed");
        return;
    }

    // Unzip into langs root
    $zip = new ZipArchive;
    if ($zip->open($tmp_zip_file) === TRUE) {
        $zip->extractTo($langs_root_path);
        $zip->close();
        wp_delete_file($tmp_zip_file);

        // reload textdomain to trigger pickup of new files
        load_plugin_textdomain($textdomain, false, $langs_root_path);
        TWXChatUtil::adminLog("twxchat_downloadLanguageFilesMaybe:: ZIP Download and unzip successful");
    } else {
	    TWXChatUtil::adminLog("twxchat_downloadLanguageFilesMaybe:: Unzip failed");
        wp_delete_file($tmp_zip_file);
    }

    delete_option('twx_chat_pending_language_files');
}

function twxchat_is_valid_langs_zip_file($file) {
    if (!is_file($file)) return false;
    return true;
    //TODO
}

// Detect Locale Change by Admin
add_action('update_option_WPLANG', 'twxchat_locale_changed', 10, 3);
function twxchat_locale_changed($old_value, $new_value, $option) {
    if ($old_value !== $new_value) {
        // Locale actually changed
        TWXChatUtil::adminLog("twxchat_locale_changed:: Locale changed from $old_value to $new_value");
        if(empty($new_value) || (strlen($new_value)>3 && substr($new_value, 0, 3) === 'en_')) return;
        twxchat_downloadLanguageFilesMaybe($new_value);
    }
}

// Show pending language files admin notice
add_action('admin_init', function() {
    
    $notice = get_option('twx_chat_pending_language_files',null);
    if(empty($notice)) return;

    echo '<div class="notice notice-warning is-dismissible">';
    echo '<p><strong>TWX-Chat:</strong> '.$notice.'</p>';
    echo '</div>';
    // check twxlabs again for language files
    twxchat_downloadLanguageFilesMaybe();
});

/*
function twxchat_show_hide_pending_language_files_notice() {

    // If Language files are being processed offline, display Admin message

    // TODO hide Admin notice if exists

    $textdomain = 'twx-chat';
    $locale = TWXChatUtil::getSiteLocale();
    if(empty($locale) || (strlen($locale)>3 && substr($locale, 0, 3) === 'en_')) return;

    // Test file is the .mo file
    $mo_file_name = $textdomain . '-' . $locale . '.mo';
    $langs_root_path = plugin_dir_path(__FILE__) . 'languages/';
    //$langs_root_path = WP_LANG_DIR . '/plugins/';
    $mo_file_path = $langs_root_path.$mo_file_name;

    if(file_exists($mo_file_path)) return;

    $admin_text = get_option('twx_chat_pending_language_files',null);
    if(empty($admin_text)) return;

    echo '<div class="notice notice-warning is-dismissible">';
    echo '<p><strong>TWX-Chat:</strong> '.$admin_text.'</p>';
    echo '</div>';

}
*/
//add_action('admin_notices', 'twxchat_show_hide_pending_language_files_notice');

function twxchat_updateChatbotsAvatarVoiceAtts(){

    // Update old avatar voice name att to new atts
    
    global $wpdb;
    $table_name = $wpdb->prefix . 'twxchat_chatbots';
    $rows = $wpdb->get_results("SELECT * FROM $table_name", ARRAY_A);

    foreach ($rows as $row) {
        $id   = (int) $row['id'];
		$atts = json_decode($row['settings'] ?? '{}', true);
        if(isset($atts['avatar_voice'])){
            if(!empty($atts['avatar_voice'])){
                $old_voice = $atts['avatar_voice']; // format {provider}:{name}:{speed} eg: aws:Olivia:100%  or {provider}:{name}  eg:  aws:Olivia
                $parts = explode(':', $old_voice);
                $voice_provider = $parts[0] ?? '';
                $voice_name     = $parts[1] ?? '';
                $voice_speed    = $parts[2] ?? '100%';

                $voice_speed_no_percent = rtrim($voice_speed, '%');
                if (!is_numeric($voice_speed_no_percent)) {
                    $voice_speed_no_percent = '100';
                }

                $atts['avatar_voice_name'] = "$voice_provider:$voice_name";
                $atts['avatar_voice_speed'] = $voice_speed_no_percent;

                //TODO save back to table
            }
            unset($atts['avatar_voice']);

             // Save updated atts back to DB
            try {
                //TWXChatUtil::adminLog("twxchat_updateChatbotsAvatarVoiceAtts:: update id: $id. atts: ".var_export($atts,true));
                $wpdb->update(
                    $table_name,
                    ['settings' => wp_json_encode($atts)],
                    ['id' => $id],
                    ['%s'],
                    ['%d']
                );
            }catch(Exception $ex){
                TWXChatUtil::adminLog("twxchat_updateChatbotsAvatarVoiceAtts:: update id: $id. error: ".$ex->getMessage(),'a',true);
            }
        }
    }

}

function twxchat_addTitleColumnToVectorsTable() {
    global $wpdb;

    $table_name  = $wpdb->prefix . 'twxchat_vectors';
    $column_name = 'title';

    // Validate identifiers
    if (!preg_match('/^[a-zA-Z0-9_]+$/', $table_name) || !preg_match('/^[a-zA-Z0-9_]+$/', $column_name)) {
        TWXChatUtil::adminLog("twxchat_addTitleColumnToVectorsTable:: Invalid table or column name: $table_name / $column_name", 'a', true);
        return;
    }

    // Check if column exists
    $column_exists = $wpdb->get_var($wpdb->prepare(
        "SHOW COLUMNS FROM `$table_name` LIKE %s",
        $column_name
    ));

    // Add the column if it doesn't exist
    if (empty($column_exists)) {
        $table_escaped  = esc_sql($table_name);
        $column_escaped = esc_sql($column_name);

        $sql = "ALTER TABLE `$table_escaped` ADD `$column_escaped` VARCHAR(255) NULL DEFAULT NULL";
        $result = $wpdb->query($sql);  // Plugin Checker complains not using prepare(), however we cannot use prepare() safely with ALTER TABLE sql

        if ($result === false) {
            TWXChatUtil::adminLog("twxchat_addTitleColumnToVectorsTable:: SQL failed: $sql. Error: " . $wpdb->last_error, 'a', true);
        }
    }
}

function twxchat_createChatbotsTable() {
    global $wpdb;

    $table_name = $wpdb->prefix . 'twxchat_chatbots';

    // Basic validation (safety check)
    if (!preg_match('/^[a-zA-Z0-9_]+$/', $table_name)) {
        TWXChatUtil::adminLog("twxchat_createChatbotsTable:: Invalid table name: $table_name", 'a', true);
        return;
    }

    $charset_collate = $wpdb->get_charset_collate();

    // Use backticks and match dbDelta quirks
    $sql = "CREATE TABLE {$table_name} (
        id INT NOT NULL AUTO_INCREMENT,
        name VARCHAR(255) NOT NULL,
        settings LONGTEXT NOT NULL,
        PRIMARY KEY  (id)
    ) $charset_collate;";

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($sql);

    // Optionally log dbDelta errors or results
    if (!empty($wpdb->last_error)) {
        TWXChatUtil::adminLog("twxchat_createChatbotsTable:: DB error: " . $wpdb->last_error, 'a', true);
    }
}

// Add a settings link to the plugin actions on the Plugins page
function twxchat_action_links($links) {
    $settings_link = '<a href="options-general.php?page=twx-chat-settings">Settings</a>';
    array_unshift($links, $settings_link);
    return $links;
}
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'twxchat_action_links');

function twxchat_admin_menu() {

    add_options_page(
        'TWX-Chat Settings',
        'TWX-Chat',
        'manage_options',
        'twx-chat-settings',
        'twxchat_settings_page'
    );
}
add_action('admin_menu', 'twxchat_admin_menu');

function twxchat_settings_page() {

    ?>
    <div class="wrap">
        <h1>TWX-Chat <?php echo __('Settings','twx-chat'); ?></h1>
        <h2 class="nav-tab-wrapper">
            <a href="#general" data-tab="general" class="nav-tab"><?php echo __('General','twx-chat'); ?></a>
            <a href="#chatbots" data-tab="chatbots" class="nav-tab"><?php echo __('Chatbots','twx-chat'); ?></a>
            <a href="#customize" data-tab="customize" class="nav-tab"><?php echo __('Customise','twx-chat'); ?></a>
            <a href="#site-crawler" data-tab="site-crawler" class="nav-tab"><?php echo __('Site Crawler','twx-chat'); ?></a>
            <a href="#knowledgebase" data-tab="knowledgebase" class="nav-tab"><?php echo __('Knowlegde Base','twx-chat'); ?></a>
            <a href="#chatlog" data-tab="chatlog" class="nav-tab"><?php echo __('Chat Log','twx-chat'); ?></a>
            <a href="#reports" data-tab="reports" class="nav-tab"><?php echo __('Reports','twx-chat'); ?></a>
            <a href="#plugininfo" data-tab="plugininfo" class="nav-tab"><?php echo __('Plugin Info','twx-chat'); ?></a>
        </h2>
        
        <div id="general" class="twx-chat-tab-content">
            <form method="post" action="options.php">
                <?php
                settings_fields('twx_chat_all_options_group');
                do_settings_sections('twx-chat-general');
                submit_button();
                ?>
            </form>
        </div>

        <div id="chatbots" class="twx-chat-tab-content">
            <h2><?php echo __('Chatbots','twx-chat'); ?></h2>
            <p><?php echo __('Create and Manage your Chatbots below','twx-chat'); ?>: <span class="twx-chat-feature-button" data-feature="chatbot_settings"><?php echo __('More help here','twx-chat'); ?></span></p>
            <div id="twx-chat-chatbots-messages" class="twx-chat-admin-message"></div>
            <button id="twx-chat-chatbots-new-btn" class="button-primary"><?php echo __('Create New Chatbot','twx-chat'); ?></button>
            <span id="twx-chat-chatbots-new-limit-reached" style="display:none;" class="twx-plus-button"><?php echo __('Chatbot Limit Reached. Please upgrade to get more.','twx-chat'); ?></span>
            <table id="twx-chat-chatbots-table" class="widefat striped"></table>
            <div id="twx-chat-chatbot-edit"></div>
        </div>

        <div id="customize" class="twx-chat-tab-content">
            <form method="post" action="options.php">
                <?php
                settings_fields('twx_chat_all_options_group');
                do_settings_sections('twx-chat-customize');
                submit_button();
                ?>
            </form>
        </div>
        
        <div id="site-crawler" class="twx-chat-tab-content">
            <form method="post" action="options.php">
                <?php
                settings_fields('twx_chat_all_options_group');
                do_settings_sections('twx-chat-site-crawler');
                submit_button();
                ?>
            </form>
        </div>

        <div id="knowledgebase" class="twx-chat-tab-content">
            <?php
            settings_fields('twx_chat_all_options_group');
            do_settings_sections('twx-chat-knowledge-base');
            ?>
        </div>

        <div id="chatlog" class="twx-chat-tab-content">
            <?php
            settings_fields('twx_chat_all_options_group');
            do_settings_sections('twx-chat-chatlog');
            ?>
        </div>

        <div id="reports" class="twx-chat-tab-content">
            <form method="post" action="options.php">
                <?php
                settings_fields('twx_chat_all_options_group');
                do_settings_sections('twx-chat-reports');
                submit_button();
                ?>
            </form>
        </div>

        <div id="plugininfo" class="twx-chat-tab-content">
            <form method="post" action="options.php">
                <?php
                settings_fields('twx_chat_all_options_group');
                do_settings_sections('twx-chat-plugininfo');
                submit_button();
                ?>
            </form>
        </div>
    </div>
	
    <?php
}


function twxchat_settings_init() {

    // Register all options group and validator
    register_setting('twx_chat_all_options_group','twx_chat_options','twxchat_all_options_validate');

    // General
    add_settings_section('twx_chat_general_section', __('General Settings','twx-chat'), 'twxchat_general_section_text', 'twx-chat-general');
    add_settings_field('twx_chat_openai_apikey', '*OpenAI API Key', 'twxchat_openai_apikey_setting', 'twx-chat-general', 'twx_chat_general_section');
    //add_settings_field('twx_chat_min_similarity', 'Min. Similarity % (non OpenAI Assistant)', 'twxchat_min_similarity_setting', 'twx-chat-general', 'twx_chat_general_section');
    add_settings_field('twx_chat_google_apikey', 'Google API Key', 'twxchat_google_apikey_setting', 'twx-chat-general', 'twx_chat_general_section');
    add_settings_field('twx_chat_aws_cognito_identity_pool_id', 'AWS Cognito Pool Identity Key', 'twxchat_aws_cognito_identity_pool_id_setting', 'twx-chat-general', 'twx_chat_general_section');
    add_settings_field('twx_chat_openai_embedding_model', 'OpenAI '.__('Text Embedding Model','twx-chat'), 'twxchat_openai_embedding_model_setting', 'twx-chat-general', 'twx_chat_general_section');

    // Customise
    add_settings_section('twx_chat_customize_section', __('Customise Settings','twx-chat'), 'twxchat_customize_section_text', 'twx-chat-customize');
    add_settings_field('twx_chat_starting_comments', __('Default Starting Comments','twx-chat'), 'twxchat_starting_comments_setting', 'twx-chat-customize', 'twx_chat_customize_section');
    add_settings_field('twx_chat_global_chatbot_id', __('Global Chatbot','twx-chat'), 'twxchat_global_chatbot_id_setting', 'twx-chat-customize', 'twx_chat_customize_section');
    add_settings_field('twx_chat_frontpage_chatbot_id', __('Front Page Chatbot','twx-chat'), 'twxchat_frontpage_chatbot_id_setting', 'twx-chat-customize', 'twx_chat_customize_section');
    //add_settings_field('twx_chat_frontpage_shortcode', __('Front Page Shortcode','twx-chat'), 'twxchat_frontpage_shortcode_setting', 'twx-chat-customize', 'twx_chat_customize_section');
    add_settings_field('twx_chat_custom_css', __('Custom CSS','twx-chat'), 'twxchat_custom_css_setting', 'twx-chat-customize', 'twx_chat_customize_section');

    // Site Crawler
    add_settings_section('twx_chat_site_crawler_section', __('Site Crawler','twx-chat'), 'twxchat_site_crawler_section_text', 'twx-chat-site-crawler');
    add_settings_field('twx_chat_site_crawler_public_posts', __('Crawl Public Posts/Pages','twx-chat'), 'twxchat_site_crawler_public_posts_setting', 'twx-chat-site-crawler', 'twx_chat_site_crawler_section');
    add_settings_field('twx_chat_site_crawler_private_posts', __('Crawl Private Posts/Pages','twx-chat'), 'twxchat_site_crawler_private_posts_setting', 'twx-chat-site-crawler', 'twx_chat_site_crawler_section');
    add_settings_field('twx_chat_site_crawler_onsite_media', __('Crawl onsite Media items','twx-chat'), 'twxchat_site_crawler_onsite_media_setting', 'twx-chat-site-crawler', 'twx_chat_site_crawler_section');
    add_settings_field('twx_chat_site_crawler_exclude_sources', __('Exclude Sources','twx-chat'), 'twxchat_site_crawler_exclude_sources_setting', 'twx-chat-site-crawler', 'twx_chat_site_crawler_section');
    add_settings_field('twx_chat_site_crawler_sitemap', __('Crawl Sitemap','twx-chat'), 'twxchat_site_crawler_sitemap_setting', 'twx-chat-site-crawler', 'twx_chat_site_crawler_section');
    add_settings_field('twx_chat_site_crawler_google_drive', __('Crawl Google Drive PDFs','twx-chat'), 'twxchat_site_crawler_google_drive_setting', 'twx-chat-site-crawler', 'twx_chat_site_crawler_section');
    add_settings_field('twx_chat_site_crawler_update_assistant', __('Update OpenAI Assistant','twx-chat'), 'twxchat_site_crawler_update_assistant_setting', 'twx-chat-site-crawler', 'twx_chat_site_crawler_section');
    add_settings_field('twx_chat_site_crawler_create_llms_txt', __('Create llms.txt file','twx-chat'), 'twxchat_site_crawler_create_llms_txt_setting', 'twx-chat-site-crawler', 'twx_chat_site_crawler_section');
    add_settings_field('twx_chat_site_crawler_woocommerce_products', __('Crawl WooCommerce Products','twx-chat'), 'twxchat_site_crawler_woocommerce_products_setting', 'twx-chat-site-crawler', 'twx_chat_site_crawler_section');
    add_settings_field('twx_chat_site_crawler_schedule', __('Schedule Site Crawler','twx-chat'), 'twxchat_site_crawler_schedule_setting', 'twx-chat-site-crawler', 'twx_chat_site_crawler_section');
    add_settings_field('twx_chat_site_crawler_schedule_dayofweek', __('Schedule Site Crawler Day','twx-chat'), 'twxchat_site_crawler_schedule_dayofweek_setting', 'twx-chat-site-crawler', 'twx_chat_site_crawler_section');
    add_settings_field('twx_chat_site_crawler_schedule_time', __('Schedule Site Crawler Time','twx-chat'), 'twxchat_site_crawler_schedule_time_setting', 'twx-chat-site-crawler', 'twx_chat_site_crawler_section');
    add_settings_field('twx_chat_site_crawler_status', __('Site Crawler Last Run','twx-chat'), 'twxchat_site_crawler_status_setting', 'twx-chat-site-crawler', 'twx_chat_site_crawler_section');

    // Knowledge Base
    add_settings_section('twx_chat_knowledge_base_section', __('Knowledge Base','twx-chat'), 'twxchat_knowledge_base_section_text', 'twx-chat-knowledge-base');

    // Chatlog
    add_settings_section('twx_chat_chatlog_section', __('Chat Log','twx-chat'), 'twxchat_chatlog_section_text', 'twx-chat-chatlog');

    // Reports
    add_settings_section('twx_chat_reports_section', __('Reports','twx-chat'), 'twxchat_reports_section_text', 'twx-chat-reports');
    add_settings_field('twx_chat_reports_chatlog_summary_email_schedule', __('Chat Log Summary Email Schedule','twx-chat'), 'twxchat_reports_chatlog_summary_email_schedule_setting', 'twx-chat-reports', 'twx_chat_reports_section');

    // Plugin Info
    add_settings_section('twx_chat_plugininfo_section', __('Plugin Info','twx-chat'), 'twxchat_plugininfo_section_text', 'twx-chat-plugininfo');
    add_settings_field('twx_chat_plugin_ver', __('Plugin Version','twx-chat'), 'twxchat_plugin_ver_setting', 'twx-chat-plugininfo', 'twx_chat_plugininfo_section');
    add_settings_field('twx_chat_show_watermark', __('Display Powered-by Watermark','twx-chat'), 'twxchat_show_watermark_setting', 'twx-chat-plugininfo', 'twx_chat_plugininfo_section');
    add_settings_field('twx_chat_copy_support_info', __('Copy Support Info','twx-chat'), 'twxchat_copy_support_info_setting', 'twx-chat-plugininfo', 'twx_chat_plugininfo_section');
    if(class_exists('TWXChatPlus')){
        // has paid plan codebase
        add_settings_field('twx_chat_twx_customer_key', __('TWX-Chat Customer Key','twx-chat'), 'twxchat_twx_customer_key_setting', 'twx-chat-plugininfo', 'twx_chat_plugininfo_section');
        add_settings_field('twx_chat_auto_updates', __('Auto Updates','twx-chat'), 'twxchat_plugin_auto_updates_setting', 'twx-chat-plugininfo', 'twx_chat_plugininfo_section');
        if(TWXChatUtil::customerPlanAccess('plus')) add_settings_field('twx_chat_available_updates', __('Available Updates','twx-chat'), 'twxchat_available_updates_setting', 'twx-chat-plugininfo', 'twx_chat_plugininfo_section');
    } else {
        add_settings_field('twx_chat_auto_updates', __('Auto Updates','twx-chat'), 'twxchat_plugin_auto_updates_setting', 'twx-chat-plugininfo', 'twx_chat_plugininfo_section');
        add_settings_field('twx_chat_upgrade_promo', __('Upgrade Now!','twx-chat'), 'twxchat_upgrade_promo_setting', 'twx-chat-plugininfo', 'twx_chat_plugininfo_section');
    }
}
add_action('admin_init', 'twxchat_settings_init');


function twxchat_general_section_text() {
    echo '<p>'.__('Enter your general settings below','twx-chat').': <span class="twx-chat-feature-button" data-feature="first_time_setup">'.__('First-Time setup instructions here','twx-chat').'</span></p>';
}

function twxchat_site_crawler_section_text() {
    echo '<p>'.__('Enter your Site Crawler settings below','twx-chat').': <span class="twx-chat-feature-button" data-feature="site_crawler_settings">'.__('More help here','twx-chat').'</span></p>';
}

function twxchat_knowledge_base_section_text() {
    ?>
    <p><?php echo __('Query and Manage your Knowledge Base','twx-chat'); ?>: <span class="twx-chat-feature-button" data-feature="knowledge_base_settings"><?php echo __('More help here','twx-chat'); ?></span></p>
    <div id="twx-chat-kb-top-buttons">
        <button id="twx-chat-knowledgebase-search-btn"><?php echo __('Load Knowledge Base','twx-chat'); ?></button>
        &nbsp;<button id="twx-chat-knowledgebase-clearfilters-btn" style="display:none;"><?php echo __('Clear Filters','twx-chat'); ?></button>
        &nbsp;<button id="twx-chat-knowledgebase-newentry-btn" style="display:none;"><?php echo __('New Entry','twx-chat'); ?></button>
        &nbsp;<button id="twx-chat-knowledgebase-editentries-btn" style="display:none;"><?php echo __('Edit Entries','twx-chat'); ?></button>
        &nbsp;<button id="twx-chat-knowledgebase-deleteentries-btn" style="display:none;"><?php echo __('Delete Entries','twx-chat'); ?></button>
        &nbsp;<button id="twx-chat-knowledgebase-saveentries-btn" style="display:none;"><?php echo __('Save','twx-chat'); ?></button>
        &nbsp;<button id="twx-chat-knowledgebase-cancelaction-btn" style="display:none;"><?php echo __('Cancel','twx-chat'); ?></button>
        <form id="twx-chat-kb-upload-file-form" method="post" enctype="multipart/form-data">
            <input type="file" name="twx_chat_kb_upload_file" required>
            <button type="submit" class="button button-primary"><?php echo __('Upload File','twx-chat'); ?></button>
        </form>
    </div>
    <div id="twx-chat-kb-messages" class="twx-chat-admin-message"></div>
    <div id="twx-chat-knowledgebase-grid"></div>
    <?php
}

function twxchat_chatlog_section_text() {

    if(TWXChatUtil::customerPlanAccess('plus') && class_exists('TWXChatPlus')) {
        global $twxchat_max_chatlog_rows;
        $num_chat_log_entries = TWXChatLog::getNumEntries();
        $num_chat_log_entries_to_keep = $num_chat_log_entries;
        if($num_chat_log_entries > ($twxchat_max_chatlog_rows * 0.5)){
            $num_chat_log_entries_to_keep = intval($twxchat_max_chatlog_rows * 0.5);
        }

        ?>
        <p><?php echo __('Query and Manage your Chatbot interactions','twx-chat'); ?>: <span class="twx-chat-feature-button" data-feature="chatlog"><?php echo __('More help here','twx-chat'); ?></span></p>
        <button id="twx-chat-download-chatlog-btn" style="display:none"><?php echo __('Download the Chat Log (CSV format)','twx-chat'); ?></button>&nbsp;
        <button id="twx-chat-delete-chatlog-entries-btn" data-max-entries="<?php echo esc_attr($twxchat_max_chatlog_rows);?>" data-entries-to-keep="<?php echo esc_attr($num_chat_log_entries_to_keep);?>" data-current-entries="<?php echo esc_attr($num_chat_log_entries);?>"><?php echo __('Delete old Chat Log entries','twx-chat'); ?></button>&nbsp;
        <button id="twx-chat-clear-chatlog-filters-btn" style="display:none"><?php echo __('Clear Filters','twx-chat'); ?></button>
        <br/><div id="twx-chat-chatlog-periods"></div>
        <div id="twx-chat-chatlog-messages" class="twx-chat-admin-message"></div>
        <br/><div id="twx-chat-chatlog-grid"></div>
        <?php
    }else{
        ?>
        <br/><p class="description"><?php echo __('The','twx-chat'); ?>&nbsp;<span class="twx-chat-feature-button" data-feature="chatlog"><?php echo __('Chatlog feature','twx-chat'); ?></span> <?php echo __('is a powerful tool that lets you view all previous User-Chatbot conversations (even the links they clicked) to gain deeper insights into their requirements. Available in ','twx-chat'); ?><span class="twx-plus-button">Plus Plan</span> and <span class="twx-pro-button">Pro Plan</span>.</p>
        <?php
    }
}

function twxchat_customize_section_text() {
    echo '<p>'.__('Enter your Customisation settings below','twx-chat').': <span class="twx-chat-feature-button" data-feature="customise_settings">'.__('More help here','twx-chat').'</span></p>';
}

function twxchat_reports_section_text(){
    echo '<p>'.__('Report settings: ','twx-chat').'<span class="twx-chat-feature-button" data-feature="reports_settings">'.__('More help here','twx-chat').'</span></p>';
}

function twxchat_plugininfo_section_text() {
    echo '<p>'.__('Plugin information and settings: ','twx-chat').'<span class="twx-chat-feature-button" data-feature="plugininfo_settings">'.__('More help here','twx-chat').'</span></p>';
}

function twxchat_plugininfo_show_update_now($new_version, $plan = 'free'){
    $plugin_slug = plugin_basename(__FILE__);
    $plan = ucwords($plan);
    echo '<div class="notice notice-warning">';
    echo "<p><strong>Update Available:</strong> TWX-Chat version ".esc_html($new_version)." (".esc_html($plan)." Plan)</p>";
    echo '<p><a href="' . esc_url(wp_nonce_url(self_admin_url("update.php?action=upgrade-plugin&plugin=$plugin_slug"), 'upgrade-plugin_' . $plugin_slug)) . '" class="button button-primary">Update Now</a></p>';
    echo '</div>';
}

// Field callbacks
function twxchat_csv_upload_setting() {
    echo '<input type="file" name="twx_chat_csv_file" />';
}

function twxchat_section_text() {
    echo '<p>'.__('Enter your settings below:','twx-chat').'</p>';
}


function twxchat_reports_chatlog_summary_email_schedule_setting(){

    $options = get_option('twx_chat_options');
    $schedule = $options['reports_chatlog_summary_email_schedule'] ?? '';
    $admin_email = get_option('admin_email','');
    $smtp_configured = TWXChatUtil::isSMTPConfigured();

    if(empty($admin_email)){
        echo '<div class="description" style="color:red;">'.__('This setting requires an admin email to be set in your Wordpress settings. Please set this and return here.','twx-chat').'</div>';
        return;
    }

    if(!$smtp_configured){
        echo '<div class="description" style="color:red;">'.__('Email not configured in Wordpress. To enable email sending from this site, please install and configure an SMTP plugin such as WP Mail SMTP or FluentSMTP.','twx-chat').'</div>';
        return;
    }

    ?>
    <select id="twx_chat_reports_chatlog_summary_email_schedule" name="twx_chat_options[reports_chatlog_summary_email_schedule]">
        <option value="" <?php selected($schedule, ''); ?>><?php echo __('Select...','twx-chat'); ?></option>
        <option value="Daily" <?php selected($schedule, 'Daily'); ?>><?php echo __('Daily','twx-chat'); ?></option>
        <option value="Weekly" <?php selected($schedule, 'Weekly'); ?>><?php echo __('Weekly','twx-chat'); ?></option>
    </select>
    <p class="description"><?php echo __('Select how often the website Admin email','twx-chat'); echo ' ('.esc_html($admin_email); ?>) <?php echo __('will receive summary emails of the Chat Log.','twx-chat'); ?></p>
    <?php
}

function twxchat_plugin_ver_setting() {
    $pluginInfo = TWXChatUtil::getPluginInfo();
    echo esc_html($pluginInfo['Version']) . '&nbsp;&nbsp;<span class="twx-' . esc_attr($pluginInfo['Plan']) . '-button">' . esc_html(ucwords($pluginInfo['Plan'])) . '</span>';
}

function twxchat_plugin_auto_updates_setting(){

    $status = TWXChatUtil::getPluginAutoUpdateStatus();
    $updateStatusHtml = '';
    if($status['will_auto_update']){
        $updateStatusHtml = __('Auto-updates for this plugin are enabled. Next scheduled check/update at','twx-chat').': '.($status['next_scheduled'] ?? __('Not scheduled','twx-chat'));
    }else{
        $updateStatusHtml = __('Auto-updates are NOT enabled for this plugin. You can enable Auto-updates under Plugins > Installed Plugins > TWX-Chat > Enable auto-updates','twx-chat');
    }
    echo esc_html($updateStatusHtml);
}

function twxchat_show_watermark_setting(){
    $options = get_option('twx_chat_options');
    $value = isset($options['show_watermark']) ? $options['show_watermark'] : 1;
    echo '<input type="checkbox" id="twx_chat_show_watermark_chk" name="twx_chat_options[show_watermark]" value="1" ' . checked(1, $value, false) . '>'
        .'<p class="description">'.__('Check this ON if you like our plugin and you want to allow your visitors to find us. It will show an unobtrusive link below the Chatbot. Checking this OFF will remove the watermark, and that\'s totally fine also :)','twx-chat').'</p>';
}

function twxchat_twx_customer_key_setting() {
    $options = get_option('twx_chat_options');
    $type = 'text';
    $description = '';
    $style = '';
    $url = TWXChatUtil::getTwxlabsUrl();
    if(isset($options['twx_customer_key']) && !empty($options['twx_customer_key'])){
        $type = 'password';
        if(get_option('twx_chat_customer_valid',false)){
            $plan = get_option('twx_chat_customer_plan','');
            $plan = $plan ? ' ('.ucwords($plan).' Plan)' : '';
            $description = __('Valid Customer Key','twx-chat').$plan;
            $style = 'color:green;';
        }else{
            $validity_message = get_option('twx_chat_customer_valid_message','').'.';
            $description = __('Invalid Customer Key','twx-chat').' '.esc_html($validity_message).' '.__('Obtain or Renew from','twx-chat').' <a href="'.esc_url($url).'" target="_blank">'.esc_url($url).'</a>';
            $style = 'color:red;';
        }
    }else{
        $description = __('You are currently on the Free Plan. Upgrade to TWX-Chat Plus or Pro at','twx-chat').' <a href="'.TWXChatUtil::getTwxlabsUrl(false).'" target="_blank">'.esc_url(TWXChatUtil::getTwxlabsUrl(false)).'</a>';
    }

    echo "<input id='twx_chat_twx_customer_key' name='twx_chat_options[twx_customer_key]' size='40' type='".esc_attr($type)."' value='" . esc_attr($options['twx_customer_key'] ?? '') . "' />&nbsp;&nbsp;".esc_html(TWXChatUtil::obscureApiKey($options['twx_customer_key'] ?? ''),'twx-').'<p class="description" style="'.esc_attr($style).'">'.wp_kses_post($description).'</p>';
}

function twxchat_copy_support_info_setting(){
    $description = __('In the event of plugin errors or failures, TWXLabs support may require you to click this button which copies support information to the clipboard. You can then paste this into the form at','twx-chat').' <a href="'.esc_url(TWXChatUtil::getTwxlabsUrl().'contact-us').'">TWXLabs '.__('support','twx-chat').'</a>.';
    echo wp_kses_post('<button id="twx_chat_copy_support_info_btn" class="button button-primary">'.__('Copy Support Info to Clipboard','twx-chat').'</button><p class="description">'.$description.'</p>');
}

function twxchat_available_updates_setting(){
    echo '<button id="twx_chat_check_for_updates_btn" class="button button-primary">'.__('Check for Updates','twx-chat').'</button>&nbsp;&nbsp;<button id="twx_chat_update_plugin_now_btn" class="button button-primary" style="display:none;">'.__('Update Now!','twx-chat').'</button>&nbsp;&nbsp;<button id="twx_chat_update_plugin_zip_btn" class="button button-primary" style="display:none;">'.__('Download ZIP','twx-chat').'</button>';
}

function twxchat_upgrade_promo_setting(){
    $html = '
    <div class="twx-upgrade-notice">
        <h3>🚀 '.__('You’re on the ','twx-chat').'<strong>Free Plan</strong></h3>
        <p>'.__('Unlock advanced features with ','twx-chat').'<span class="twx-plus-button">Plus</span> or 
        <span class="twx-pro-button">Pro</span>:</p>
        <ul class="twx-promo-style-1">
            <li>'.__('Customer Chat Logs','twx-chat').'</li>
            <li>'.__('More Chatbots and Avatars','twx-chat').'</li>
            <li>'.__('Collect user fields','twx-chat').'</li>
            <li>'.__('WooCommerce integration','twx-chat').'</li>
            <li>'.__('Google Drive integration','twx-chat').'</li>
            <li>'.__('Priority Support','twx-chat').'</li>
            <li>... '.__('And Much More','twx-chat').'</li>
        </ul>
        <p><a href="'.esc_url(TWXChatUtil::getTwxlabsUrl().'features').'" target="_blank" class="twx-promo-style-2">🔓 '.__('Features & Pricing','twx-chat').'</a></p>
    </div>';
    echo wp_kses_post($html);
}

function twxchat_openai_apikey_setting() {
    $options = get_option('twx_chat_options');
    $description = '<p class="description">'.__('Obtain an OpenAI API Key from','twx-chat').' <span class="twx-chat-feature-button" data-feature="openai_api_key">'.__('here','twx-chat').'</span>';
    echo "<input id='twx_chat_openai_apikey' name='twx_chat_options[openai_apikey]' size='40' type='password' value='" . esc_attr($options['openai_apikey'] ?? '') . "' />&nbsp;&nbsp;" . esc_html(TWXChatUtil::obscureApiKey($options['openai_apikey'] ?? '')) . wp_kses_post($description);
}

function twxchat_google_apikey_setting() {
    $options = get_option('twx_chat_options');
	$type = isset($options['google_apikey']) && !empty($options['google_apikey']) ? 'password' : 'text' ;
    $note = TWXChatUtil::customerPlanAccess('plus') ? '' : ' '.__('Note this feature is available in the','twx-chat').'<span class="twx-pro-button">Pro Plan</span>.';
    echo "<input id='twx_chat_google_apikey' name='twx_chat_options[google_apikey]' size='40' type='" . esc_attr($type) . "' value='" . esc_attr($options['google_apikey'] ?? '') . "' />&nbsp;&nbsp;" . esc_html(TWXChatUtil::obscureApiKey($options['google_apikey'] ?? '')) . '<p class="description">'.__('Enter Google API Key if you want Site Crawler to crawl Google Drive docs.','twx-chat').wp_kses_post($note).'</p>';
}

function twxchat_aws_cognito_identity_pool_id_setting() {
    $options = get_option('twx_chat_options');
	$type = isset($options['aws_cognito_identity_pool_id']) && !empty($options['aws_cognito_identity_pool_id']) ? 'password' : 'text' ;
    echo '<input id="twx_chat_aws_cognito_identity_pool_id" name="twx_chat_options[aws_cognito_identity_pool_id]" size="40" type="' . esc_attr($type) . '" value="' . esc_attr($options['aws_cognito_identity_pool_id'] ?? '') . '" />&nbsp;&nbsp;' . esc_html(TWXChatUtil::obscureApiKey($options['aws_cognito_identity_pool_id'] ?? '')) . '<p class="description">'.__('Enter AWS Cognito Identity Pool ID if you want to use AWS neural voices for Avatar Chatbots.','twx-chat').'<br/>'.
          __('Instructions for getting this key can be found','twx-chat').' <span class="twx-chat-feature-button" data-feature="aws_cognito_identity_pool_id">'.__('here','twx-chat').'</span></p>';
}

function twxchat_openai_embedding_model_setting() {
    $options = get_option('twx_chat_options');
    $current_model = isset($options['openai_embedding_model']) && !empty($options['openai_embedding_model']) ? $options['openai_embedding_model'] : TWXCHAT_DEFAULT_OPENAI_EMBEDDING_MODEL;
    $models = array_keys(TWXChatUtil::getEmbeddingModelCosts());
    ?>
    <select id="twx_chat_openai_embedding_model" name="twx_chat_options[openai_embedding_model]" data-current-model="<?php echo $current_model; ?>">
        <?php
        foreach($models as $model){
            echo '<option value="'.$model.'" '.selected($current_model, $model).'>'.esc_html($model).'</option>';
        }
        ?>
    </select>
    <p class="description"><?php echo esc_html__("Select which OpenAI embedding model to use for indexing your knowledge base content. Higher-quality models (like 'text-embedding-3-large') produce more accurate search results and support more languages, but cost slightly more per token.", 'twx-chat'); ?></p>
    <p class="description"><?php echo esc_html__("NOTE! For Non-Latin based languages (Arabic, Greek, Japanese etc), we recommend using one of the 'text-embedding-3' models, otherwise the default 'text-embedding-ada-002' is fine.",'twx-chat'); ?></p>
    <p class="description"><?php echo esc_html__("IMPORTANT! If you change this, you will be prompted to re-index your Knowledge Base which may take some time depending on how many entries you have. Also, you may need to adjust your 'Min. Similarity' settings since different embedding models yield different similarity score scales.",'twx-chat'); ?></p>
    <p class="description">
        <?php echo esc_html__('More info', 'twx-chat'); ?>
        <span class="twx-chat-feature-button" data-feature="openai_embedding_model">
            <?php echo esc_html__('here', 'twx-chat'); ?>
        </span>.
    </p>
    <?php
}

function twxchat_starting_comments_setting() {
    $options = get_option('twx_chat_options');
    echo '<textarea id="twx_chat_starting_comments" name="twx_chat_options[starting_comments]" rows="5" cols="50">' . esc_textarea($options['starting_comments'] ?? '') . '</textarea><p class="description">'.__('If no Starting Comments are specified in the Chatbot Settings, these will be used by default.','twx-chat').'</p>';
}
function twxchat_global_chatbot_id_setting() {
    $options = get_option('twx_chat_options');
    $current_id = isset($options['global_chatbot_id']) && !empty($options['global_chatbot_id']) ? $options['global_chatbot_id'] : '';
    $chatbots = TWXChatUtil::getChatbots(['style' => 'floating', 'remember_conversation' => ['true','logged_in']]);
    $chatbots = array_merge([['id'=>'','name'=>__('None','twx-chat')]], $chatbots);
    ?>
    <select id="twx_chat_global_chatbot_id" name="twx_chat_options[global_chatbot_id]">
        <?php
        foreach($chatbots as $chatbot){
            echo '<option value="'.$chatbot['id'].'" '.selected($current_id, $chatbot['id']).'>'.esc_html($chatbot['name']).'</option>';
        }
        ?>
    </select>
    <p class="description"><?php echo esc_html__("Select the Chatbot you want to appear on all pages. Chatbot settings must be 'Floating' style and able to 'Remember User Conversations'. If a page has another Chatbot shortcode, that Chatbot will be used instead.", 'twx-chat'); ?></p>
    <?php
}
function twxchat_frontpage_chatbot_id_setting() {
    $options = get_option('twx_chat_options');
    $current_id = isset($options['frontpage_chatbot_id']) && !empty($options['frontpage_chatbot_id']) ? $options['frontpage_chatbot_id'] : '';
    $chatbots = TWXChatUtil::getChatbots(['style' => 'floating']);
    $chatbots = array_merge([['id'=>'','name'=>__('None','twx-chat')]], $chatbots);
    ?>
    <select id="twx_chat_frontpage_chatbot_id" name="twx_chat_options[frontpage_chatbot_id]">
        <?php
        foreach($chatbots as $chatbot){
            echo '<option value="'.$chatbot['id'].'" '.selected($current_id, $chatbot['id']).'>'.esc_html($chatbot['name']).'</option>';
        }
        ?>
    </select>
    <p class="description"><?php echo esc_html__("Select the Floating Chatbot you want to appear on the Front page of your website. Will supercede any Global Chatbot setting for the Front page.", 'twx-chat'); ?></p>
    <?php
}
/*
function twxchat_frontpage_shortcode_setting() {
    $options = get_option('twx_chat_options');
    echo "<textarea id='twx_chat_frontpage_shortcode' name='twx_chat_options[frontpage_shortcode]' rows='2' cols='100'>" . esc_textarea($options['frontpage_shortcode'] ?? '') . '</textarea><p class="description">'.__('Shortcode for chatbot that will be displayed on the front page, must be Style=floating','twx-chat').'</p>';
}
*/
function twxchat_custom_css_setting() {
    $options = get_option('twx_chat_options');
    echo '<textarea id="twx_chat_custom_css" name="twx_chat_options[custom_css]" rows="10" cols="100">' . esc_textarea($options['custom_css'] ?? '') . '</textarea><p class="description">'.__('Customise the way your Chatbots look with your own CSS styling here.','twx-chat').'</p>';
}

function twxchat_site_crawler_public_posts_setting() {
    $options = get_option('twx_chat_options');
    $value = isset($options['site_crawler_public_posts']) ? $options['site_crawler_public_posts'] : 0;
    echo '<input type="checkbox" id="twx_chat_site_crawler_public_posts_chk" name="twx_chat_options[site_crawler_public_posts]" value="1" ' . checked(1, $value, false) . '>';
}

function twxchat_site_crawler_private_posts_setting() {
    $options = get_option('twx_chat_options');
    $value = isset($options['site_crawler_private_posts']) ? $options['site_crawler_private_posts'] : 0;
    echo '<input type="checkbox" id="twx_chat_site_crawler_private_posts_chk" name="twx_chat_options[site_crawler_private_posts]" value="1" ' . checked(1, $value, false) . '>';
}

function twxchat_site_crawler_selected_posts_setting(){

    ?>
    <button type="button" class="button" id="twxchat-toggle-site-crawler-selected-posts-table">
        <?php _e('Show/Hide Page & Post Selector', 'twx-chat'); ?>
    </button>

    <div id="twxchat-site-crawler-selected-posts-table-wrap" style="display:none; margin-top:1em;">
        <?php
        require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';

        class TWXChat_Crawler_List_Table extends WP_List_Table {
         public function prepare_items() {
                // Fetch posts and pages
                $args = [
                    'post_type'      => ['post', 'page'],
                    'post_status'    => 'publish',
                    'posts_per_page' => 50,
                ];

                $query = new WP_Query($args);
                $this->items = $query->posts;

                $columns  = $this->get_columns();
                $hidden   = [];
                $sortable = [];
                $this->_column_headers = [$columns, $hidden, $sortable];
            }

            public function get_columns() {
                return [
                    'cb'    => '<input type="checkbox" />',
                    'title' => __('Title', 'twx-chat'),
                    'type'  => __('Type', 'twx-chat'),
                    'date'  => __('Last Modified', 'twx-chat'),
                ];
            }

            protected function column_cb($item) {
                $options = get_option('twx_chat_options');
                $selected = (array) $options['twx_chat_site_crawler_selected_posts'] ?? [];
                $checked = in_array($item->ID, $selected) ? 'checked' : '';
                return sprintf(
                    '<input type="checkbox" name="twx_chat_site_crawler_selected_posts[]" value="%s" %s />',
                    esc_attr($item->ID),
                    $checked
                );
            }

            protected function column_title($item) {
                $edit_link = get_edit_post_link($item->ID);
                return sprintf(
                    '<a href="%s" target="_blank">%s</a>',
                    esc_url($edit_link),
                    esc_html($item->post_title ?: __('(no title)', 'twx-chat'))
                );
            }

            protected function column_type($item) {
                return esc_html($item->post_type);
            }

            protected function column_date($item) {
                return esc_html(get_the_modified_date('', $item->ID));
            }

            public function no_items() {
                _e('No posts or pages found.', 'twxchat');
            }
        }

        $table = new TWXChat_Crawler_List_Table();
        $table->prepare_items();
        $table->display();
        ?>
    </div>

    <script>
        document.getElementById('twxchat-toggle-site-crawler-selected-posts-table').addEventListener('click', function(){
            const el = document.getElementById('twxchat-site-crawler-selected-posts-table-wrap');
            el.style.display = el.style.display === 'none' ? 'block' : 'none';
        });
    </script>
    <?php    

}

function twxchat_site_crawler_onsite_media_setting() {
    $options = get_option('twx_chat_options');
    $value = isset($options['site_crawler_onsite_media']) ? $options['site_crawler_onsite_media'] : 0;
    echo '<input type="checkbox" id="twx_chat_site_crawler_onsite_media_chk" name="twx_chat_options[site_crawler_onsite_media]" value="1" ' . checked(1, $value, false) . '>&nbsp;&nbsp;<span class="description">'.__('Note: only Media items with either Alt Text, Captions or Descriptions are crawled. PDFs will be split into pages in the Knowledge Base.','twx-chat').'</span>';
}

function twxchat_site_crawler_exclude_sources_setting() {
    $options = get_option('twx_chat_options');
    $value = isset($options['site_crawler_exclude_sources']) ? $options['site_crawler_exclude_sources'] : 0;
    echo '<input type="checkbox" id="twx_chat_site_crawler_exclude_sources_chk" name="twx_chat_options[site_crawler_exclude_sources]" value="1" ' . checked(1, $value, false) . '>';
	echo '&nbsp;<input id="twx_chat_site_crawler_exclude_sources_list" name="twx_chat_options[site_crawler_exclude_sources_list]" size="40" type="text" value="' . esc_attr($options['site_crawler_exclude_sources_list'] ?? '') . '" /><p style="margin-left:30px" class="description">'.__('Comma seperated list of sources to exclude from the crawl, eg: \'checkout,demo\' would exclude any sources that have \'checkout\' or \'demo\' in the URL','twx-chat').'</p>';
}

function twxchat_site_crawler_create_llms_txt_setting(){
    $options = get_option('twx_chat_options');
    $value = isset($options['site_crawler_create_llms_txt']) ? $options['site_crawler_create_llms_txt'] : 0;
    echo '<input type="checkbox" id="twx_chat_site_crawler_create_llms_txt_chk" name="twx_chat_options[site_crawler_create_llms_txt]" value="1" ' . checked(1, $value, false) . '>
        &nbsp;&nbsp;<span class="description">'.__('Generate an','twx-chat').' <span class="twx-chat-feature-button" data-feature="create_llms_txt">llms.txt</span> '.__('file used by external AI Agents to ingest your website content without having to crawl your whole sitemap.','twx-chat').'<br/>
		&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'.__('This only works for the \'Crawl Public Posts/Pages\' setting above.','twx-chat').'<br/>
		&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'.__('NOTE! Checking this ON will overwrite any existing /llms.txt on your website','twx-chat').'</span>.';
}

function twxchat_site_crawler_sitemap_setting() {

    if(!TWXChatUtil::customerPlanAccess('pro')){
    	echo '<p class="description">'.__('Available in ','twx-chat').'<span class="twx-pro-button">Pro Plan</span></p>';
        return;
    }

    $options = get_option('twx_chat_options');
    $value = isset($options['site_crawler_sitemap']) ? $options['site_crawler_sitemap'] : 0;
    echo '<input type="checkbox" id="twx_chat_site_crawler_sitemap_chk" name="twx_chat_options[site_crawler_sitemap]" value="1" ' . checked(1, $value, false) . '>';
	echo '&nbsp;<input id="twx_chat_site_crawler_start_url" name="twx_chat_options[site_crawler_start_url]" size="40" type="text" value="' . esc_attr($options['site_crawler_start_url'] ?? '') . '" /><p style="margin-left:30px" class="description">'.__('eg:','twx-chat').' https://yoursite.com/sitemap.xml</p>';
}

function twxchat_site_crawler_google_drive_setting() {

    if(!TWXChatUtil::customerPlanAccess('pro') || !method_exists('TWXChatPro', 'gdrive_get_folder_name')){
    	echo '<p class="description">'.__('Available in ','twx-chat').'<span class="twx-pro-button">Pro Plan</span></p>';
        return;
    }

    $options = get_option('twx_chat_options');
    $value = isset($options['site_crawler_google_drive']) ? $options['site_crawler_google_drive'] : 0;
    echo '<input type="checkbox" id="twx_chat_site_crawler_google_drive_chk" name="twx_chat_options[site_crawler_google_drive]" value="1" ' . checked(1, $value, false) . '>';

	$folder_id = $options['site_crawler_gdrive_folder_id'] ?? '';
	$folder_name = '';
	if(!empty($folder_id)){
        $folder_name = TWXChatPro::gdrive_get_folder_name($folder_id);
		if($folder_name === false) $folder_name = __('Invalid Google Drive folder ID or invalid Google API Key in General tab','twx-chat');
	}
	echo '&nbsp;&nbsp;<input id="twx_chat_site_crawler_gdrive_folder_id" name="twx_chat_options[site_crawler_gdrive_folder_id]" size="40" type="text" value="' . esc_attr($options['site_crawler_gdrive_folder_id'] ?? '') . '" />&nbsp;&nbsp;' . esc_html($folder_name) . '<p style="margin-left:30px" class="description">'.__('Google Drive Folder ID to crawl. NOTE: Currently only PDF files are crawled.','twx-chat').'</p>';
}

function twxchat_site_crawler_update_assistant_setting() {
    $options = get_option('twx_chat_options');
    $value = isset($options['site_crawler_update_assistant']) ? $options['site_crawler_update_assistant'] : 0;
    echo '<input type="checkbox" id="twx_chat_site_crawler_update_assistant_chk" name="twx_chat_options[site_crawler_update_assistant]" value="1" ' . checked(1, $value, false) . '>';
	echo '&nbsp;<input id="twx_chat_site_crawler_assistant_id" name="twx_chat_options[site_crawler_assistant_id]" size="40" type="text" value="' . esc_attr($options['site_crawler_assistant_id'] ?? '') . '" /><p style="margin-left:30px" class="description">'.__('OpenAI Assistant ID. Must belong to the OpenAI API Key specified in the General tab','twx-chat').'</p>';
}

function twxchat_site_crawler_woocommerce_products_setting(){

    if(!TWXChatUtil::customerPlanAccess('pro') || !method_exists('TWXChatPro', 'get_woocommerce_products')){
    	echo '<p class="description">'.__('Available in ','twx-chat').'<span class="twx-pro-button">Pro Plan</span></p>';
        return;
    }

    if ( ! class_exists( 'WooCommerce' ) ) {    
    	echo '<p class="description">WooCommerce '.__('Not Installed/Active','twx-chat').'</p>';
        return;
    }

    $description = __('Crawl Active WooCommerce Products','twx-chat');
    $options = get_option('twx_chat_options');
    $value = isset($options['site_crawler_woocommerce_products']) ? $options['site_crawler_woocommerce_products'] : 0;
    echo '<input type="checkbox" id="twx_chat_site_crawler_woocommerce_products_chk" name="twx_chat_options[site_crawler_woocommerce_products]" value="1" ' . checked(1, $value, false) . '>&nbsp;&nbsp;<span class="description">'.wp_kses_post($description).'</span>';
}

function twxchat_site_crawler_schedule_setting() {
    $options = get_option('twx_chat_options');
    $schedule = $options['site_crawler_schedule'] ?? '';
    $running = get_option('twx_chat_site_crawler_running',false);
    ?>
    <select id="twx_chat_site_crawler_schedule" name="twx_chat_options[site_crawler_schedule]">
        <option value="" <?php selected($schedule, ''); ?>><?php echo __('Select','twx-chat'); ?>...</option>
        <option value="Daily" <?php selected($schedule, 'Daily'); ?>><?php echo __('Daily','twx-chat'); ?></option>
        <option value="Weekly" <?php selected($schedule, 'Weekly'); ?>><?php echo __('Weekly','twx-chat'); ?></option>
    </select>
    <button class="button button-primary" data-running="<?php echo esc_attr($running); ?>" id="twx_chat_site_crawler_crawl_now_btn"><?php echo __('Crawl Now!','twx-chat'); ?></button>&nbsp;&nbsp;
    <button class="button button-primary" id="twx_chat_site_crawler_cancel_crawl_btn" disabled><?php echo __('Cancel Crawl','twx-chat'); ?></button>
    <?php
}

function twxchat_site_crawler_schedule_time_setting() {
    $options = get_option('twx_chat_options');
    echo '<input id="twx_chat_site_crawler_schedule_time" name="twx_chat_options[site_crawler_schedule_time]" type="time" value="' . esc_attr($options['site_crawler_schedule_time'] ?? '') . '" />';
}

function twxchat_site_crawler_schedule_dayofweek_setting() {
    $options = get_option('twx_chat_options');
    $schedule_dayofweek = $options['site_crawler_schedule_dayofweek'] ?? '';
    ?>
    <select id="twx_chat_site_crawler_schedule_dayofweek" name="twx_chat_options[site_crawler_schedule_dayofweek]">
        <option value="" <?php selected($schedule_dayofweek, ''); ?>><?php echo __('Select','twx-chat'); ?>...</option>
        <option value="Monday" <?php selected($schedule_dayofweek, 'Monday'); ?>><?php echo __('Monday','twx-chat'); ?></option>
        <option value="Tuesday" <?php selected($schedule_dayofweek, 'Tuesday'); ?>><?php echo __('Tuesday','twx-chat'); ?></option>
        <option value="Wednesday" <?php selected($schedule_dayofweek, 'Wednesday'); ?>><?php echo __('Wednesday','twx-chat'); ?></option>
        <option value="Thursday" <?php selected($schedule_dayofweek, 'Thursday'); ?>><?php echo __('Thursday','twx-chat'); ?></option>
        <option value="Friday" <?php selected($schedule_dayofweek, 'Friday'); ?>><?php echo __('Friday','twx-chat'); ?></option>
        <option value="Saturday" <?php selected($schedule_dayofweek, 'Saturday'); ?>><?php echo __('Saturday','twx-chat'); ?></option>
        <option value="Sunday" <?php selected($schedule_dayofweek, 'Sunday'); ?>><?php echo __('Sunday','twx-chat'); ?></option>
    </select>
    <?php
}

function twxchat_site_crawler_status_setting() {

    echo '<div id="twx_chat_site_crawler_log"><ul>';
	$logFile = TWXChatUtil::getUploadsDir('spider_results').'twxchatspider_progress.txt';
	if(file_exists($logFile)){
		$logLines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
		foreach($logLines as $line){
			echo '<li>' . TWXChatUtil::escHtml($line) . '</li>'; 
		}
	}
    echo '</ul></div>';
}

function twxchat_settings_error_exists($error_code){

    // Fetch existing settings errors
    $existing_errors = get_settings_errors('twx_chat_options');
    
    // Check if the specific error already exists
    if (!empty($existing_errors)) {
        foreach ($existing_errors as $error) {
            if ($error['code'] === $error_code) {
                return true;
            }
        }
    }

    return false;
}

function twxchat_remove_settings_error($error_code){
    $errors = get_settings_errors('twx_chat_options');
    if ($errors) {
        foreach ($errors as $key => $error) {
            if ($error['code'] === $error_code) {
                unset($errors[$key]);
            }
        }
        // Re-register the cleaned-up errors
        global $wp_settings_errors;
        $wp_settings_errors = $errors;
    }    
}

function twxchat_updateOption($opt_name, &$input, &$options, &$new_input, $default_val = '{NOT_SUPPLIED}'){
    // Updates $new_input and returns true if changed from prev

    if(isset($input[$opt_name])){

        // User entered value
        $val = $input[$opt_name];
        $new_input[$opt_name] = is_string($val) ? sanitize_text_field($val) : $val;
        // Check if changed
        if(isset($options[$opt_name]) && $val != $options[$opt_name]){
            return true;
        }else{
            return false;
        }

    }else{

        // no user value, use prev option
        if(isset($options[$opt_name])){
            $new_input[$opt_name] = $options[$opt_name];
            return false;
        }else{
            if($default_val !== '{NOT_SUPPLIED}') {
                $new_input[$opt_name] = $default_val;
                return true;
            }else{
                return false;
            }
        }
    }
}

function twxchat_options_hasChangedVal($optName, &$options, &$input){
    $oldVal = $options[$optName] ?? null;
    $newVal = $input[$optName] ?? null;
    return $oldVal !== $newVal;
}

function twxchat_get_admin_tab_by_options($options){
    if(isset($options['openai_apikey'])) return 'general';
    if(isset($options['site_crawler_schedule'])) return 'site-crawler';
    if(isset($options['custom_css'])) return 'customize';
    if(isset($options['twx_customer_key'])) return 'plugininfo';
    if(isset($options['reports_chatlog_summary_email_schedule'])) return 'reports';
    return false;
}

function twxchat_all_options_validate($input) {

	// Validate all Admin settings. Need to put all options (all tabs) in here otherwise values won't be saved

    $options = get_option('twx_chat_options', []);

    $tab = twxchat_get_admin_tab_by_options($input);

    // Initial Merge existing options into validated output
    $new_input = $options;

    switch($tab){
        case 'general':
            twxchat_validate_general_options($input, $options, $new_input);
            break;
        case 'customize':
            twxchat_validate_customize_options($input, $options, $new_input);
            break;
        case 'site-crawler':
            twxchat_validate_site_crawler_options($input, $options, $new_input);
            break;
        case 'reports':
            twxchat_validate_report_options($input, $options, $new_input);
            break;
        case 'plugininfo':
            twxchat_validate_plugininfo_options($input, $options, $new_input);
            break;
    }

    return $new_input;
}

function twxchat_validate_general_options(&$input, &$options, &$new_input){

    // Validate 'General' options - just set $new_input values as needed

    // OpenAI API Key - mandatory
    $error_code = 'twx_chat_openai_apikey_error';
    $optName = 'openai_apikey';
    twxchat_updateOption($optName, $input, $options, $new_input, '');
    if(empty($new_input[$optName])){
        if(!twxchat_settings_error_exists($error_code)){
            add_settings_error(
                'twx_chat_options',
                $error_code,
                __('OpenAI API Key is required.', 'twx-chat'),
                'error'
            );
        }
    }else{
        twxchat_remove_settings_error($error_code);
    }

    // Min Similarity
    /* REMOVED v1.9.24
    $minAllowVal = 1; $maxAllowVal = 99;
    if (isset($input['min_similarity'])) {
        $value = intval($input['min_similarity']);
        if ($value < $minAllowVal) {
            $value = $minAllowVal; // Minimum allowed value
        } elseif ($value > $maxAllowVal) {
            $value = $maxAllowVal; // Maximum allowed value
        }
        $new_input['min_similarity'] = $value;
    } else {
		$new_input['min_similarity'] = $options['min_similarity'] ?? TWXCHAT_DEFAULT_MIN_SIMILARITY;
	}
    */

	// Google API Key
    twxchat_updateOption('google_apikey', $input, $options, $new_input);

    // AWS Congnito Identity Pool ID
    twxchat_updateOption('aws_cognito_identity_pool_id', $input, $options, $new_input);

    // OpenAI Text Embedding Model
    $changed = twxchat_updateOption('openai_embedding_model', $input, $options, $new_input, TWXCHAT_DEFAULT_OPENAI_EMBEDDING_MODEL);
    if($changed){
        // re-index KB
        $apiKey = get_option('twx_chat_options')['openai_apikey'] ?? '';
        $numRefreshed = TWXChatKnowledgeBaseUtil::refreshEmbeddings($apiKey, $new_input['openai_embedding_model']);
        TWXChatUtil::adminLog("$numRefreshed embeddings refreshed in KB");
    }

}

function twxchat_validate_customize_options(&$input, &$options, &$new_input){

    // Validate 'Customize' options - just set $new_input values as needed

	$new_input['starting_comments'] = isset($input['starting_comments']) ? sanitize_textarea_field($input['starting_comments']) : $options['starting_comments'];
	$new_input['custom_css'] = isset($input['custom_css']) ? TWXChatUtil::sanitizeCSS($input['custom_css']) : $options['custom_css'];

    // global_chatbot_id
    //twxchat_updateOption('global_chatbot_id', $input, $options, $new_input, '');
    $error_code = 'twx_chat_global_chatbot_id_validation_error';
	if(isset($input['global_chatbot_id'])){
        if(twxchat_validate_global_chatbot_id($input['global_chatbot_id'])){
            $new_input['global_chatbot_id'] = $input['global_chatbot_id'];
            twxchat_remove_settings_error($error_code);
        }else{
            if(!twxchat_settings_error_exists($error_code)){
                add_settings_error(
                    'twx_chat_options',
                    $error_code,
                    __('Invalid Global Chatbot ID. Ensure Chatbot has style=floating and remember_conversation=(not false).', 'twx-chat'),
                    'error'
                );
            }
        }
    }else{
        $new_input['global_chatbot_id'] = ($options['global_chatbot_id'] ?? '');
        twxchat_remove_settings_error($error_code);
    }

    // frontpage_chatbot_id
    //twxchat_updateOption('frontpage_chatbot_id', $input, $options, $new_input, '');
    $error_code = 'twx_chat_frontpage_chatbot_id_validation_error';
	if(isset($input['frontpage_chatbot_id'])){
        if(twxchat_validate_frontpage_chatbot_id($input['frontpage_chatbot_id'])){
            $new_input['frontpage_chatbot_id'] = $input['frontpage_chatbot_id'];
            twxchat_remove_settings_error($error_code);
        }else{
            if(!twxchat_settings_error_exists($error_code)){
                add_settings_error(
                    'twx_chat_options',
                    $error_code,
                    __('Invalid Front Page Chatbot ID. Ensure Chatbot has Style=floating.', 'twx-chat'),
                    'error'
                );
            }
        }
    }else{
        $new_input['frontpage_chatbot_id'] = ($options['frontpage_chatbot_id'] ?? '');
        twxchat_remove_settings_error($error_code);
    }

}

function twxchat_validate_site_crawler_options(&$input, &$options, &$new_input){

    // Validate 'Site Crawler' options - just set $new_input values as needed

    // Checkbox options are handled differently as their input will be missing if unchecked
    $new_input['site_crawler_public_posts'] = isset($input['site_crawler_public_posts']) ? '1' : '0';
    $new_input['site_crawler_private_posts'] = isset($input['site_crawler_private_posts']) ? '1' : '0';
    $new_input['site_crawler_onsite_media'] = isset($input['site_crawler_onsite_media']) ? '1' : '0';
    $new_input['site_crawler_exclude_sources'] = isset($input['site_crawler_exclude_sources']) ? '1' : '0';
    $new_input['site_crawler_sitemap'] = isset($input['site_crawler_sitemap']) ? '1' : '0';
    $new_input['site_crawler_create_llms_txt'] = isset($input['site_crawler_create_llms_txt']) ? '1' : '0';
    $new_input['site_crawler_woocommerce_products'] = isset($input['site_crawler_woocommerce_products']) ? '1' : '0';

    twxchat_updateOption('site_crawler_exclude_sources_list', $input, $options, $new_input);

    twxchat_updateOption('site_crawler_start_url', $input, $options, $new_input);
    $error_code = 'twx_chat_site_crawler_start_url_not_exists';
    if($new_input['site_crawler_sitemap'] == '1' && isset($new_input['site_crawler_start_url']) && !empty($new_input['site_crawler_start_url']) && !TWXChatUtil::urlExists($new_input['site_crawler_start_url'])){
        if(!twxchat_settings_error_exists($error_code)){
            add_settings_error(
                'twx_chat_options',
                $error_code,
                __("Sitemap URL does not exist.", 'twx-chat'),
                'error'
            );
        }
    }else{
        twxchat_remove_settings_error($error_code);
    }

    $new_input['site_crawler_google_drive'] = isset($input['site_crawler_google_drive']) ? '1' : '0';
    $optName = 'site_crawler_gdrive_folder_id';
    if(twxchat_updateOption($optName, $input, $options, $new_input)){
        $new_input[$optName] = str_ireplace('https://drive.google.com/drive/u/1/folders/', '', $new_input[$optName]);
    }
    $error_code = 'twx_chat_site_crawler_gdrive_folder_id_missing_error';
    if(!empty($new_input['site_crawler_google_drive']) && empty($new_input['site_crawler_gdrive_folder_id'])){
        if(!twxchat_settings_error_exists($error_code)){
            add_settings_error(
                'twx_chat_options',
                $error_code,
                __('Google Drive Folder ID is required if selected to Crawl.', 'twx-chat'),
                'error'
            );
        }
    }else{
        twxchat_remove_settings_error($error_code);
    }

    $new_input['site_crawler_update_assistant'] = isset($input['site_crawler_update_assistant']) ? '1' : '0';
    twxchat_updateOption('site_crawler_assistant_id', $input, $options, $new_input);
    $error_code = 'twx_chat_site_crawler_assistant_id_missing_error';
    if(!empty($new_input['site_crawler_update_assistant']) && empty($new_input['site_crawler_assistant_id'])){
        if(!twxchat_settings_error_exists($error_code)){
            add_settings_error(
                'twx_chat_options',
                $error_code,
                __('OpenAI Assistant ID is required if selected to Update.', 'twx-chat'),
                'error'
            );
        }
    }else{
        twxchat_remove_settings_error($error_code);
    }

    twxchat_updateOption('site_crawler_schedule', $input, $options, $new_input, '');
    twxchat_updateOption('site_crawler_schedule_dayofweek', $input, $options, $new_input, '');
    twxchat_updateOption('site_crawler_schedule_time', $input, $options, $new_input, '');

    // validate schedule settings
    $error_code = 'twx_chat_site_crawler_schedule_time_error';
    if($new_input['site_crawler_schedule'] == 'Daily' || $new_input['site_crawler_schedule'] == 'Weekly'){
        // ensure time is set. Filter 'update_option_twxchat_options' will also be triggered which will set schedule if needed
        if(!isset($new_input['site_crawler_schedule_time']) || empty($new_input['site_crawler_schedule_time'])){
            if(!twxchat_settings_error_exists($error_code)){
                add_settings_error(
                    'twx_chat_options',
                    $error_code,
                    __('Site Crawler Schedule time is required.', 'twx-chat'),
                    'error'
                );
            }
        }else{
            twxchat_remove_settings_error($error_code);
        }
    }else{
        twxchat_clear_all_site_crawler_scheduled_jobs();
        twxchat_remove_settings_error($error_code);
    }

    $error_code = 'twx_chat_site_crawler_schedule_dayofweek_error';
    if($new_input['site_crawler_schedule'] == 'Weekly'){
        // ensure DOW set (and time from above)
        if(!isset($new_input['site_crawler_schedule_dayofweek']) || empty($new_input['site_crawler_schedule_dayofweek'])){
            if(!twxchat_settings_error_exists($error_code)){
                add_settings_error(
                    'twx_chat_options',
                    $error_code,
                    __('Site Crawler Schedule Day Of Week is required.', 'twx-chat'),
                    'error'
                );
            }
        }else{
            twxchat_remove_settings_error($error_code);
        }
    }else{
        twxchat_remove_settings_error($error_code);
    }

	if(isset($input['site_crawler_status'])){
		$new_input['site_crawler_status'] = $input['site_crawler_status'];
	}else{
		$new_input['site_crawler_status'] = [];
	}

}

function twxchat_validate_report_options(&$input, &$options, &$new_input){

    // Validate 'report' options - just set $new_input values as needed

    twxchat_updateOption('reports_chatlog_summary_email_schedule', $input, $options, $new_input, '');

    if($new_input['reports_chatlog_summary_email_schedule'] == 'Daily' || $new_input['reports_chatlog_summary_email_schedule'] == 'Weekly'){
        //
    }else{
        twxchat_clear_all_reporting_jobs();
    }

}

function twxchat_validate_plugininfo_options(&$input, &$options, &$new_input){

    // Validate 'plugininfo' options - just set $new_input values as needed

    $new_input['show_watermark'] = isset($input['show_watermark']) ? 1 : 0; // checkbox values wont exist at all if they are unchecked, otherwise they are checked

    // TWX Customer Key
    if(class_exists('TWXChatPlus')){
        $optName = 'twx_customer_key';
        if(twxchat_updateOption($optName, $input, $options, $new_input, '')){
            // on change, verify customer
            TWXChatPlus::verifyCustomer($input[$optName]);
        }
    }

}

function twxchat_validate_frontpage_chatbot_id($chatbot_id){

    // Must be empty or have style=floating
    if(empty($chatbot_id)) return true;
    $atts = TWXChatUtil::getChatbotAttributesById(['id'=>$chatbot_id],true);
    return (isset($atts['style']) && $atts['style'] === 'floating');
}

function twxchat_validate_global_chatbot_id($chatbot_id){

    // Must be empty or have style=floating
    if(empty($chatbot_id)) return true;
    $atts = TWXChatUtil::getChatbotAttributesById(['id'=>$chatbot_id],true);
    return (isset($atts['style']) && $atts['style'] === 'floating' && 
            isset($atts['remember_conversation']) && $atts['remember_conversation'] != 'false');
}

// Shortcode Handler
function twxchat_shortcode($atts) {

    global $twxchat_chatbot_rendered;
    $twxchat_chatbot_rendered = true;

    if(empty($atts['id'] ?? null)) return __('Invalid twxchat shortcode. Missing chatbot id','twx-chat');

    $atts = TWXChatUtil::getChatbotAttributesById($atts, true);
    $chatbot_id = trim($atts['id']);

	// Set attribute defaults if empty
	$options = get_option('twx_chat_options');
    $cookie_name = 'twxchat_remember_' . $chatbot_id;

    // Check if we need to remember previous conversations
    if(empty($atts['thread_id'])) {

        $thread_id = '';

        if (
            isset($atts['remember_conversation']) &&
            (
                $atts['remember_conversation'] === 'true' ||
                ($atts['remember_conversation'] === 'logged_in' && is_user_logged_in())
            )
        ) {
            $atts['remember_conversation'] = 'true'; // Client JS only needs true/false
            $thread_id_short = '';

            // Check if the request has a URL parameter
            if(isset($_GET[$cookie_name])){

                // Use GET param instead of browser cookie
                $thread_id_short = sanitize_text_field($_GET[$cookie_name]);

                // Check if user wants to unset cookie
                if(empty($thread_id_short)){
                    // user wants to unset cookie
                    setcookie($cookie_name, '', time() - 3600, '/');
                    unset($_COOKIE[$cookie_name]);
                }

                // Verify thread id matches chatbot id
                if(empty(TWXChatLog::getChatLogEntriesByThreadId("twxchat_thread_{$thread_id_short}", null, $chatbot_id))){
                    $thread_id_short = '';
                }

            }else{
                // Check if user want to explictly forget thread, ie: uses ?twxchat_forget_3=1  will forget browser cookie for chatbot #3
                if(!empty($_GET['twxchat_forget_'.$chatbot_id] ?? '0')){
                    setcookie($cookie_name, '', time() - 3600, '/');
                    unset($_COOKIE[$cookie_name]);
                }else{
                    if(isset($_COOKIE[$cookie_name])){
                        $thread_id_short = sanitize_text_field($_COOKIE[$cookie_name]);
                        // Verify thread id matches chatbot id
                        if(empty(TWXChatLog::getChatLogEntriesByThreadId("twxchat_thread_{$thread_id_short}", null, $chatbot_id))){
                            $thread_id_short = '';
                        }
                    }else{
                        $thread_id_short = TWXChatUtil::generateConversationThreadId(true);
                    }
                }
            }
			
			if(!empty($thread_id_short)){
				$thread_id = "twxchat_thread_{$thread_id_short}";
			}
			
        }

        if(empty($thread_id) && $atts['kb_or_assistant'] !== 'openai_assistant') {            
            $thread_id = TWXChatUtil::generateConversationThreadId();
        }

        $atts['thread_id'] = $thread_id;
    }
	if(empty($atts['min_similarity'])) $atts['min_similarity'] = TWXCHAT_DEFAULT_MIN_SIMILARITY;
	if(empty($atts['starting_comments'])) $atts['starting_comments'] = $options['starting_comments'];

    $allAtts = TWXChatUtil::getAllChatbotShortcodeAttributes();
	
	$topDiv = '<div id="twxchat-container"';
    $exclude = ['extra_instructions', 'starting_comments'];
	foreach($atts as $attr => $val){
        if(in_array($attr, $exclude)) continue;
        // if there is a parent attribute dependency, check it
        if(isset($allAtts[$attr])){
            $attDef = $allAtts[$attr];
            if(isset($attDef['parent_attr']) && isset($attDef['parent_attr_value']) && isset($atts[$attDef['parent_attr']])){
                $parentAtt = $atts[$attDef['parent_attr']];
                if($parentAtt !== $attDef['parent_attr_value']) continue;
            }
        }

		$newAttr = 'data-'.str_replace('_','-',$attr);
        if(is_array($val)) $val = json_encode($val);
        $topDiv .= ' '.$newAttr.'="'.htmlspecialchars(($val ?? ''), ENT_QUOTES, 'UTF-8').'"';
	}

    // detect if user logged in and can edit pages
    if (is_user_logged_in() && current_user_can('edit_posts')) {
		$topDiv .= ' data-user-can-edit="true"';
    }
    // detect if we are using the global chatbot which has some different capabilities in JS
    if (($options['global_chatbot_id'] ?? '') === $atts['id']){
        $topDiv .= ' data-global-chatbot="true"';
    }
	$topDiv .= '>';

	$type = $atts['type'] ?? '';
    if(!$type) return __('Missing twxchat shortcode attribute','twx-chat').': type';

    $style = $atts['style'] ?? '';
    if(!$style) return __('Missing twxchat shortcode attribute','twx-chat').': style';

    if($type === 'avatar'){
        if($style === 'embedded'){
            $inner_html = twx_get_avatar_container_html($atts);
            return $topDiv.'<div id="twx-chat-embed">'.$inner_html.'</div></div>';
        }else{
            return $topDiv.twx_get_chatbot_minimized_html($atts).'</div>';
        }
    }else{
        if($style === 'embedded'){
            $inner_html = twx_get_chatbot_container_html($atts);
            return $topDiv.'<div id="twx-chat-embed">'.$inner_html.'</div></div>';
        }elseif($style === 'floating'){
            return $topDiv.twx_get_chatbot_minimized_html($atts).'</div>';
        }else{
            return __('Invalid twxchat shortcode attributes','twx-chat');
        }
    }

}
add_shortcode('twxchat', 'twxchat_shortcode');

function twxchat_render_shortcode_in_footer() {
    // For Global/Frontpage Chatbots that don't have an actual shortcode in the page

    global $twxchat_chatbot_rendered;
    if($twxchat_chatbot_rendered) return; // Chatbot already rendered

    // after footer rendered, check if we want to append floating global/frontpage chatbot
    $options = get_option('twx_chat_options');
    $frontpage_chatbot_id = $options['frontpage_chatbot_id'] ?? '';
    if(!empty($frontpage_chatbot_id) && is_front_page()){
        //TWXChatUtil::adminLog("twxchat_render_shortcode_in_footer:: frontpage_chatbot_id: $frontpage_chatbot_id");
        echo do_shortcode('[twxchat id="'.$frontpage_chatbot_id.'"]');
        return;
    }
    
    // Global chatbot maybe
    $global_chatbot_id = $options['global_chatbot_id'] ?? '';
    if(!empty($global_chatbot_id)){
        //TWXChatUtil::adminLog("twxchat_render_shortcode_in_footer:: global_chatbot_id: $global_chatbot_id");
        echo do_shortcode('[twxchat id="'.$global_chatbot_id.'"]');
        return;
    }
}
add_action('wp_footer', 'twxchat_render_shortcode_in_footer', 20);

function twxchat_enqueue_scripts() {

    global $post;

    // ensure shortcode exists on page or we have frontpage/global chatbot setting
    $atts = [];
    $options = get_option('twx_chat_options');
    $frontpage_chatbot_id = $options['frontpage_chatbot_id'] ?? '';
    $global_chatbot_id = $options['global_chatbot_id'] ?? '';
    $chatbot_id = null;
    if(!empty($frontpage_chatbot_id) && is_front_page()){
        $chatbot_id = $frontpage_chatbot_id;
        $atts = TWXChatUtil::getChatbotAttributesById(['id'=>$chatbot_id], true);
    }else if(has_shortcode($post->post_content, 'twxchat') && (is_page() || is_single())){
        $atts = TWXChatUtil::getChatbotAttributesFromPost($post);
        if(isset($atts['id']) && !empty($atts['id'])){
            $chatbot_id = $atts['id'];
            $atts = TWXChatUtil::getChatbotAttributesById(['id'=>$chatbot_id], true); // ignore all atts present in shortcode except id
        }else{
            $atts = [];
        }
    }else if(!empty($global_chatbot_id)){
        $chatbot_id = $global_chatbot_id;
        $atts = TWXChatUtil::getChatbotAttributesById(['id'=>$chatbot_id], true);
    }

    if(empty($atts)) return; // nothing to render or invalid atts

    // Get Avatar options
    $is_avatar = false;
    $avatar_bkg_url = '';

    if(isset($atts['type']) && $atts['type'] === 'avatar') $is_avatar = true;
    if(isset($atts['avatar_background_url']) && !empty($atts['avatar_background_url'])) $avatar_bkg_url = $atts['avatar_background_url'];

    /*
    if(empty($chatbot_id)){
        $atts = TWXChatUtil::getChatbotAttributesFromPost($post);
    }else{
        $atts = TWXChatUtil::getChatbotAttributesById(['id'=>$chatbot_id], true);
    }
    if(empty($atts)) {
        TWXChatUtil::adminLog("twxchat_enqueue_scripts:: empty atts");
        return;
    }
    */

    /*
    if(isset($atts['id']) && !empty($atts['id'])){
        $atts = TWXChatUtil::getChatbotAttributesById($atts,false);
        if(isset($atts['type']) && $atts['type'] === 'avatar') $is_avatar = true;
        if(isset($atts['avatar_background_url']) && !empty($atts['avatar_background_url'])) $avatar_bkg_url = $atts['avatar_background_url'];

    }else{
        // Non-ID Chatbot. If the shortcode with the 'avatar_character' attribute is found, set flag
        preg_match_all( '/\[twxchat[^]]*avatar_character=["\'][^"\']*["\'][^]]*\]/', $post->post_content, $matches );
        $is_avatar = !empty($matches[0]);

        $matches = [];
        preg_match_all('/\[twxchat[^]]*avatar_background_url=["\']([^"\']+)["\'][^]]*\]/', $post->post_content, $matches);
        if (!empty($matches[1])) {
            $avatar_bkg_url = $matches[1][0]; // Get the first match
        }
    }
    */

    $pluginInfo = TWXChatUtil::getPluginInfo();

    wp_enqueue_script('twx_chat_script', plugins_url('assets/js/twx-chat.js', __FILE__), array('jquery', 'wp-i18n'), $pluginInfo['Version'], true);
    wp_set_script_translations('twx_chat_script','twx-chat', plugin_dir_path(__FILE__) . 'languages/');

	$opts = [
        'plugin_ver' => $pluginInfo['Version'],
		'ajax_url' =>  admin_url('admin-ajax.php'),
		'assets_path' => plugins_url('assets', __FILE__),
        'avatar_assets_path' => TWXChatUtil::getAvatarAssetsRootPath('baseurl'),
		'images_path' => plugins_url('assets/images', __FILE__),
		'mp3_output_path' => wp_upload_dir()['baseurl'].'/twx-chat/mp3',
		'google_drive_upload_path' => wp_upload_dir()['baseurl'].'/twx-chat/google_drive',
        'stream_handler' => plugins_url('stream_handler.php', __FILE__),
        'test_stream_server' => plugins_url('test_stream_server.php', __FILE__),
        'openai_tts_handler' => plugins_url('openai_tts_handler.php', __FILE__),
        'locale' => TWXChatUtil::getSiteLocale(),
        'nonce' => wp_create_nonce('twx_chat_nonce'),
	];

    if($avatar_bkg_url) $opts['avatar_background_url'] = $avatar_bkg_url;

    wp_localize_script('twx_chat_script', 'twxChat', $opts);

    // If Plus Plan
    if(TWXChatUtil::customerPlanAccess('plus')){
        wp_enqueue_script('twx_chat_plus_script', plugins_url('assets/js/twx-chat-plus.js', __FILE__), array('jquery', 'wp-i18n'), $pluginInfo['Version'], true);
        wp_set_script_translations('twx_chat_plus_script','twx-chat', plugin_dir_path(__FILE__) . 'languages/');
    }

    // If Pro Plan
    if(TWXChatUtil::customerPlanAccess('pro')){
        wp_enqueue_script('twx_chat_pro_script', plugins_url('assets/js/twx-chat-pro.js', __FILE__), array('jquery', 'wp-i18n'), $pluginInfo['Version'], true);
        wp_set_script_translations('twx_chat_pro_script','twx-chat', plugin_dir_path(__FILE__) . 'languages/');
    }

    if($is_avatar){
        wp_enqueue_style('twx_chat_css', plugins_url('assets/css/twx-chat-avatar.css', __FILE__));
    }else{
        wp_enqueue_style('twx_chat_css', plugins_url('assets/css/twx-chat.css', __FILE__));
    }

	// Custom inline CSS
    $custom_css = $options['custom_css'] ?? '';
    if (!empty($custom_css)) {
        // Enqueue a dummy style to attach the inline CSS to
        wp_register_style('twx_chat_custom_css', false); // False URL for a placeholder handle
        wp_enqueue_style('twx_chat_custom_css');

        // Add the inline CSS
        wp_add_inline_style('twx_chat_custom_css', $custom_css);
    }
	
    // Voice Synth classes
    wp_enqueue_script_module('twx_chat_script_voice_stt', plugins_url('assets/js/twx-voice-agent.js', __FILE__), array(), $pluginInfo['Version']);
    wp_set_script_translations('twx_chat_script_voice_stt','twx-chat', plugin_dir_path(__FILE__) . 'languages/');

    // Avatar Character scripts
    if ($is_avatar) {

        // AWS SDK file for TTS - from https://sdk.amazonaws.com/js/aws-sdk-2.645.0.min.js
        wp_enqueue_script('twx_chat_script_avatar_aws_sdk', plugins_url('assets/js/aws-sdk-2.645.0.min.js', __FILE__), array(), $pluginInfo['Version'], false);

        // Avatar HOST file (also contains BABYLON) - compiled from source in D:\dev\avatar\babylon-amazon-sumerian-hosts\packages\amazon-sumerian-hosts-babylon\test\integration_test\Babylon.js\
        wp_enqueue_script('twx_chat_script_avatar_babylon', plugins_url('assets/js/twxchatBabylon.js', __FILE__), array(), $pluginInfo['Version'], false);

        // Avatar Main js module file
        wp_enqueue_script_module('twx_chat_script_avatar_main', plugins_url('assets/js/twx-chat-avatar.js', __FILE__), array(), $pluginInfo['Version']);
        wp_set_script_translations('twx_chat_script_avatar_main','twx-chat', plugin_dir_path(__FILE__) . 'languages/');
    }

}
add_action('wp_enqueue_scripts', 'twxchat_enqueue_scripts');

function twxchat_enqueue_admin_scripts() {

    // Check if we're on twx-chat settings page
    $plugin_page = 'twx-chat-settings';
    if (!isset($_GET['page']) || $_GET['page'] !== $plugin_page) {
        return;
    }

    $pluginInfo = TWXChatUtil::getPluginInfo();

    wp_enqueue_style(
        'twx_chat_admin_css', // Handle for the stylesheet
        plugins_url('assets/css/twx-chat-admin.css', __FILE__), 
        [], // Dependencies, if any
        $pluginInfo['Version']
    );
	
	wp_enqueue_script(
		'twx_chat_admin_script',
		plugins_url('assets/js/twx-chat-admin.js', __FILE__),
		array('jquery', 'wp-i18n'), // dependencies
		$pluginInfo['Version'],
		true // load in the footer
	);

    // Allow translations for this script
    //wp_set_script_translations('twx_chat_admin_script', 'twx-chat');    
    wp_set_script_translations(
        'twx_chat_admin_script',
        'twx-chat',
        plugin_dir_path(__FILE__) . 'languages/'
    );

    // Needed for Polly Voices selection
    wp_enqueue_script('twx_chat_admin_script_aws_sdk', plugins_url('assets/js/aws-sdk-2.645.0.min.js', __FILE__), array(), $pluginInfo['Version'], false);

    if(TWXChatUtil::customerPlanAccess('plus')){
        $plus_admin_script = 'assets/js/twx-chat-admin-plus.js';
        if(file_exists(plugin_dir_path(__FILE__).$plus_admin_script)){
            wp_enqueue_script(
                'twx_chat_admin_plus_script',
                plugins_url($plus_admin_script, __FILE__),
                array('jquery', 'wp-i18n'), // dependencies
                $pluginInfo['Version'],
                true // load in the footer
            );
            wp_set_script_translations(
                'twx_chat_admin_plus_script',
                'twx-chat',
                plugin_dir_path(__FILE__) . 'languages/'
            );
        }
    }

    if(TWXChatUtil::customerPlanAccess('pro')){
        $plus_admin_script = 'assets/js/twx-chat-admin-pro.js';
        if(file_exists(plugin_dir_path(__FILE__).$plus_admin_script)){
            wp_enqueue_script(
                'twx_chat_admin_pro_script',
                plugins_url($plus_admin_script, __FILE__),
                array('jquery', 'wp-i18n'), // dependencies
                $pluginInfo['Version'],
                true // load in the footer
            );
            wp_set_script_translations(
                'twx_chat_admin_pro_script',
                'twx-chat',
                plugin_dir_path(__FILE__) . 'languages/'
            );
        }
    }
    
    $options = get_option('twx_chat_options');
    $avatars = TWXChatUtil::fetchChatbotAvatars(($options['twx_customer_key'] ?? ''));
    
    $avatars_local = [];
    $thumb_path_rel = "assets/images/avatar-thumbs/";
    $thumb_path_remote = TWXChatUtil::getTwxlabsUrl().'assets/avatar-assets/characters/images/';
    foreach($avatars as $key => $avatar){
        $thumb_file_name = $avatar['images']['thumb'] ?? '';
        if(empty($thumb_file_name)) continue;
        $thumb_src_exists = file_exists(plugin_dir_path(__FILE__).$thumb_path_rel.$thumb_file_name);
        if($thumb_src_exists){
            // Thumb exists locally, use this
            $thumb_src_url = plugin_dir_url(__FILE__).$thumb_path_rel.$thumb_file_name;
            $thumb_src_url = str_replace(site_url(), '', $thumb_src_url);
        }else{
            // Thumb must live remotely, use that
            $thumb_src_url = $thumb_path_remote.$thumb_file_name;
        }
        $avatars_local[$key] = ['thumb_src' => $thumb_src_url];
    }

	$opts = [
		'ajax_url' =>  admin_url('admin-ajax.php'),
		'images_path' => plugins_url('assets/images', __FILE__),
        'twxchat_support_base_url' => TWXChatUtil::getTwxlabsUrl(false),
        'avatars' => $avatars_local,
        'aws_cognito_identity_pool_id' => $options['aws_cognito_identity_pool_id'] ?? '',
        'nonce' => wp_create_nonce('twx_chat_nonce'),
        'all_chatbot_atts' => TWXChatUtil::getAllChatbotShortcodeAttributes(),
        'chatLogFilterPeriods' => [ __('Today','twx-chat')=>'Today',__('2 Days','twx-chat')=>'2 Days',__('1 Week','twx-chat')=>'1 Week',__('1 Month','twx-chat')=>'1 Month',__('3 Months','twx-chat')=>'3 Months',__('All Time','twx-chat')=>'All Time' ],
	];

    if(TWXChatUtil::customerPlanAccess('plus')){
        $opts['proxy_url'] = plugins_url('twx_proxy.php', __FILE__);
    }

    wp_localize_script('twx_chat_admin_script', 'twxChat', $opts);
		
}
add_action('admin_enqueue_scripts', 'twxchat_enqueue_admin_scripts');

function twx_get_chatbot_minimized_html($atts){

    // Floating chatbot

    $starting_comments = $atts['starting_comments'] ?? 'How Can I Help?';
    $floating_title = $atts['floating_title'] ?? $starting_comments;
    $header = '';
    $main_container = '';
    $top_style = '';
    $avatar_img_min = isset($atts['floating_image_url']) && !empty($atts['floating_image_url']) ? '<img id="twx-chat-avatar-img-min" alt="Chatbot Image" src="'. $atts['floating_image_url'] . '">' : '';

    if($atts['type'] === 'avatar'){
        $main_container = twx_get_avatar_container_html($atts);
    }else{
        $starting_comments_max_div = isset($atts['starting_comments']) && !empty($atts['starting_comments']) ? '<div class="bot-greeting wp-block">'.$atts['starting_comments'].'</div>' : '';
        $main_container = twx_get_chatbot_container_html($atts,false);
        $header = '<div id="twx-chat-floating-header"><button id="minimize-chatbot" class="chat-toggle-button wp-element-button">-</button>'.$starting_comments_max_div.'</div>';
        $top_style = ' style="display:none;"';
    }

    $minimized_container = '
    <div id="twx-chat-minimized">
        '.$avatar_img_min.'
        <div id="twx-chat-minimized-inner" style="display: flex;">
            <div class="chatbot-greeting">'.$floating_title.'</div>
            <button id="expand-chatbot" class="chat-toggle-button wp-element-button">+</button>
        </div>    
    </div>';

    return $minimized_container.
        '<div id="twx-chat-floating"'.$top_style.'">
            '.$header.$main_container.'
        </div>';

}

function twx_get_avatar_container_html($atts){

    $options = get_option('twx_chat_options');
    $starting_comments = $atts['starting_comments'] ?? __('How Can I Help?','twx-chat');
    $floating_title = $atts['floating_title'] ?? $starting_comments;
    $load_screen_style = isset($atts['avatar_image_url']) && !empty($atts['avatar_image_url']) ? "background-image: url('".$atts['avatar_image_url']."')" : '';
    $icons_path = esc_url(plugins_url('assets/images/tabler-icons',__FILE__));
    $minimize_button = $atts['style'] === 'floating' ? '<button id="floating-avatar-minimize" class="avatar-control-btn"><img src="'.$icons_path.'/minus.svg" alt="Minimize"></button>' : '';
    $show_watermark = $options['show_watermark'] ?? '1';
    $watermark = $show_watermark ? '<div id="twx-chat-watermark"><a href="'.TWXChatUtil::getTwxlabsUrl().'" target="_blank">Powered by TWXLabs.au</a></div>' : '';
    $startBtnText = esc_html__('Start','twx-chat');
    if(isset($atts['thread_id']) && !empty($atts['thread_id'])){
        $chatlogEntries = TWXChatLog::getChatLogEntriesByThreadId($atts['thread_id'], 2, $atts['id']);
        if(!empty($chatlogEntries)){
            $startBtnText = esc_html__('Continue','twx-chat');
            if(!empty(trim($atts['continue_comments'] ?? ''))){
                $starting_comments = $atts['continue_comments'];
                $floating_title = $atts['continue_comments'];
            }
        }
    }

    $html = '
    <div id="avatar-container">

        <!--Loading screen-->
        <div id="avatar-loadScreen" style="'.$load_screen_style.'">
            '.$minimize_button.'
            <div id="avatar-start-text"></div>
            <div id="avatar-loader" class="hidden"></div>
            <div id="avatar-loader-progress" class="hidden"></div>
        </div>

        <!-- control buttons -->
        <button id="avatar-status"></button>
        <div id="avatar-greeting-loadScreen">'.($atts['style'] === 'embedded' ? $starting_comments : $floating_title).'</div>
        <div id="user-utterance"></div>
        <button id="avatar-greeting-button">'.$startBtnText.'</button>
        <button id="avatar-minimize" class="avatar-control-btn"><img src="'.$icons_path.'/arrows-minimize.svg" alt="'.__('Minimize','twx-chat').'"></button>
        <button id="avatar-maximize" class="avatar-control-btn"><img src="'.$icons_path.'/arrows-maximize.svg" alt="'.__('Maximize','twx-chat').'"></button>
        <button id="avatar-exit" class="avatar-control-btn"><img src="'.$icons_path.'/square-x.svg" alt="'.__('Exit','twx-chat').'"></button>
        <button id="avatar-microphone-on" class="avatar-control-btn microphone"><img src="'.$icons_path.'/microphone.svg" alt="'.__('Mic ON','twx-chat').'"></button>
        <button id="avatar-microphone-off" class="avatar-control-btn microphone"><img src="'.$icons_path.'/microphone-off.svg" alt="'.__('Mic OFF','twx-chat').'"></button>
        <button id="avatar-play" class="avatar-control-btn"><img src="'.$icons_path.'/player-play.svg" alt="'.__('Play','twx-chat').'"></button>
        <button id="avatar-pause" class="avatar-control-btn"><img src="'.$icons_path.'/player-pause.svg" alt="'.__('Pause','twx-chat').'"></button>
        <button id="avatar-camera-on" class="avatar-control-btn camera"><img src="'.$icons_path.'/camera.svg" alt="'.__('Camera ON','twx-chat').'"></button>
        <button id="avatar-camera-off" class="avatar-control-btn camera"><img src="'.$icons_path.'/camera-off.svg" alt="'.__('Camera OFF','twx-chat').'"></button>
        <button id="avatar-conversation-on" class="avatar-control-btn conversation"><img src="'.$icons_path.'/message.svg" alt="ConvON"></button>
        <button id="avatar-conversation-off" class="avatar-control-btn conversation"><img src="'.$icons_path.'/message-off.svg" alt="ConvOFF"></button>
        <button id="avatar-debug-console-on" class="avatar-control-btn debug-console"><img src="'.$icons_path.'/script.svg" alt="ConsoleON"></button>
        <button id="avatar-debug-console-off" class="avatar-control-btn debug-console"><img src="'.$icons_path.'/script-x.svg" alt="ConsoleOFF"></button>

        <!-- Conversation widget -->
        <div id="avatar-conversation-widget" class="chatbot-container">
            <div id="avatar-greeting-conversation">'.$atts['starting_comments'].'</div>
            <div id="avatar-conversation-content" class="chat-messages">
            '.twxchat_common_questions_html($atts['common_questions']).'
            </div>
            <div id="avatar-conversation-footer">
                <input type="text" id="user-input" class="chat-input" placeholder="'.__('Enter your query/response here','twx-chat').'..." disabled>
				<button id="avatar-user-send-btn" class="wp-element-button">'.__('Send','twx-chat').'</button>
            </div>
        </div>

        <!-- Flashcards widget -->
        <div id="avatar-flashcards-widget"></div>

        <!-- Debug console widget -->
        <div id="avatar-debug-console-widget" class="chatbot-container">
            <div id="avatar-debug-console-content" class="chat-messages">
            </div>
        </div>

        <!-- scene canvas will be appended here after load -->

    </div>
    '.$watermark.'
	<!-- Sources -->
	<div id="sourcesContainer" class="sources-container" style="display: none;"></div>
    ';
    
    return $html;
}

function twx_get_chatbot_container_html($atts, $includeStartingComments = true) {
	
	extract($atts); // creates vars from keys in array
	$starting_comments = isset($starting_comments) ? $starting_comments : __('How Can I Help?','twx-chat');
	$options = get_option('twx_chat_options');
	$apiKey = $options['openai_apikey'];
	
	$microphone_img_url = esc_url(plugins_url('assets/images/microphone.png', __FILE__));

    $chatbot_container_style = '';

    $cancel_stream_btn_svg = '
        <svg width="30" height="30" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <!-- Black background circle -->
        <circle cx="50" cy="50" r="48" fill="black"/>
        <!-- White square (Stop button) -->
        <rect x="25" y="25" width="50" height="50" fill="white"/>
        </svg>
        ';
	
	return '
    <div class="chatbot-container wp-block"'.$chatbot_container_style.'>
		'.twxchat_debugInfo($apiKey, $atts).'
        <!-- Chatbot body -->
        <div class="chatbot-body wp-block">
            '.($includeStartingComments ? '<div class="bot-greeting wp-block">'.$starting_comments.'</div>' : '').'
			'.twxchat_common_questions_html($common_questions).'
            <!-- Previous chat messages -->
            <div class="chat-messages wp-block">
                <!-- Previous messages here -->
            </div>
        </div>
        <!-- Chatbot footer -->
        <div class="chatbot-footer">
            <!-- Chat input field -->
            <input type="text" id="user-input" class="chat-input" placeholder="'.__('Enter your query/response here','twx-chat').'..." disabled>
			<div class="footer-buttons">
				<!-- Send button -->
				<button class="send-button wp-element-button" disabled>Send</button>
				<!-- Voice Input button -->
				<button class="voice-input-button wp-element-button"><img src="'.$microphone_img_url.'" alt="microphone" /></button>
				<!-- Cancel Stream button -->
				<button class="cancel-stream-button wp-element-button" disabled>'.$cancel_stream_btn_svg.'</button>
			</div>
        </div>
    </div>
	<!-- Voice Input Dialog -->
	<div id="voice-input-dialog" style="display: none;">
		<div class="voice-input-dialog-content">
			<div class="voice-input-background-effect"></div>
			<div class="voice-input-listening">'.__('Listening...','twx-chat').'</div>
			<button class="cancel-voice-input">'.__('Cancel','twx-chat').'</button>
		</div>
	</div>
	<!-- TTS/Custom Voice Player -->
	<div id="twx-chat-custom-voice-player-container"></div>
	<!-- Popup -->
	<div id="productPopup" class="product-popup" style="display: none;">
		<div class="popup-content">
			<span class="close-button" onclick="closePopup()">&times;</span>
			<img id="productImage" src="" alt="Product Image">
			<h2 id="productDescription"></h2>
			<p id="productPrice"></p>
			<a id="productLink" href="" target="_blank">'.__('View Product','twx-chat').'</a>
		</div>
	</div>
	<!-- Sources -->
	<div id="sourcesContainer" class="sources-container" style="display: none;"></div>
	';
}

function twxchat_common_questions_html($questions) {

	if(empty($questions)) return '';
	
	if(!is_array($questions)) $questions = explode(',',$questions);

	$html = '<div class="common-questions"><h3>'.__('Common Questions','twx-chat').'</h3><ul>';
	
	foreach($questions as $q){
		$html .= '<li><button class="wp-element-button">'.trim($q).'</button></li>';
	}
	
	$html .= '</ul></div>';
	
	return $html;
}

function twxchat_debugInfo($apiKey, $atts){

    extract($atts);
    if(!isset($twx_debug) || $twx_debug !== 'true') return '';
	
	$debugInfoX['API Key'] = TWXChatUtil::obscureApiKey($apiKey);
	$debugInfoX['Thread ID'] = '<span id="twx-debug-threadId">'.$thread_id.'</span>';
	
	if(!empty($assistant_id)){
		
		$asstObj = new TWXChatOpenAIAssistant($apiKey, $assistant_id);
		$assistant = $asstObj->getAssistant();
		$debugInfoX['Assistant ID'] = $assistant_id;
		$debugInfoX['Assistant Name'] = $assistant['name'];
		$debugInfoX['Model'] = '<span id="twx-debug-model">'.$assistant['model'].'</span>'; // this may change if tokens/budget exceeded etc
		$debugInfoX['Temperature'] = $assistant['temperature'];

		$asstFiles = $asstObj->getAssistantFiles();

		if(!empty($asstFiles) && !isset($asstFiles['error'])){
			$i=1;
			foreach($asstFiles as $asstFile){
				$file = $asstObj->getFile($asstFile['id']);
				$debugInfoX["Attached File $i"] = $file['filename'].' ('.number_format($file['bytes']/1024,2).' Kb, created at '.$file['created_at'].')';
				$i++;
			}
		}
	}else{
		// Assistant Less
		$debugInfoX['Model'] = '<span id="twx-debug-model">'.($atts['llm_model'] ?? '').'</span>'; // this will be populated after query
		$debugInfoX['Min.Similarity'] = $min_similarity.'%';
	}
	
	if(!empty($extra_instructions)) $debugInfoX['Extra Instructions'] = $extra_instructions;
	if(!empty($allow_feedback)) $debugInfoX['Allow Feedback'] = $allow_feedback;
	if(!empty($flashcards)) $debugInfoX['Flashcards'] = $flashcards;
	if(!empty($stream)) $debugInfoX['Streaming'] = $stream;
    if(!empty($kb_source)) $debugInfoX['KB source'] = $kb_source .' ('.twxchat_get_source_count($kb_source).' sources)';
	
	$content = 'Debug Info:<br/>';
	foreach($debugInfoX as $key => $value){
		$content .= "<li>$key: <b>$value</b></li>";
	}
	
	return '<div class="twx-debug">'.$content.'</div>';
}

function twxchat_get_source_count($source) {
    global $wpdb;

    // Escape the source string for LIKE query and prepare the statement
    $like_source = '%' . $wpdb->esc_like($source) . '%';

    $prepared_query = $wpdb->prepare(
        "SELECT COUNT(*) FROM {$wpdb->prefix}twxchat_vectors WHERE source LIKE %s",
        $like_source
    );

    // Get the count result
    $count = $wpdb->get_var($prepared_query);

    return (int) $count; // Cast to int to ensure consistent return type
}

add_action('update_option_twx_chat_options', function($old_options, $new_options, $option) {
    
	// Monitor changes to TWX-Chat options
    if ($option === 'twx_chat_options') {

        // Site Crawler

        $oldSchedule = isset($old_options['site_crawler_schedule']) ? $old_options['site_crawler_schedule'] : '';
        $newSchedule = isset($new_options['site_crawler_schedule']) ? $new_options['site_crawler_schedule'] : '';

        $oldTime = isset($old_options['site_crawler_schedule_time']) ? $old_options['site_crawler_schedule_time'] : '';
        $newTime = isset($new_options['site_crawler_schedule_time']) ? $new_options['site_crawler_schedule_time'] : '';

        $oldDOW = isset($old_options['site_crawler_schedule_dayofweek']) ? $old_options['site_crawler_schedule_dayofweek'] : '';
        $newDOW = isset($new_options['site_crawler_schedule_dayofweek']) ? $new_options['site_crawler_schedule_dayofweek'] : '';

        if ($oldSchedule !== $newSchedule || $oldTime !== $newTime || $oldDOW !== $newDOW) twxchat_handle_change_site_crawler_schedule($newSchedule, $newTime, $newDOW);

        // Reporting

        $oldReportsChatLogSummaryEmailSchedule = isset($old_options['reports_chatlog_summary_email_schedule']) ? $old_options['reports_chatlog_summary_email_schedule'] : '';
        $newReportsChatlogSummaryEmailSchedule = isset($new_options['reports_chatlog_summary_email_schedule']) ? $new_options['reports_chatlog_summary_email_schedule'] : '';

        if ($oldReportsChatLogSummaryEmailSchedule !== $newReportsChatlogSummaryEmailSchedule) twxchat_handle_change_reports_chatlog_summary_email_schedule($newReportsChatlogSummaryEmailSchedule);

    }
}, 10, 3);

function twxchat_handle_change_site_crawler_schedule($new_schedule, $new_time, $new_dayfoweek = '') {

    // Site Crawler schedule change

    $timezone_string = get_option('timezone_string');
    if (empty($timezone_string)) $timezone_string = 'UTC';
    
	switch($new_schedule){
        case '':
            twxchat_clear_all_site_crawler_scheduled_jobs();
            break;
		case 'Daily':
			// Schedule a daily event
            TWXChatUtil::adminLog('twxchat_handle_change_site_crawler_schedule:: Schedule a daily event');
            twxchat_clear_all_site_crawler_scheduled_jobs();
            $daily_time = $new_time;
            $datetime = new DateTime('today ' . $daily_time, new DateTimeZone($timezone_string));
            $datetime->setTimezone(new DateTimeZone('UTC')); // Convert to UTC
            $timestamp = $datetime->getTimestamp();
            wp_schedule_event($timestamp, 'daily', 'twx_chat_site_crawler_daily_event');
			break;
		case 'Weekly':
			// Schedule a weekly event
            TWXChatUtil::adminLog('twx_chat_handle_change_site_crawler_schedule:: Schedule a weekly event');
            twxchat_clear_all_site_crawler_scheduled_jobs();
            $weekly_day = $new_dayfoweek;
            $weekly_time = $new_time;
            $datetime = new DateTime('next ' . $weekly_day . ' ' . $weekly_time, new DateTimeZone($timezone_string));
            $datetime->setTimezone(new DateTimeZone('UTC')); // Convert to UTC
            $timestamp = $datetime->getTimestamp();
            wp_schedule_event($timestamp, 'weekly', 'twx_chat_site_crawler_weekly_event');
			break;
	}
	
}

function twxchat_restore_site_crawler_scheduled_jobs(){

    // in case site crawler jobs get deleted on plugin update etc, lets restore them

    $options = get_option('twx_chat_options');
    $siteCrawlerSchedule = $options['site_crawler_schedule'] ?? '';
    if(empty($siteCrawlerSchedule)) return;

    $scheduledTime = $options['site_crawler_schedule_time'] ?? '';
    if(empty($scheduledTime)) return;

    if($siteCrawlerSchedule === 'Daily'){
        $hook = 'twx_chat_site_crawler_daily_event';
        if(wp_next_scheduled($hook)) return;
        twxchat_handle_change_site_crawler_schedule($siteCrawlerSchedule, $scheduledTime);

    }else if($siteCrawlerSchedule === 'Weekly'){
        $hook = 'twx_chat_site_crawler_weekly_event';
        if(wp_next_scheduled($hook)) return;
        $scheduledDOW = $options['site_crawler_schedule_dayofweek'] ?? '';
        if(empty($scheduledDOW)) return;

        twxchat_handle_change_site_crawler_schedule($siteCrawlerSchedule, $scheduledTime, $scheduledDOW);
    }
}

function twxchat_clear_all_site_crawler_scheduled_jobs(){

    TWXChatUtil::clearScheduledCronJob('twx_chat_site_crawler_daily_event');

    TWXChatUtil::clearScheduledCronJob('twx_chat_site_crawler_weekly_event');
}

add_action('twx_chat_site_crawler_daily_event', 'twxchat_run_site_crawl_daily');
function twxchat_run_site_crawl_daily() {
    TWXChatSpider::runSiteCrawl('Daily');
}

add_action('twx_chat_site_crawler_weekly_event', 'twxchat_run_site_crawl_weekly');
function twxchat_run_site_crawl_weekly() {
    TWXChatSpider::runSiteCrawl('Weekly');
}

function twxchat_handle_change_reports_chatlog_summary_email_schedule($new_schedule){

    // Chatlog Summary Email Schedule changed

    $timezone_string = get_option('timezone_string');
    if (empty($timezone_string)) $timezone_string = 'UTC';
    
	switch($new_schedule){
        case '':
            twxchat_clear_all_reporting_jobs();
            break;
		case 'Daily':
			// Schedule a daily event
            TWXChatUtil::adminLog('twxchat_handle_change_reports_chatlog_summary_email_schedule:: Schedule a daily event');
            twxchat_clear_all_reporting_jobs();
            $daily_time = '11:59 PM';
            $datetime = new DateTime('today ' . $daily_time, new DateTimeZone($timezone_string));
            $datetime->setTimezone(new DateTimeZone('UTC')); // Convert to UTC
            $timestamp = $datetime->getTimestamp();
            wp_schedule_event($timestamp, 'daily', 'twx_chat_reports_chatlog_summary_email_daily_event');
			break;
		case 'Weekly':
			// Schedule a weekly event
            TWXChatUtil::adminLog('twxchat_handle_change_reports_chatlog_summary_email_schedule:: Schedule a weekly event');
            twxchat_clear_all_reporting_jobs();
            $weekly_day = 'Sunday';
            $weekly_time = '11:59 PM';
            $datetime = new DateTime('next ' . $weekly_day . ' ' . $weekly_time, new DateTimeZone($timezone_string));
            $datetime->setTimezone(new DateTimeZone('UTC')); // Convert to UTC
            $timestamp = $datetime->getTimestamp();
            wp_schedule_event($timestamp, 'weekly', 'twx_chat_reports_chatlog_summary_email_weekly_event');
			break;
	}

}

function twxchat_restore_reporting_scheduled_jobs(){

    // in case reporting jobs get deleted on plugin update etc, lets restore them

    $options = get_option('twx_chat_options');
    $schedule = $options['reports_chatlog_summary_email_schedule'] ?? '';
    if(empty($schedule)) return;

    if($schedule === 'Daily'){
        $hook = 'twx_chat_reports_chatlog_summary_email_daily_event';
        if(wp_next_scheduled($hook)) return;
        twxchat_handle_change_reports_chatlog_summary_email_schedule($schedule);

    }else if($schedule === 'Weekly'){
        $hook = 'twx_chat_reports_chatlog_summary_email_weekly_event';
        if(wp_next_scheduled($hook)) return;
        twxchat_handle_change_reports_chatlog_summary_email_schedule($schedule);
    }

}

function twxchat_clear_all_reporting_jobs(){

    TWXChatUtil::clearScheduledCronJob('twx_chat_reports_chatlog_summary_email_daily_event');

    TWXChatUtil::clearScheduledCronJob('twx_chat_reports_chatlog_summary_email_weekly_event');
}

add_action('twx_chat_reports_chatlog_summary_email_daily_event', 'twxchat_run_reports_chatlog_summary_daily');
function twxchat_run_reports_chatlog_summary_daily() {
    twxchat_run_reports_chatlog_summary_email('Daily');
}

add_action('twx_chat_reports_chatlog_summary_email_weekly_event', 'twxchat_run_reports_chatlog_summary_weekly');
function twxchat_run_reports_chatlog_summary_weekly() {
    twxchat_run_reports_chatlog_summary_email('Weekly');
}

function twxchat_run_reports_chatlog_summary_email($schedule){

    // run chatlog summary report now

    TWXChatUtil::adminLog("twxchat_run_reports_chatlog_summary_email:: start $schedule run");

    $toEmail = get_option('admin_email','');
    if(empty($toEmail)){
        TWXChatUtil::adminLog("twxchat_run_reports_chatlog_summary_email:: missing Admin Email address. Aborting");
        return;
    }

    if($schedule !== 'Daily' && $schedule !== 'Weekly'){
        TWXChatUtil::adminLog("twxchat_run_reports_chatlog_summary_email:: missing/invalid schedule: $schedule");
        return;
    }

    try {
        
        $options = get_option('twx_chat_options');
        $openaiApiKey = $options['openai_apikey'] ?? '';
        if(empty($openaiApiKey)){
            TWXChatUtil::adminLog("twxchat_run_reports_chatlog_summary_email:: missing OpenAI API Key. Aborting");
            return;
        }

        $filters = [];
        $filters['wpUser'] = 'visitor';
        $period = ($schedule === 'Daily') ? '1 Day' : '1 Week';

        $entries = TWXChatLog::getChatLogEntries($period, $filters);

        // Choose columns we want to summarise
        $colsAll = TWXChatLog::getChatLogColumns();
        $cols = [];
        $cols['userInput'] = $colsAll['userInput'];
        $cols['botResponse'] = $colsAll['botResponse'];
        $cols['isVoiceInput'] = $colsAll['isVoiceInput'];
        $cols['userHyperlinkClicks'] = $colsAll['userHyperlinkClicks'];

        $parsedEntries = var_export(TWXChatPlus::getCSVArray($entries, $cols), true);
        $styleTag  = 'style'; // Cant use wp_register_script() here as it is an email body not webpage
        $sitename = get_bloginfo('name');

        $html = "
            <html>
            <head>
                <$styleTag>
                    body { font-family: Arial, sans-serif; line-height: 1.6; }
                    .summary { background: #f8f8f8; padding: 10px; border-left: 4px solid #0073aa; }
                </$styleTag>
            </head>
            <body>
                <h2>".esc_html($schedule)." ".__('Chat Log Summary','twx-chat')."</h2>
                <p><strong>WordPress Site:</strong> $sitename</p>
                <p><strong>Plugin:</strong> TWX-Chat</p>
                <p><strong># ".__('Visitor Interactions','twx-chat').":</strong> ".esc_html(count($entries))."</p>
        ";

        if(count($entries) > 0){

            //$completionsObject = new TWXChatOpenAICompletions($openaiApiKey);
            $openai = new TWXChatOpenAIResponses($openaiApiKey);
            $localeInstr = ' Respond in '.TWXChatUtil::getSiteLocale().' language.';
            $messages = [
                ['role' => 'user','content' => "Summarise the following visitor chat log entries in HTML format. Do not quote any actual entries. Do not include a header. Keep results concise and use dot points where appropriate. Also mention any common themes and insights emerging from the data. $localeInstr\nEntries:\n".$parsedEntries],
            ];

            $queryResponse = $openai->query($messages, TWXCHAT_DEFAULT_MAX_OUTPUT_TOKENS, false);
            $answer = $queryResponse['answer'] ?? '';
            // Strip outer HTML and get body
            if (preg_match('/<body[^>]*>(.*?)<\/body>/is', $answer, $matches)) {
                $answer = trim($matches[1]);
            }
            $html .= '<div class="summary">'.$answer.'</div>';
        }

        $admin_url = admin_url('options-general.php?page=twx-chat-settings');
        $chatlog_settings_url = wp_login_url($admin_url . '#chatlog');
        //$chatlog_settings_url = admin_url( 'options-general.php?page=twx-chat-settings#chatlog' );
        $reports_settings_url = wp_login_url($admin_url . '#reports');
        //$reports_settings_url = admin_url( 'options-general.php?page=twx-chat-settings#reports' ); 

        if(!class_exists('TWXChatPlus') || !TWXChatUtil::customerPlanAccess('plus') ){
            $html .= '<p>'.__('If you would like to see actual visitor conversations with TWX-Chat for this period or any other period','twx-chat').', <a href="'.esc_attr(TWXChatUtil::getTwxlabsUrl().'download/plus').'" target="_blank">'.__('Updgrade Now','twx-chat').'</a> '.__('to one of our paid plans.','twx-chat').'</p>';
        }else{
            $html .= '<p>'.__('To see all actual visitor conversations with TWX-Chat for this period or any other period, visit the Chat Log in','twx-chat').' <a href="'.esc_attr($chatlog_settings_url).'" target="_blank">TWX-Chat '.__('Settings','twx-chat').'</a>.</p>';
        }

        $html .= '<p>'.__('You are receiving this email because you have selected to receive','twx-chat').' '.$schedule.' '.__('Chat Log summary emails in your TWX-Chat Reports','twx-chat').' <a href="'.esc_attr($reports_settings_url).'" target="_blank">'.__('settings page','twx-chat').'</a>. '.__('You can change these settings at any time.','twx-chat').'</p>';

        $html .= TWXChatUtil::getEmailSigHtml()."</body></html>";

        list($success, $errors) = TWXChatUtil::sendEmail(
            $toEmail,
            '[TWX-Chat] '.__('Your','twx-chat').' '.$schedule.' '.__('Chat Log Summary as requested','twx-chat'),
            $html
        );

        if(!$success){
            TWXChatUtil::adminLog("twxchat_run_reports_chatlog_summary_email:: email errors: ".var_export($errors,true));
        }

    }catch(Exception $ex){
        TWXChatUtil::adminLog("twxchat_run_reports_chatlog_summary_email:: error: ".$ex->getMessage());
    }

}

add_action('admin_notices', 'twxchat_chatlog_threshold_warning_notice');
function twxchat_chatlog_threshold_warning_notice() {

    //global $wpdb;
    global $twxchat_max_chatlog_rows;

    $row_count = TWXChatLog::getNumEntries();
    $warning_threshold = intval($twxchat_max_chatlog_rows * 0.8);
    $settings_url = admin_url('options-general.php?page=twx-chat-settings#chatlog'); //admin.php?page=your_plugin_settings');
    if ($row_count >= $warning_threshold) {
        if($row_count < $twxchat_max_chatlog_rows){
            // Approaching
            echo '<div class="notice notice-warning is-dismissible">';
            echo '<p><strong>TWX-Chat Warning:</strong> '.__('Your Chat Log is approaching its limit. Download your Chat Log','twx-chat').' <a href="' . esc_url($settings_url) . '">'.__('here','twx-chat').'</a> '.__('before automatic deletion occurs at','twx-chat').' '. esc_html($twxchat_max_chatlog_rows) . ' '.__('entries','twx-chat').'</p>';
            echo '</div>';
        }else{
            // Exceeded
            echo '<div class="notice notice-warning is-dismissible">';
            echo '<p><strong>TWX-Chat Warning:</strong> '.__('Your Chat Log has exceeded its entry limit of','twx-chat').' '.esc_html($twxchat_max_chatlog_rows).' '.__('entries. Daily purges of old entries will now occur. Download your Chat Log','twx-chat').' <a href="' . esc_url($settings_url) . '">'.__('here','twx-chat').'</a>.</p>';
            echo '</div>';
        }
    }
}

add_action('twx_chat_cleanup_chatlog_event', 'twxchat_cleanup_chatlog_table');
function twxchat_cleanup_chatlog_table() {

    // Cleans up old rows in Chat Log table or warns if approaching limit. Sends emails to Admin.
    //TWXChatUtil::adminLog("twxchat_cleanup_chatlog_table:: start");

    global $wpdb;
    global $twxchat_max_chatlog_rows;

    $table_name = $wpdb->prefix . 'twxchat_chatlog';
    $row_count = TWXChatLog::getNumEntries();
    
    $warning_threshold = intval($twxchat_max_chatlog_rows * 0.8);

    // Check if we need to warn with an email or start purging
    if ($row_count >= $warning_threshold) {
        if ($row_count < $twxchat_max_chatlog_rows) {
            twxchat_send_chatlog_warning_email($row_count);
        } else {
            // Purge
            $limit = absint($twxchat_max_chatlog_rows); // Ensure it's an integer
            $sql = "
                DELETE FROM `$table_name`
                WHERE id NOT IN (
                    SELECT id FROM (
                        SELECT id FROM `$table_name`
                        ORDER BY id DESC
                        LIMIT %d
                    ) AS t
                )
            ";
            $wpdb->query( $wpdb->prepare( $sql, $limit ) );  // WP Plugin checker says: WordPress.DB.PreparedSQL.NotPrepared. But it is being prepared so??

            $deleted_rows = $wpdb->rows_affected;

            twxchat_send_chatlog_deleted_email($deleted_rows);
            TWXChatUtil::adminLog("Scheduled chatlog cleanup deleted $deleted_rows old entries.");
        }
    }

}

// External callable endpoint: Query Chatbot
add_action('rest_api_init', function () {
    register_rest_route('twxchat/v1', '/query-chatbot', [
        'methods'             => 'POST',
        'callback'            => 'twxchat_handle_rest_query_chatbot',
        'permission_callback' => 'twxchat_rest_permission_check_query_chatbot',
        'args'                => [
            'query' => [
                'required' => true, 
                'type'     => 'string',
            ],
            'chatbot_id' => [
                'required' => true, 
                'type'     => 'string',
            ],
        ],
    ]);
});

function twxchat_handle_rest_query_chatbot(WP_REST_Request $request) {

    if(!TWXChatUtil::customerPlanAccess('pro') || !method_exists('TWXChatPro', 'REST_query_chatbot')){
        return new WP_Error(
            'rest_forbidden',
            __( 'Permission denied. Only Pro Plan owners have access to this service', 'twx-chat' ),
            [ 'status' => 401 ]
        );
    } 

    $result = TWXChatPro::REST_query_chatbot($request);

    return new WP_REST_Response($result, 200);
}

function twxchat_rest_permission_check_query_chatbot(WP_REST_Request $request) {

    if(!TWXChatUtil::customerPlanAccess('pro') || !method_exists('TWXChatPro', 'REST_query_chatbot_permission_check')){

        return new WP_Error(
            'rest_forbidden',
            __( 'Permission denied. Only Pro Plan owners have access to this service', 'twx-chat' ),
            [ 'status' => 401 ]
        );
        
        //return false;
    }
    return TWXChatPro::REST_query_chatbot_permission_check($request);
}


function twxchat_send_chatlog_warning_email($row_count){

    TWXChatUtil::adminLog("twxchat_send_chatlog_warning_email:: start");

    global $twxchat_max_chatlog_rows;
    $admin_email = get_option('admin_email');
    
    if (empty($admin_email)) {
        return;
    }

    $sitename = get_bloginfo('name');

    $subject = "[TWX-Chat] Warning: TWX-Chat Wordpress Plugin Chat Log Approaching Limit ($sitename)";
    $styleTag  = 'style'; // Cant use wp_register_script() here as it is an email body not webpage

    $message = "
        <html>
        <head>
            <$styleTag>
                body { font-family: Arial, sans-serif; line-height: 1.6; }
                .warning { color: red; font-weight: bold; }
                .recommend { background: #f8f8f8; padding: 10px; border-left: 4px solid #0073aa; }
            </$styleTag>
        </head>
        <body>
            <p><strong>WordPress Site:</strong> $sitename</p>
            <p><strong>Plugin:</strong> TWX-Chat</p>
            <p class='warning'>Warning: Your Chat Log has reached $row_count entries 
            (over 80% of the $twxchat_max_chatlog_rows entry limit). Old entries will be deleted within 24 hours.</p>
            
            <div class='recommend'>
                <p><strong>Recommended Action: Archive and Delete Chat old Log entries</strong></p>
                <ol>
                    <li>Go to the site's <strong>WordPress Admin</strong> console.</li>
                    <li>Navigate to <strong>Settings &gt; TWX-Chat &gt; Chat Log (tab)</strong>.</li>
                    <li>Select a <strong>Period</strong> and then click the <strong>'Download the Chatlog CSV'</strong> button. Perform the Download/Save file.</li>
                    <li>Click the <strong>Delete old Chat Log entries</strong> button and enter the number of entries to keep in the system. Click OK.</li>
                    <li><strong>We recommend keeping less than 50% of the maximum entries ($twxchat_max_chatlog_rows)</strong> to avoid frequent warning emails/alerts.</li>
                </ol>
            </div>
            ".TWXChatUtil::getEmailSigHtml()."</body></html>";

    list($success, $errors) = TWXChatUtil::sendEmail($admin_email, $subject, $message );
    if($success){
        TWXChatUtil::adminLog("twxchat_send_chatlog_warning_email:: Chat log warning email successfully sent to $admin_email.");
    }else{
        TWXChatUtil::adminLog("twxchat_send_chatlog_warning_email:: Chat log warning email errors: ".var_export($errors,true));
    }

}

function twxchat_send_chatlog_deleted_email($row_count){

    TWXChatUtil::adminLog("twxchat_send_chatlog_deleted_email:: start");

    global $twxchat_max_chatlog_rows;
    $admin_email = get_option('admin_email');
    
    if (empty($admin_email)) {
        return;
    }

    $sitename = get_bloginfo('name');

    $subject = "[TWX-Chat] FYI: TWX-Chat Wordpress Plugin has deleted $row_count old Chat Log entries ($sitename)";
    $styleTag  = 'style'; // Cant use wp_register_script() here as it is an email body not webpage

    $message = "
        <html>
        <head>
            <$styleTag>
                body { font-family: Arial, sans-serif; line-height: 1.6; }
                .warning { color: red; font-weight: bold; }
                .recommend { background: #f8f8f8; padding: 10px; border-left: 4px solid #0073aa; }
            </$styleTag>
        </head>
        <body>
            <p><strong>WordPress Site:</strong> $sitename</p>
            <p><strong>Plugin:</strong> TWX-Chat</p>
            <p class='warning'>Note: $row_count old entries have been automatically deleted from your Chat Log. 
            This has likely ocurred due to the Chat Log exceeding maximum size of $twxchat_max_chatlog_rows entries.</p>
            
            <div class='recommend'>
                <p><strong>Recommended Action: Archive and Delete old Chat Log entries</strong></p>
                <ol>
                    <li>Go to the site's <strong>WordPress Admin</strong> console.</li>
                    <li>Navigate to <strong>Settings &gt; TWX-Chat &gt; Chat Log (tab)</strong>.</li>
                    <li>Select a <strong>Period</strong> and then click the <strong>'Download the Chatlog CSV'</strong> button. Perform the Download/Save file.</li>
                    <li>Click the <strong>Delete old Chat Log entries</strong> button and enter the number of entries to keep in the system. Click OK.</li>
                    <li><strong>We recommend keeping less than 50% of the maximum entries ($twxchat_max_chatlog_rows)</strong> to avoid frequent warning emails/alerts.</li>
                </ol>
            </div>
            ".TWXChatUtil::getEmailSigHtml()."</body></html>";

    list($success, $errors) = TWXChatUtil::sendEmail($admin_email, $subject, $message );
    if($success){
        TWXChatUtil::adminLog("twxchat_send_chatlog_deleted_email:: Chat log deleted email successfully sent to $admin_email.");
    }else{
        TWXChatUtil::adminLog("twxchat_send_chatlog_deleted_email:: Chat log deleted email errors: ".var_export($errors,true));
    }
}

add_action( 'wp_mail_failed', function( $wp_error ) {
    TWXChatUtil::adminLog( 'WP Mail failed: ' . $wp_error->get_error_message() );
});


?>
