<?php
/*
Plugin Name: BluPass
Plugin URI: https://bluishgen.com.ng
Description: Customer support, rewards, agent management, mobile access, and token billing for VTUpress-powered websites.
Version: 1.0.6
Author: Bluishgen
Author URI: https://bluishgen.com.ng
Text Domain: blupass
*/

if (!defined('ABSPATH')) exit;

define('SBC_DB_VERSION', '4.7.0');
define('SBC_PLUGIN_URL', plugin_dir_url(__FILE__));
define('SBC_PLUGIN_VERSION', '1.0.6');

require_once __DIR__ . '/includes/class-sbc-operations.php';
require_once __DIR__ . '/includes/class-sbc-agent-desk.php';

class SBC_Plugin {

    private $current_mobile_device = null;
    private $shortcode_rendered = false;

    public function __construct() {
        add_filter('cron_schedules', [$this, 'cron_schedules']);

        register_activation_hook(__FILE__, [$this, 'activate']);
        register_deactivation_hook(__FILE__, [$this, 'deactivate']);

        add_action('plugins_loaded', [$this, 'maybe_upgrade_db']);

        add_shortcode('sbc_bonus_chat', [$this, 'shortcode']);
        add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend']);
        add_action('wp_footer', [$this, 'render_automatic_widget'], 99);

        add_action('admin_menu', [$this, 'admin_menu']);
        add_action('admin_init', [$this, 'register_settings']);
        add_action('admin_enqueue_scripts', [$this, 'enqueue_settings_media']);
        add_action('admin_post_sbc_save_settings', [$this, 'save_support_settings']);
        add_action('rest_api_init', [$this, 'register_mobile_rest_routes']);
        add_action('admin_post_sbc_generate_pairing_code', [$this, 'generate_mobile_pairing_code']);
        add_action('admin_post_sbc_revoke_mobile_device', [$this, 'revoke_mobile_device']);
        add_action('admin_post_sbc_activate_license', [$this, 'activate_commercial_license']);
        add_action('admin_post_sbc_license_checkout', [$this, 'commercial_checkout']);
        add_filter('pre_set_site_transient_update_plugins', [$this, 'commercial_update_check']);
        add_action('admin_notices', [$this, 'commercial_update_notice']);
        add_action('admin_notices', [$this, 'database_repair_notice']);
        add_filter('plugin_action_links_' . plugin_basename(__FILE__), [$this, 'plugin_action_links']);
        add_filter('plugin_row_meta', [$this, 'plugin_row_meta'], 10, 2);
        add_action('admin_post_sbc_check_updates', [$this, 'check_for_updates_now']);

        add_action('sbc_check_timeouts', [$this, 'check_timeouts']);
        add_action('sbc_chat_transferred', [$this, 'notify_transferred_agent'], 10, 2);

        // Frontend AJAX
        add_action('wp_ajax_nopriv_sbc_lookup_email', [$this, 'lookup_email']);
        add_action('wp_ajax_sbc_lookup_email', [$this, 'lookup_email']);

        add_action('wp_ajax_sbc_get_support_context', [$this, 'get_support_context']);
        add_action('wp_ajax_sbc_get_recent_transactions', [$this, 'get_recent_transactions']);

        add_action('wp_ajax_nopriv_sbc_start_chat', [$this, 'start_chat']);
        add_action('wp_ajax_sbc_start_chat', [$this, 'start_chat']);

        add_action('wp_ajax_nopriv_sbc_send_message', [$this, 'send_message']);
        add_action('wp_ajax_sbc_send_message', [$this, 'send_message']);

        add_action('wp_ajax_nopriv_sbc_get_status', [$this, 'get_status']);
        add_action('wp_ajax_sbc_get_status', [$this, 'get_status']);

        add_action('wp_ajax_nopriv_sbc_end_chat', [$this, 'end_chat']);
        add_action('wp_ajax_sbc_end_chat', [$this, 'end_chat']);

        add_action('wp_ajax_nopriv_sbc_customer_typing', [$this, 'customer_typing']);
        add_action('wp_ajax_sbc_customer_typing', [$this, 'customer_typing']);

        add_action('wp_ajax_nopriv_sbc_request_withdrawal', [$this, 'request_withdrawal']);
        add_action('wp_ajax_sbc_request_withdrawal', [$this, 'request_withdrawal']);

        add_action('wp_ajax_nopriv_sbc_submit_rating', [$this, 'submit_rating']);
        add_action('wp_ajax_sbc_submit_rating', [$this, 'submit_rating']);

        // Admin AJAX
        add_action('wp_ajax_sbc_admin_get_inbox', [$this, 'admin_get_inbox']);
        add_action('wp_ajax_sbc_admin_reply', [$this, 'admin_reply']);
        add_action('wp_ajax_sbc_admin_close_chat', [$this, 'admin_close_chat']);
        add_action('wp_ajax_sbc_admin_typing', [$this, 'admin_typing']);
        add_action('wp_ajax_sbc_admin_deadzone_reset', [$this, 'admin_deadzone_reset']);
        add_action('wp_ajax_sbc_admin_clear_all', [$this, 'admin_clear_all']);

        add_action('wp_ajax_sbc_admin_get_withdrawals', [$this, 'admin_get_withdrawals']);
        add_action('wp_ajax_sbc_admin_approve_withdrawal', [$this, 'admin_approve_withdrawal']);
        add_action('wp_ajax_sbc_admin_decline_withdrawal', [$this, 'admin_decline_withdrawal']);

        add_action('wp_ajax_sbc_admin_get_customers', [$this, 'admin_get_customers']);
        add_action('wp_ajax_sbc_admin_adjust_balance', [$this, 'admin_adjust_balance']);
        add_action('wp_ajax_sbc_admin_refresh_transaction', [$this, 'admin_refresh_transaction']);
    }

    public function cron_schedules($schedules) {
        $schedules['every_minute'] = [
            'interval' => 60,
            'display'  => 'Every Minute',
        ];
        return $schedules;
    }

    private function tables() {
        global $wpdb;
        return [
            'chats'      => $wpdb->prefix . 'sbc_chats',
            'msgs'       => $wpdb->prefix . 'sbc_messages',
            'comp'       => $wpdb->prefix . 'sbc_compensation_log',
            'wallet_tx'  => $wpdb->prefix . 'sbc_wallet_transactions',
            'withdraw'   => $wpdb->prefix . 'sbc_withdrawal_requests',
            'devices'    => $wpdb->prefix . 'sbc_mobile_devices',
            'mobile_audit' => $wpdb->prefix . 'sbc_mobile_audit',
        ];
    }

    private function balance_meta_key() {
        return 'sbc_bonus_balance';
    }

    private function reset_version() {
        return (int) get_option('sbc_reset_version', 1);
    }

    private function bump_reset_version() {
        $v = $this->reset_version() + 1;
        update_option('sbc_reset_version', $v, false);
        return $v;
    }

    private function withdrawal_threshold() {
        return max(0, (int) get_option('sbc_withdrawal_threshold', 500));
    }

    private function bonus_cooldown_hours() {
        return max(1, (int) get_option('sbc_bonus_cooldown_hours', 10));
    }

    private function bonus_cooldown_cutoff() {
        return current_datetime()
            ->modify('-' . $this->bonus_cooldown_hours() . ' hours')
            ->format('Y-m-d H:i:s');
    }

    private function bonus_is_available($customer_key) {
        $status = $this->bonus_cooldown_status($customer_key);
        return $status['available'];
    }

    private function bonus_cooldown_status($customer_key) {
        global $wpdb;
        $t = $this->tables();
        $last_awarded = $wpdb->get_var($wpdb->prepare(
            "SELECT MAX(created_at) FROM {$t['comp']} WHERE customer_key = %s",
            $customer_key
        ));

        if (!$last_awarded) {
            return ['available' => true, 'remaining_seconds' => 0, 'message' => ''];
        }

        $last_time = date_create_immutable_from_format('Y-m-d H:i:s', $last_awarded, wp_timezone());
        if (!$last_time) {
            return ['available' => true, 'remaining_seconds' => 0, 'message' => ''];
        }

        $cooldown_seconds = $this->bonus_cooldown_hours() * HOUR_IN_SECONDS;
        $remaining = max(0, ($last_time->getTimestamp() + $cooldown_seconds) - current_datetime()->getTimestamp());
        if ($remaining <= 0) {
            return ['available' => true, 'remaining_seconds' => 0, 'message' => ''];
        }

        $hours = intdiv($remaining, HOUR_IN_SECONDS);
        $minutes = (int) ceil(($remaining % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS);
        if ($minutes >= 60) { $hours++; $minutes = 0; }
        $wait = $hours > 0 ? $hours . 'h' : '';
        if ($minutes > 0) $wait .= ($wait ? ' ' : '') . $minutes . 'm';

        return [
            'available' => false,
            'remaining_seconds' => $remaining,
            'message' => 'You have received a bonus today. No bonus until tomorrow.',
        ];
    }

    /** Keep only the three newest chats and their messages for one customer. */
    private function prune_customer_history($customer_key) {
        global $wpdb;
        $t = $this->tables();

        if (!$customer_key) return;

        $old_ids = $wpdb->get_col($wpdb->prepare(
            "SELECT id FROM {$t['chats']} WHERE customer_key = %s ORDER BY updated_at DESC, id DESC LIMIT 3, 1000000",
            $customer_key
        ));

        if (!$old_ids) return;

        $old_ids = array_values(array_filter(array_map('absint', $old_ids)));
        if (!$old_ids) return;

        $placeholders = implode(',', array_fill(0, count($old_ids), '%d'));
        $wpdb->query($wpdb->prepare("DELETE FROM {$t['msgs']} WHERE chat_id IN ($placeholders)", $old_ids));
        $wpdb->query($wpdb->prepare("DELETE FROM {$t['chats']} WHERE id IN ($placeholders)", $old_ids));
    }

    private function prune_all_customer_histories() {
        global $wpdb;
        $t = $this->tables();
        $keys = $wpdb->get_col("SELECT DISTINCT customer_key FROM {$t['chats']} WHERE customer_key <> ''");
        foreach ($keys as $key) $this->prune_customer_history($key);
    }

    private function ensure_integration_schema() {
        global $wpdb;
        $t = $this->tables();
        $column = $wpdb->get_var("SHOW COLUMNS FROM {$t['chats']} LIKE 'vtupress_transaction_id'");
        if (!$column) {
            $this->install_tables();
            wp_cache_flush();
        }
    }

    private function get_user_balance($user_id) {
        return (float) get_user_meta((int) $user_id, $this->balance_meta_key(), true);
    }

    private function set_user_balance($user_id, $balance) {
        update_user_meta((int) $user_id, $this->balance_meta_key(), round((float) $balance, 2));
    }

    private function add_wallet_transaction($user_id, $chat_id, $type, $amount, $note = '') {
        global $wpdb;
        $t = $this->tables();
        $now = current_time('mysql');

        $before = $this->get_user_balance($user_id);
        $after  = round($before + (float) $amount, 2);

        $this->set_user_balance($user_id, $after);

        $wpdb->insert($t['wallet_tx'], [
            'user_id'        => (int) $user_id,
            'chat_id'        => (int) $chat_id,
            'tx_type'        => sanitize_text_field($type),
            'amount'         => (float) $amount,
            'balance_before'  => $before,
            'balance_after'   => $after,
            'note'           => sanitize_text_field($note),
            'created_at'      => $now,
        ]);

        return $after;
    }

    private function has_pending_withdrawal($user_id) {
        global $wpdb;
        $t = $this->tables();

        return (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$t['withdraw']} WHERE user_id = %d AND status = 'pending'",
            (int) $user_id
        )) > 0;
    }

    private function wallet_status($user_id) {
        $balance   = $this->get_user_balance($user_id);
        $threshold = $this->withdrawal_threshold();
        $pending   = $this->has_pending_withdrawal($user_id);

        return [
            'balance'            => $balance,
            'threshold'          => $threshold,
            'pending_withdrawal' => $pending,
            'can_withdraw'       => (!$pending && $balance >= $threshold),
        ];
    }

    private function vtupress_user($wp_user_id) {
        global $wpdb;
        $table = $wpdb->prefix . 'vtupress_users';
        if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) !== $table) return null;
        return $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE userId = %d LIMIT 1", (int) $wp_user_id));
    }

    private function transaction_summary($transaction) {
        $details = json_decode((string) ($transaction->details ?? ''), true);
        if (!is_array($details)) $details = [];
        $plan = $details['plan_name'] ?? $details['plan'] ?? $details['name'] ?? $details['package'] ?? '';
        $network = $details['network'] ?? $details['provider'] ?? '';
        return [
            'id' => (int) ($transaction->id ?? 0),
            'service' => sanitize_text_field((string) ($transaction->service ?? '')),
            'plan' => sanitize_text_field((string) $plan),
            'network' => sanitize_text_field((string) $network),
            'recipient' => sanitize_text_field((string) ($transaction->recipient ?? $transaction->phone ?? $transaction->account ?? '')),
            'amount' => (float) ($transaction->amount ?? $transaction->price ?? 0),
            'status' => sanitize_text_field((string) ($transaction->status ?? '')),
            'reference' => sanitize_text_field((string) ($transaction->reference ?? $transaction->request_id ?? '')),
            'created_at' => sanitize_text_field((string) ($transaction->created_at ?? $transaction->date ?? '')),
        ];
    }

    private function require_logged_in_customer() {
        if (!is_user_logged_in()) {
            wp_send_json_error(['message' => 'Please log in to use customer support.', 'login_required' => true], 401);
        }
        return get_current_user_id();
    }

    private function require_chat_owner($chat) {
        $user_id = $this->require_logged_in_customer();
        if (!$chat || (int) $chat->user_id !== (int) $user_id) {
            wp_send_json_error(['message' => 'You do not have access to this support chat.'], 403);
        }
        return $user_id;
    }

    private function canned_replies() {
        if(function_exists('sbc_ops')){$rows=sbc_ops()->visible_canned_for_mobile(get_current_user_id());$messages=array_values(array_filter(array_map(function($row){return trim((string)($row['message']??''));},$rows)));if($messages)return $messages;}
        return ['Thanks for reaching out, an agent will be right with you.','Could you share a bit more detail so we can help faster?','Your issue has been resolved. Is there anything else we can help with?'];
    }

    public function activate() {
        $this->install_tables();
        add_option('sbc_reset_version', 1, '', false);
        add_option('sbc_withdrawal_threshold', 500, '', false);
        add_option('sbc_bonus_cooldown_hours', 10, '', false);
        update_option('sbc_db_version', SBC_DB_VERSION, false);

        if (!wp_next_scheduled('sbc_check_timeouts')) {
            wp_schedule_event(time() + 60, 'every_minute', 'sbc_check_timeouts');
        }
    }

    public function deactivate() {
        wp_clear_scheduled_hook('sbc_check_timeouts');
    }

    public function maybe_upgrade_db() {
        global $wpdb;
        $t=$this->tables();
        $needs_repair=get_option('sbc_db_version') !== SBC_DB_VERSION;
        if(!$needs_repair){
            foreach((array)$wpdb->get_results("SHOW INDEX FROM {$t['chats']}") as $index){
                if((int)$index->Non_unique===0&&strtoupper((string)$index->Key_name)!=='PRIMARY'){$needs_repair=true;break;}
            }
        }
        if ($needs_repair) {
            $this->install_tables();
            if(function_exists('sbc_ops'))sbc_ops()->repair_schema();
            $remaining_unique=false;
            foreach((array)$wpdb->get_results("SHOW INDEX FROM {$t['chats']}") as $index){
                if((int)$index->Non_unique===0&&strtoupper((string)$index->Key_name)!=='PRIMARY'){$remaining_unique=true;break;}
            }
            if(!$remaining_unique)update_option('sbc_db_version', SBC_DB_VERSION, false);
            else update_option('sbc_last_database_error',['first'=>'An obsolete UNIQUE index is still present on the chat table.','retry'=>$wpdb->last_error?:'The database account may not have ALTER permission.','time'=>current_time('mysql')],false);
        }
    }

    private function install_tables() {
        global $wpdb;
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';

        $charset_collate = $wpdb->get_charset_collate();
        $t = $this->tables();

        $sql1 = "CREATE TABLE {$t['chats']} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            user_id bigint(20) unsigned NOT NULL DEFAULT 0,
            customer_key varchar(191) NOT NULL,
            session_token varchar(64) NOT NULL DEFAULT '',
            customer_email varchar(191) NOT NULL DEFAULT '',
            full_name varchar(191) NOT NULL DEFAULT '',
            service_type varchar(50) NOT NULL DEFAULT '',
            initial_message longtext NULL,
            status varchar(20) NOT NULL DEFAULT 'collecting',
            timer_started_at datetime DEFAULT NULL,
            timer_expires_at datetime DEFAULT NULL,
            timer_expires_ts bigint(20) unsigned NOT NULL DEFAULT 0,
            agent_replied_at datetime DEFAULT NULL,
            closed_at datetime DEFAULT NULL,
            admin_unread_count int(11) NOT NULL DEFAULT 0,
            compensation_awarded tinyint(1) NOT NULL DEFAULT 0,
            compensation_amount decimal(10,2) NOT NULL DEFAULT 0,
            bonus_awarded_at datetime DEFAULT NULL,
            customer_typing_at datetime DEFAULT NULL,
            agent_typing_at datetime DEFAULT NULL,
            rating tinyint(1) DEFAULT NULL,
            rating_feedback varchar(255) NOT NULL DEFAULT '',
            rating_submitted_at datetime DEFAULT NULL,
            vtupress_transaction_id bigint(20) unsigned NOT NULL DEFAULT 0,
            complaint_reason varchar(100) NOT NULL DEFAULT '',
            transaction_snapshot longtext NULL,
            assigned_agent_id bigint(20) unsigned NOT NULL DEFAULT 0,
            department_id bigint(20) unsigned NOT NULL DEFAULT 0,
            assignment_heartbeat_at datetime DEFAULT NULL,
            last_human_activity_at datetime DEFAULT NULL,
            inactivity_warned_at datetime DEFAULT NULL,
            token_debit decimal(6,2) NOT NULL DEFAULT 0,
            created_at datetime NOT NULL,
            updated_at datetime NOT NULL,
            PRIMARY KEY  (id),
            KEY customer_key (customer_key),
            KEY session_token (session_token),
            KEY status (status),
            KEY timer_expires_ts (timer_expires_ts),
            KEY vtupress_transaction_id (vtupress_transaction_id),
            KEY updated_at (updated_at)
        ) $charset_collate;";

        $sql2 = "CREATE TABLE {$t['msgs']} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            chat_id bigint(20) unsigned NOT NULL,
            sender_type varchar(20) NOT NULL,
            sender_label varchar(191) NOT NULL DEFAULT '',
            sender_department varchar(191) NOT NULL DEFAULT '',
            message longtext NOT NULL,
            request_key varchar(64) DEFAULT NULL,
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            KEY chat_id (chat_id),
            UNIQUE KEY request_key (request_key)
        ) $charset_collate;";

        $sql3 = "CREATE TABLE {$t['comp']} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            chat_id bigint(20) unsigned NOT NULL,
            customer_key varchar(191) NOT NULL,
            award_date date NOT NULL,
            amount decimal(10,2) NOT NULL DEFAULT 0,
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            KEY customer_created (customer_key, created_at),
            KEY chat_id (chat_id)
        ) $charset_collate;";

        $sql4 = "CREATE TABLE {$t['wallet_tx']} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            user_id bigint(20) unsigned NOT NULL DEFAULT 0,
            chat_id bigint(20) unsigned NOT NULL DEFAULT 0,
            tx_type varchar(30) NOT NULL DEFAULT '',
            amount decimal(10,2) NOT NULL DEFAULT 0,
            balance_before decimal(10,2) NOT NULL DEFAULT 0,
            balance_after decimal(10,2) NOT NULL DEFAULT 0,
            note varchar(255) NOT NULL DEFAULT '',
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            KEY user_id (user_id),
            KEY chat_id (chat_id),
            KEY tx_type (tx_type)
        ) $charset_collate;";

        $sql5 = "CREATE TABLE {$t['withdraw']} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            user_id bigint(20) unsigned NOT NULL DEFAULT 0,
            customer_key varchar(191) NOT NULL DEFAULT '',
            amount decimal(10,2) NOT NULL DEFAULT 0,
            status varchar(20) NOT NULL DEFAULT 'pending',
            requested_at datetime NOT NULL,
            resolved_at datetime DEFAULT NULL,
            admin_note varchar(255) NOT NULL DEFAULT '',
            external_reference varchar(100) NOT NULL DEFAULT '',
            failure_reason varchar(255) NOT NULL DEFAULT '',
            PRIMARY KEY  (id),
            KEY user_id (user_id),
            KEY status (status),
            KEY customer_key (customer_key)
        ) $charset_collate;";

        $sql6 = "CREATE TABLE {$t['devices']} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            admin_user_id bigint(20) unsigned NOT NULL DEFAULT 0,
            device_name varchar(120) NOT NULL DEFAULT '',
            token_hash char(64) NOT NULL,
            push_subscription_id varchar(191) NOT NULL DEFAULT '',
            last_seen_at datetime DEFAULT NULL,
            revoked_at datetime DEFAULT NULL,
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY token_hash (token_hash),
            KEY admin_user_id (admin_user_id),
            KEY revoked_at (revoked_at)
        ) $charset_collate;";

        $sql7 = "CREATE TABLE {$t['mobile_audit']} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            device_id bigint(20) unsigned NOT NULL DEFAULT 0,
            admin_user_id bigint(20) unsigned NOT NULL DEFAULT 0,
            chat_id bigint(20) unsigned NOT NULL DEFAULT 0,
            action_name varchar(40) NOT NULL DEFAULT '',
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            KEY device_id (device_id),
            KEY chat_id (chat_id),
            KEY created_at (created_at)
        ) $charset_collate;";

        dbDelta($sql1);
        dbDelta($sql2);
        dbDelta($sql3);
        dbDelta($sql4);
        dbDelta($sql5);
        dbDelta($sql6);
        dbDelta($sql7);

        // dbDelta can miss columns on some older/customized MySQL tables.
        // Add the complaint and agent-identity fields explicitly when absent.
        $chat_columns=[
            'vtupress_transaction_id'=>"bigint(20) unsigned NOT NULL DEFAULT 0",
            'complaint_reason'=>"varchar(100) NOT NULL DEFAULT ''",
            'transaction_snapshot'=>"longtext NULL",
        ];
        foreach($chat_columns as $name=>$definition){
            if(!$wpdb->get_var("SHOW COLUMNS FROM {$t['chats']} LIKE '".esc_sql($name)."'"))$wpdb->query("ALTER TABLE {$t['chats']} ADD {$name} {$definition}");
        }
        $timer_column=$wpdb->get_row("SHOW COLUMNS FROM {$t['chats']} LIKE 'timer_expires_at'");
        if($timer_column&&strtoupper((string)$timer_column->Null)!=='YES'){
            $wpdb->query("ALTER TABLE {$t['chats']} MODIFY timer_expires_at datetime DEFAULT NULL");
        }
        $message_columns=[
            'sender_label'=>"varchar(191) NOT NULL DEFAULT ''",
            'sender_department'=>"varchar(191) NOT NULL DEFAULT ''",
            'request_key'=>"varchar(64) DEFAULT NULL",
        ];
        foreach($message_columns as $name=>$definition){
            if(!$wpdb->get_var("SHOW COLUMNS FROM {$t['msgs']} LIKE '".esc_sql($name)."'"))$wpdb->query("ALTER TABLE {$t['msgs']} ADD {$name} {$definition}");
        }

        // The current model permits several conversations per customer and repeated
        // complaints over time. No non-primary chat field may remain UNIQUE. Older
        // releases used several index names, so remove all non-primary unique keys.
        $chat_indexes=$wpdb->get_results("SHOW INDEX FROM {$t['chats']}");$unique_indexes=[];
        foreach($chat_indexes as $index){
            if((int)$index->Non_unique===0&&strtoupper((string)$index->Key_name)!=='PRIMARY')$unique_indexes[(string)$index->Key_name][]=sanitize_key((string)$index->Column_name);
        }
        foreach($unique_indexes as $index_name=>$columns){
            $safe_index=str_replace('`','``',$index_name);
            $dropped=$wpdb->query("ALTER TABLE {$t['chats']} DROP INDEX `{$safe_index}`");
            if($dropped===false)error_log('BluPass could not remove obsolete unique index '.$index_name.': '.$wpdb->last_error);
        }

        // Version 2.0 used this unique key to enforce one award per calendar
        // day. The configurable cooldown now uses created_at instead.
        $legacy_key = $wpdb->get_var("SHOW INDEX FROM {$t['comp']} WHERE Key_name = 'uniq_customer_day'");
        if ($legacy_key) {
            $wpdb->query("ALTER TABLE {$t['comp']} DROP INDEX uniq_customer_day");
        }
    }

    public function admin_menu() {
        $support_cap = class_exists('SBC_Operations') ? SBC_Operations::AGENT_CAP : 'manage_options';
        add_menu_page(
            'BluPass',
            'BluPass',
            $support_cap,
            'sbc-settings',
            [$this, 'workspace_landing'],
            'dashicons-format-chat',
            58
        );

        add_submenu_page(
            'sbc-settings',
            'Inbox',
            'Inbox',
            $support_cap,
            'sbc-inbox',
            [$this, 'workspace_inbox_page']
        );

        add_submenu_page(
            'sbc-settings',
            'Customers & Balances',
            'Customers & Balances',
            $support_cap,
            'sbc-customers',
            [$this, 'workspace_customers_page']
        );

        add_submenu_page(
            'sbc-settings',
            'Withdrawals',
            'Withdrawals',
            'manage_options',
            'sbc-withdrawals',
            [$this, 'withdrawals_page']
        );

        add_submenu_page(
            'sbc-settings',
            'Mobile App',
            'Mobile App',
            'manage_options',
            'sbc-mobile-app',
            [$this, 'mobile_app_page']
        );

    }

    public function workspace_landing(){
        if(current_user_can('manage_options')){$this->settings_page();return;}
        $this->workspace_inbox_page();
    }

    public function workspace_inbox_page(){
        if(current_user_can('manage_options')){$this->inbox_page();return;}
        if(!function_exists('sbc_ops')||!sbc_ops()->is_registered_staff(get_current_user_id()))wp_die('Your account is not registered as an active support agent.');
        global $sbc_agent_desk;if($sbc_agent_desk instanceof SBC_Agent_Desk)echo $sbc_agent_desk->desk_screen();
    }

    public function workspace_customers_page(){
        if(current_user_can('manage_options')){$this->customers_page();return;}
        if(!function_exists('sbc_ops')||!sbc_ops()->is_registered_staff(get_current_user_id()))wp_die('Unauthorized');
        global $wpdb;$table=$wpdb->prefix.'sbc_chats';$rows=$wpdb->get_results("SELECT customer_key,MAX(full_name) full_name,MAX(customer_email) customer_email,COUNT(*) chats_count,MAX(updated_at) last_activity FROM {$table} GROUP BY customer_key ORDER BY last_activity DESC LIMIT 500");
        echo '<div class="wrap"><h1>Customers</h1><p>Customer contact and conversation activity. Financial balances are restricted to administrators.</p><table class="widefat striped"><thead><tr><th>Customer</th><th>Chats</th><th>Last activity</th></tr></thead><tbody>';
        foreach($rows as $row)echo '<tr><td><strong>'.esc_html($row->full_name?:'Customer').'</strong><br><small>'.esc_html($row->customer_email).'</small></td><td>'.(int)$row->chats_count.'</td><td>'.esc_html($row->last_activity).'</td></tr>';
        if(!$rows)echo '<tr><td colspan="3">No customers yet.</td></tr>';echo '</tbody></table></div>';
    }

    public function register_settings() {
        register_setting('sbc_license_settings', 'sbc_license_server_url', [
            'type' => 'string', 'sanitize_callback' => 'esc_url_raw', 'default' => '',
        ]);
        register_setting('sbc_push_settings', 'sbc_onesignal_app_id', [
            'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '',
        ]);
        register_setting('sbc_push_settings', 'sbc_onesignal_rest_api_key', [
            'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '',
        ]);
    }

    private function complaint_service_catalog(){
        $catalog=['airtime'=>'Airtime','data'=>'Data','cable'=>'Cable TV','bill'=>'Electricity / Bills','electricity'=>'Electricity','bulk_sms'=>'Bulk SMS','betting'=>'Betting','education'=>'Education Payments','exam_pin'=>'Exam Pins','recharge_card'=>'Recharge Card Printing','airtime_to_cash'=>'Airtime to Cash','spectranet'=>'Spectranet','smile'=>'Smile Data'];
        global $wpdb;$table=$wpdb->prefix.'vtupress_transactions';$exists=$wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s',$table));
        if($exists===$table){$found=$wpdb->get_col("SELECT DISTINCT service FROM {$table} WHERE service<>'' ORDER BY service LIMIT 50");foreach($found as $service){$key=sanitize_key($service);if($key&&!isset($catalog[$key]))$catalog[$key]=ucwords(str_replace(['_','-'],' ',$key));}}
        return $catalog;
    }
    private function enabled_complaint_services(){
        $catalog=$this->complaint_service_catalog();$saved=get_option('sbc_complaint_services',null);$enabled=$saved===null?['airtime','data','cable','bill']:(array)$saved;$out=[];foreach($enabled as $key){$key=sanitize_key($key);if(isset($catalog[$key]))$out[$key]=$catalog[$key];}return $out;
    }

    private function vtupress_service_value($key){
        $key=sanitize_key($key);global $wpdb;$table=$wpdb->prefix.'vtupress_transactions';$exists=$wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s',$table));
        if($exists===$table){$values=$wpdb->get_col("SELECT DISTINCT service FROM {$table} WHERE service<>'' LIMIT 100");foreach($values as $value)if(sanitize_key($value)===$key)return (string)$value;}
        return $key;
    }

    public function enqueue_settings_media(){
        if(!current_user_can('manage_options')||sanitize_key($_GET['page']??'')!=='sbc-settings')return;
        wp_enqueue_media();
    }

    private function targeting_lines($option,$default=''){
        $raw=(string)get_option($option,$default);$lines=preg_split('/\r\n|\r|\n/',$raw);$out=[];
        foreach((array)$lines as $line){$line=trim((string)$line);if($line!=='')$out[]=$line;}
        return array_values(array_unique($out));
    }

    private function wildcard_target_match($patterns){
        $request_uri=(string)wp_unslash($_SERVER['REQUEST_URI']??'/');$path=(string)(wp_parse_url($request_uri,PHP_URL_PATH)?:'/');$full=home_url($request_uri);$targets=[$path,$request_uri,$full];
        foreach((array)$patterns as $pattern){$pattern=trim((string)$pattern);if($pattern==='')continue;$regex='#^'.str_replace('\\*','.*',preg_quote($pattern,'#')).'$#i';foreach($targets as $target){if(preg_match($regex,$target))return true;if(strpos($pattern,'*')===false&&rtrim($target,'/')===rtrim($pattern,'/'))return true;}}
        return false;
    }

    private function template_target_match($targets){
        $files=get_included_files();if(function_exists('get_page_template')){$page_template=get_page_template();if($page_template)$files[]=$page_template;}
        foreach((array)$files as $file){$candidate=strtolower(str_replace('\\','/',(string)$file));$basename=basename($candidate);foreach((array)$targets as $target){$target=strtolower(ltrim(str_replace('\\','/',trim((string)$target)),'/'));if($target==='' )continue;if(strpos($target,'/')===false&&$basename===$target)return true;if(strlen($candidate)>=strlen($target)&&substr_compare($candidate,$target,-strlen($target),strlen($target),true)===0)return true;}}
        return false;
    }

    private function should_render_automatically(){
        $mode=sanitize_key(get_option('sbc_display_mode','shortcode'));if($mode==='shortcode'||is_admin()||wp_doing_ajax())return false;
        if((int)get_option('sbc_display_logged_in_only',0)&&!is_user_logged_in())return false;
        if($this->wildcard_target_match($this->targeting_lines('sbc_display_exclude_urls')))return false;
        if($mode==='all')return true;
        if($mode==='urls')return $this->wildcard_target_match($this->targeting_lines('sbc_display_include_urls'));
        if($mode==='templates')return $this->template_target_match($this->targeting_lines('sbc_display_template_files','top.html'));
        return false;
    }

    public function render_automatic_widget(){
        if($this->shortcode_rendered||!$this->should_render_automatically())return;
        echo $this->shortcode(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- shortcode builds escaped markup.
    }

    public function save_support_settings(){
        if(!current_user_can('manage_options'))wp_die('Unauthorized');check_admin_referer('sbc_save_settings');
        $timeout=max(10,min(86400,absint($_POST['sbc_timeout_seconds']??60)));$bonus=max(0,(float)($_POST['sbc_bonus_amount']??50));$threshold=max(0,(float)($_POST['sbc_withdrawal_threshold']??500));$cooldown=max(1,min(8760,absint($_POST['sbc_bonus_cooldown_hours']??10)));
        $catalog=$this->complaint_service_catalog();$services=[];foreach((array)($_POST['sbc_complaint_services']??[]) as $service){$key=sanitize_key(wp_unslash($service));if(isset($catalog[$key]))$services[]=$key;}
        $primary=sanitize_hex_color(wp_unslash($_POST['sbc_primary_color']??''))?:'#0867ed';$secondary=sanitize_hex_color(wp_unslash($_POST['sbc_secondary_color']??''))?:'#4f46e5';$accent=sanitize_hex_color(wp_unslash($_POST['sbc_accent_color']??''))?:'#14b8a6';
        $widget_side=in_array(($_POST['sbc_widget_side']??'right'),['left','right'],true)?$_POST['sbc_widget_side']:'right';
        $widget_horizontal=max(0,min(500,absint($_POST['sbc_widget_horizontal_offset']??20)));
        $widget_bottom=max(0,min(500,absint($_POST['sbc_widget_bottom_offset']??20)));
        $widget_mobile_horizontal=max(0,min(120,absint($_POST['sbc_widget_mobile_horizontal_offset']??16)));
        $widget_mobile_bottom=max(0,min(200,absint($_POST['sbc_widget_mobile_bottom_offset']??16)));
        $support_name=trim(sanitize_text_field(wp_unslash($_POST['sbc_support_name']??'BluPass Support')));if($support_name==='')$support_name='BluPass Support';$support_name=function_exists('mb_substr')?mb_substr($support_name,0,60):substr($support_name,0,60);
        $support_icon_id=absint($_POST['sbc_support_icon_id']??0);if($support_icon_id&&!wp_attachment_is_image($support_icon_id))$support_icon_id=0;
        $display_mode=sanitize_key(wp_unslash($_POST['sbc_display_mode']??'shortcode'));if(!in_array($display_mode,['shortcode','all','urls','templates'],true))$display_mode='shortcode';
        $include_urls=sanitize_textarea_field(wp_unslash($_POST['sbc_display_include_urls']??''));$exclude_urls=sanitize_textarea_field(wp_unslash($_POST['sbc_display_exclude_urls']??''));$template_files=sanitize_textarea_field(wp_unslash($_POST['sbc_display_template_files']??'top.html'));$logged_in_only=!empty($_POST['sbc_display_logged_in_only'])?1:0;
        update_option('sbc_timeout_seconds',$timeout,false);update_option('sbc_bonus_amount',$bonus,false);update_option('sbc_withdrawal_threshold',$threshold,false);update_option('sbc_bonus_cooldown_hours',$cooldown,false);update_option('sbc_complaint_services',array_values(array_unique($services)),false);update_option('sbc_primary_color',$primary,false);update_option('sbc_secondary_color',$secondary,false);update_option('sbc_accent_color',$accent,false);update_option('sbc_widget_side',$widget_side,false);update_option('sbc_widget_horizontal_offset',$widget_horizontal,false);update_option('sbc_widget_bottom_offset',$widget_bottom,false);update_option('sbc_widget_mobile_horizontal_offset',$widget_mobile_horizontal,false);update_option('sbc_widget_mobile_bottom_offset',$widget_mobile_bottom,false);update_option('sbc_support_name',$support_name,false);update_option('sbc_support_icon_id',$support_icon_id,false);update_option('sbc_display_mode',$display_mode,false);update_option('sbc_display_include_urls',$include_urls,false);update_option('sbc_display_exclude_urls',$exclude_urls,false);update_option('sbc_display_template_files',$template_files,false);update_option('sbc_display_logged_in_only',$logged_in_only,false);
        wp_safe_redirect(add_query_arg(['page'=>'sbc-settings','settings-updated'=>'true'],admin_url('admin.php')));exit;
    }

    public function settings_page() {
        ?>
        <div class="wrap">
            <h1>Support &amp; Bonus Settings</h1>
            <p>Use shortcode: <code>[sbc_bonus_chat]</code></p>

            <?php if(!empty($_GET['settings-updated'])):?><div class="notice notice-success is-dismissible"><p>Settings saved successfully.</p></div><?php endif;?>
            <form method="post" action="<?php echo esc_url(admin_url('admin-post.php'));?>"><input type="hidden" name="action" value="sbc_save_settings"><?php wp_nonce_field('sbc_save_settings');?>
                <table class="form-table">
                    <tr>
                        <th scope="row">Timer (seconds)</th>
                        <td>
                            <input type="number" name="sbc_timeout_seconds" value="<?php echo esc_attr(get_option('sbc_timeout_seconds', 60)); ?>" />
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">Bonus Amount (₦)</th>
                        <td>
                            <input type="number" name="sbc_bonus_amount" value="<?php echo esc_attr(get_option('sbc_bonus_amount', 50)); ?>" />
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">Withdrawal Threshold (₦)</th>
                        <td>
                            <input type="number" name="sbc_withdrawal_threshold" value="<?php echo esc_attr(get_option('sbc_withdrawal_threshold', 500)); ?>" />
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">Bonus cooldown (hours)</th>
                        <td>
                            <input type="number" min="1" name="sbc_bonus_cooldown_hours" value="<?php echo esc_attr($this->bonus_cooldown_hours()); ?>" />
                            <p class="description">How long a customer must wait after receiving a bonus before becoming eligible again. Default: 10 hours.</p>
                        </td>
                    </tr>
                    <tr><th scope="row">Customer complaint services</th><td><fieldset><input type="hidden" name="sbc_complaint_services[]" value=""><p class="description">Choose which VTUpress services customers can select and browse transaction history for.</p><?php $enabled=$this->enabled_complaint_services();foreach($this->complaint_service_catalog() as $key=>$label):?><label style="display:inline-block;min-width:180px;margin:8px 8px 0 0"><input type="checkbox" name="sbc_complaint_services[]" value="<?php echo esc_attr($key);?>" <?php checked(isset($enabled[$key]));?>> <?php echo esc_html($label);?></label><?php endforeach;?></fieldset></td></tr>
                    <tr><th scope="row">Interface color palette</th><td><div style="display:flex;gap:18px;flex-wrap:wrap"><label>Primary<br><input type="color" name="sbc_primary_color" value="<?php echo esc_attr(get_option('sbc_primary_color','#0867ed'));?>"></label><label>Secondary<br><input type="color" name="sbc_secondary_color" value="<?php echo esc_attr(get_option('sbc_secondary_color','#4f46e5'));?>"></label><label>Accent<br><input type="color" name="sbc_accent_color" value="<?php echo esc_attr(get_option('sbc_accent_color','#14b8a6'));?>"></label></div><p class="description">Changes buttons, highlights, avatars, messages, and navigation across the admin workspace, agent desktop, and customer chatbot. White cards remain white for readability.</p><div style="display:flex;gap:8px;margin-top:12px"><button type="button" class="button sbc-palette" data-colors="#0867ed,#4f46e5,#14b8a6">Ocean</button><button type="button" class="button sbc-palette" data-colors="#7c3aed,#db2777,#f59e0b">Royal</button><button type="button" class="button sbc-palette" data-colors="#0f766e,#0891b2,#22c55e">Emerald</button><button type="button" class="button sbc-palette" data-colors="#ea580c,#dc2626,#f59e0b">Sunset</button></div><script>document.addEventListener('click',function(e){if(!e.target.classList.contains('sbc-palette'))return;var c=e.target.dataset.colors.split(','),f=document.querySelectorAll('input[type=color]');f.forEach(function(x,i){x.value=c[i]})})</script></td></tr>
                    <tr><th scope="row">Chat button positioning</th><td><fieldset><legend class="screen-reader-text">Chat button positioning</legend><div style="display:flex;gap:16px;flex-wrap:wrap;align-items:end"><label>Screen side<br><select name="sbc_widget_side"><option value="right" <?php selected(get_option('sbc_widget_side','right'),'right');?>>Right</option><option value="left" <?php selected(get_option('sbc_widget_side','right'),'left');?>>Left</option></select></label><label>Desktop side spacing (px)<br><input type="number" min="0" max="500" name="sbc_widget_horizontal_offset" value="<?php echo esc_attr(get_option('sbc_widget_horizontal_offset',20));?>"></label><label>Desktop bottom spacing (px)<br><input type="number" min="0" max="500" name="sbc_widget_bottom_offset" value="<?php echo esc_attr(get_option('sbc_widget_bottom_offset',20));?>"></label><label>Mobile side spacing (px)<br><input type="number" min="0" max="120" name="sbc_widget_mobile_horizontal_offset" value="<?php echo esc_attr(get_option('sbc_widget_mobile_horizontal_offset',16));?>"></label><label>Mobile bottom spacing (px)<br><input type="number" min="0" max="200" name="sbc_widget_mobile_bottom_offset" value="<?php echo esc_attr(get_option('sbc_widget_mobile_bottom_offset',16));?>"></label></div><p class="description">Controls the floating customer-chat button wherever <code>[sbc_bonus_chat]</code> is placed. The opened mobile chat still fills the phone screen.</p></fieldset></td></tr>
                    <?php $support_icon_id=absint(get_option('sbc_support_icon_id',0));$support_icon_url=$support_icon_id?wp_get_attachment_image_url($support_icon_id,'thumbnail'):SBC_PLUGIN_URL.'assets/images/blupass-icon.png';?>
                    <tr><th scope="row">Customer chatbot branding</th><td><label>Support name<br><input type="text" class="regular-text" maxlength="60" name="sbc_support_name" value="<?php echo esc_attr(get_option('sbc_support_name','BluPass Support'));?>" placeholder="BluPass Support"></label><div style="display:flex;align-items:center;gap:14px;margin-top:14px"><img id="sbc-support-icon-preview" src="<?php echo esc_url($support_icon_url);?>" alt="Support icon preview" style="width:72px;height:72px;border-radius:18px;object-fit:cover;border:1px solid #dcdcde;background:#fff"><div><input type="hidden" id="sbc_support_icon_id" name="sbc_support_icon_id" value="<?php echo (int)$support_icon_id;?>"><button type="button" class="button" id="sbc-choose-support-icon">Choose icon</button> <button type="button" class="button" id="sbc-remove-support-icon">Use BluPass default</button><p class="description">Recommended: square PNG or WebP, exactly 512 × 512 pixels, preferably with a transparent background.</p></div></div></td></tr>
                    <tr><th scope="row">Automatic chatbot targeting</th><td><fieldset><label>Display method<br><select name="sbc_display_mode" id="sbc-display-mode"><option value="shortcode" <?php selected(get_option('sbc_display_mode','shortcode'),'shortcode');?>>Shortcode only</option><option value="all" <?php selected(get_option('sbc_display_mode','shortcode'),'all');?>>Automatically on every page</option><option value="urls" <?php selected(get_option('sbc_display_mode','shortcode'),'urls');?>>Automatically on selected URLs</option><option value="templates" <?php selected(get_option('sbc_display_mode','shortcode'),'templates');?>>Automatically when template files are loaded</option></select></label><p><label><input type="checkbox" name="sbc_display_logged_in_only" value="1" <?php checked((int)get_option('sbc_display_logged_in_only',0),1);?>> Show automatic chatbot only to logged-in customers</label></p><div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:16px;max-width:900px"><label>Selected URL patterns<br><textarea class="large-text code" rows="5" name="sbc_display_include_urls" placeholder="/dashboard/*&#10;/buy-data/"><?php echo esc_textarea(get_option('sbc_display_include_urls',''));?></textarea><span class="description">One per line. Use <code>*</code> as a wildcard.</span></label><label>Excluded URL patterns<br><textarea class="large-text code" rows="5" name="sbc_display_exclude_urls" placeholder="/checkout/*&#10;/privacy-policy/"><?php echo esc_textarea(get_option('sbc_display_exclude_urls',''));?></textarea><span class="description">Applied to every automatic mode.</span></label><label>Target template filenames<br><textarea class="large-text code" rows="5" name="sbc_display_template_files" placeholder="top.html"><?php echo esc_textarea(get_option('sbc_display_template_files','top.html'));?></textarea><span class="description">One filename or path ending per line, such as <code>top.html</code>. BluPass detects included templates without editing VTUpress files.</span></label></div><p class="description">Shortcodes continue to work in every mode. Automatic output is skipped when the shortcode already rendered on the page.</p></fieldset></td></tr>
                </table>
                <?php submit_button(); ?>
            </form>
            <script>(function(){var choose=document.getElementById('sbc-choose-support-icon'),remove=document.getElementById('sbc-remove-support-icon'),field=document.getElementById('sbc_support_icon_id'),preview=document.getElementById('sbc-support-icon-preview'),fallback=<?php echo wp_json_encode(SBC_PLUGIN_URL.'assets/images/blupass-icon.png');?>,frame;if(choose)choose.addEventListener('click',function(e){e.preventDefault();if(frame){frame.open();return;}frame=wp.media({title:'Choose chatbot icon (512 × 512)',button:{text:'Use this icon'},library:{type:'image'},multiple:false});frame.on('select',function(){var item=frame.state().get('selection').first().toJSON();field.value=item.id;preview.src=(item.sizes&&item.sizes.thumbnail?item.sizes.thumbnail.url:item.url);});frame.open();});if(remove)remove.addEventListener('click',function(e){e.preventDefault();field.value='0';preview.src=fallback;});})();</script>
        </div>
        <?php
    }

    public function enqueue_frontend() {
        $primary=sanitize_hex_color(get_option('sbc_primary_color','#0867ed'))?:'#0867ed';$secondary=sanitize_hex_color(get_option('sbc_secondary_color','#4f46e5'))?:'#4f46e5';$accent=sanitize_hex_color(get_option('sbc_accent_color','#14b8a6'))?:'#14b8a6';
        $widget_side=in_array(get_option('sbc_widget_side','right'),['left','right'],true)?get_option('sbc_widget_side','right'):'right';
        $widget_horizontal=max(0,min(500,absint(get_option('sbc_widget_horizontal_offset',20))));$widget_bottom=max(0,min(500,absint(get_option('sbc_widget_bottom_offset',20))));$widget_mobile_horizontal=max(0,min(120,absint(get_option('sbc_widget_mobile_horizontal_offset',16))));$widget_mobile_bottom=max(0,min(200,absint(get_option('sbc_widget_mobile_bottom_offset',16))));
        $widget_horizontal_css=$widget_side==='left'?"left:{$widget_horizontal}px;right:auto;":"right:{$widget_horizontal}px;left:auto;";
        $widget_mobile_horizontal_css=$widget_side==='left'?"left:{$widget_mobile_horizontal}px;right:auto;":"right:{$widget_mobile_horizontal}px;left:auto;";
        wp_enqueue_script(
            'sbc-widget',
            plugins_url('assets/js/sbc-widget.js', __FILE__),
            ['jquery'],
            '1.0.3',
            true
        );

        wp_localize_script('sbc-widget', 'SBC_CHAT', [
            'ajaxUrl'            => admin_url('admin-ajax.php'),
            'nonce'              => wp_create_nonce('sbc_nonce'),
            'timeout'            => (int) get_option('sbc_timeout_seconds', 60),
            'bonus'              => (int) get_option('sbc_bonus_amount', 50),
            'currency'           => '₦',
            'resetVersion'       => $this->reset_version(),
            'withdrawalThreshold'=> (int) get_option('sbc_withdrawal_threshold', 500),
            'loggedIn'          => is_user_logged_in(),
            'loginUrl'          => wp_login_url(get_permalink()),
            'complaintServices' => array_map(function($key,$label){return ['key'=>$key,'label'=>$label];},array_keys($this->enabled_complaint_services()),array_values($this->enabled_complaint_services())),
        ]);

        wp_register_style('sbc-style', false);
        wp_enqueue_style('sbc-style');

        wp_add_inline_style('sbc-style', <<<CSS
#sbc-root{
    --sbc-primary:{$primary};
    --sbc-secondary:{$secondary};
    --sbc-accent:{$accent};
    position:fixed;
    {$widget_horizontal_css}
    bottom:{$widget_bottom}px;
    z-index:999999;
    font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
}

#sbc-open{
    position:relative;
    background:linear-gradient(135deg,var(--sbc-primary) 0%,var(--sbc-secondary) 100%);
    color:#fff;
    border:0;
    border-radius:999px;
    padding:14px 22px;
    cursor:pointer;
    font-size:14px;
    font-weight:600;
    box-shadow:0 10px 30px rgba(79,107,255,.35);
    display:flex;
    align-items:center;
    gap:8px;
    transition:transform .15s ease, box-shadow .15s ease;
}
#sbc-open:hover{ transform:translateY(-2px); box-shadow:0 14px 34px rgba(79,107,255,.42); }
#sbc-open .sbc-open-dot{
    width:8px; height:8px; border-radius:50%; background:#5CFFB1;
    box-shadow:0 0 0 0 rgba(92,255,177,.6);
    animation:sbcPulse 1.8s infinite;
}
#sbc-open-badge{
    position:absolute;
    top:-6px;
    right:-6px;
    background:#ef4444;
    color:#fff;
    font-size:11px;
    font-weight:700;
    min-width:20px;
    height:20px;
    border-radius:999px;
    display:none;
    align-items:center;
    justify-content:center;
    padding:0 5px;
    border:2px solid #fff;
}
@keyframes sbcPulse{
    0%{ box-shadow:0 0 0 0 rgba(92,255,177,.55); }
    70%{ box-shadow:0 0 0 8px rgba(92,255,177,0); }
    100%{ box-shadow:0 0 0 0 rgba(92,255,177,0); }
}

#sbc-panel{
    width:400px;
    height:700px;
    max-width:calc(100vw - 20px);
    max-height:calc(100vh - 20px);
    background:#fff;
    border-radius:20px;
    box-shadow:0 20px 60px rgba(20,20,50,.25);
    margin-top:10px;
    overflow:hidden;
    display:flex;
    flex-direction:column;
    opacity:0;
    transform:translateY(16px) scale(.98);
    transition:opacity .18s ease, transform .18s ease;
}
#sbc-panel.sbc-open-anim{
    opacity:1;
    transform:translateY(0) scale(1);
}

.sbc-head{
    background:linear-gradient(135deg,var(--sbc-primary) 0%,var(--sbc-secondary) 100%);
    color:#fff;
    padding:16px 18px;
    font-weight:700;
    display:flex;
    align-items:center;
    gap:12px;
}

.sbc-bot-avatar{
    position:relative;
    width:40px;
    height:40px;
    border-radius:50%;
    background:rgba(255,255,255,.18);
    color:#fff;
    display:flex;
    align-items:center;
    justify-content:center;
    font-weight:700;
    flex-shrink:0;
    font-size:16px;
}
.sbc-bot-avatar img{width:88%;height:88%;object-fit:contain;display:block}
.sbc-bot-avatar .sbc-online-dot{
    position:absolute;
    right:-1px;
    bottom:-1px;
    width:11px;
    height:11px;
    border-radius:50%;
    background:#5CFFB1;
    border:2px solid var(--sbc-primary);
}

.sbc-head-title{ font-size:15px; }
.sbc-head-sub{ font-size:12px; font-weight:400; opacity:.9; margin-top:2px; }

.sbc-head-actions{ margin-left:auto; display:flex; gap:6px; }
.sbc-head-icon-btn{
    background:rgba(255,255,255,.16);
    border:0;
    color:#fff;
    width:30px;
    height:30px;
    border-radius:50%;
    cursor:pointer;
    font-size:15px;
    display:flex;
    align-items:center;
    justify-content:center;
    line-height:1;
}
.sbc-head-icon-btn:hover{ background:rgba(255,255,255,.28); }
.sbc-head-icon-btn.sbc-muted{ opacity:.55; }
#sbc-minimize{font-size:23px;font-weight:400;padding:0 0 2px}

.sbc-inner{
    padding:10px 12px 12px;
    display:flex;
    flex-direction:column;
    flex:1;
    min-height:0;
    box-sizing:border-box;
    gap:7px;
    background:#f6f7fb;
}

.sbc-banner{
    background:#fff8e1;
    border:1px solid #f5d98a;
    color:#7a5b00;
    padding:7px 10px;
    border-radius:10px;
    font-size:12px;
    display:none;
}

.sbc-wallet{
    background:#fff;
    border:1px solid #e7e9f5;
    padding:8px 10px;
    border-radius:11px;
    font-size:11px;
    display:none;
    box-shadow:0 2px 8px rgba(20,20,60,.04);
    grid-template-columns:auto auto 1fr auto;
    align-items:center;
    gap:4px 9px;
}

.sbc-wallet strong{
    display:inline;
    margin:0;
    color:#111827;
}

.sbc-wallet-amount{
    font-size:17px;
    font-weight:800;
    background:linear-gradient(135deg,var(--sbc-primary),var(--sbc-secondary));
    -webkit-background-clip:text;
    background-clip:text;
    color:transparent;
}

.sbc-wallet .sbc-btn{
    margin:0;
    padding:7px 10px;
    font-size:11px;
}

#sbc-wallet-note{
    grid-column:1 / 4;
    margin-top:0 !important;
    line-height:1.25;
}

.sbc-timer,
.sbc-status{
    font-size:12px;
    color:#6b7280;
    padding:0 2px;
}

.sbc-timer:not(:empty){
    align-self:flex-start;
    padding:4px 9px;
    border-radius:999px;
    color:var(--sbc-primary);
    background:color-mix(in srgb,var(--sbc-primary) 9%,white);
    border:1px solid color-mix(in srgb,var(--sbc-primary) 22%,white);
    font-weight:800;
    font-variant-numeric:tabular-nums;
}

.sbc-messages{
    flex:1;
    min-height:280px;
    overflow-y:auto;
    -webkit-overflow-scrolling:touch;
    padding:4px 2px;
    border-radius:12px;
    position:relative;
}

.sbc-day-divider{
    text-align:center;
    font-size:11px;
    color:#9ca3af;
    margin:10px 0;
}

.sbc-msg-row{
    display:flex;
    margin-bottom:10px;
}
.sbc-msg-row.user{ justify-content:flex-end; }

.sbc-msg{
    font-size:14px;
    line-height:1.45;
    max-width:82%;
    padding:10px 13px;
    border-radius:16px;
    word-break:break-word;
    animation:sbcFadeIn .18s ease;
}
@keyframes sbcFadeIn{
    from{ opacity:0; transform:translateY(4px); }
    to{ opacity:1; transform:translateY(0); }
}

.sbc-msg strong{
    display:block;
    font-size:11px;
    margin-bottom:3px;
    opacity:.65;
}

.sbc-msg-time{
    display:block;
    font-size:10px;
    margin-top:4px;
    opacity:.55;
}

.sbc-msg.bot, .sbc-msg.system{
    background:#fff;
    border:1px solid #eceef7;
    border-bottom-left-radius:4px;
}

.sbc-msg.user{
    background:linear-gradient(135deg,var(--sbc-primary),var(--sbc-secondary));
    color:#fff;
    border-bottom-right-radius:4px;
}

.sbc-msg.agent{
    background:#fff;
    border:1px solid #d9f2e3;
    border-bottom-left-radius:4px;
}
.sbc-msg.agent strong{ color:var(--sbc-accent); opacity:1; }
.sbc-msg.bot strong{ color:var(--sbc-primary); opacity:1; }
.sbc-msg-row.warning{ justify-content:center; }
.sbc-msg.warning{
    max-width:94%;
    width:100%;
    color:#9b1c1c;
    background:#fff0f0;
    border:1px solid #f5a3a3;
    border-left:5px solid #dc2626;
    border-radius:10px;
    font-weight:700;
    text-align:center;
}

.sbc-typing-row{ display:flex; margin-bottom:10px; }
.sbc-typing-bubble{
    background:#fff;
    border:1px solid #eceef7;
    border-radius:16px;
    border-bottom-left-radius:4px;
    padding:12px 16px;
    display:flex;
    gap:4px;
}
.sbc-typing-bubble span{
    width:6px; height:6px; border-radius:50%;
    background:#b6bbd0;
    animation:sbcBounce 1.2s infinite ease-in-out;
}
.sbc-typing-bubble span:nth-child(2){ animation-delay:.15s; }
.sbc-typing-bubble span:nth-child(3){ animation-delay:.3s; }
@keyframes sbcBounce{
    0%,60%,100%{ transform:translateY(0); opacity:.5; }
    30%{ transform:translateY(-4px); opacity:1; }
}

.sbc-jump-bottom{
    position:absolute;
    bottom:8px;
    left:50%;
    transform:translateX(-50%);
    background:#111827;
    color:#fff;
    border:0;
    border-radius:999px;
    padding:6px 14px;
    font-size:12px;
    cursor:pointer;
    display:none;
    box-shadow:0 6px 18px rgba(0,0,0,.2);
}

.sbc-actions{
    display:flex;
    flex-wrap:wrap;
    gap:8px;
}

.sbc-option-btn{
    background:#fff;
    border:1px solid color-mix(in srgb,var(--sbc-primary) 24%,white);
    color:var(--sbc-primary);
    border-radius:999px;
    padding:9px 13px;
    cursor:pointer;
    font-size:13px;
    font-weight:600;
    transition:.12s ease;
}
.sbc-login-link{ text-decoration:none; font-size:13px; padding:10px 18px; }
.sbc-transaction-option{
    flex:0 0 100%; display:flex; flex-direction:column; gap:4px; padding:10px 12px;
    text-align:left; border:1px solid #e2e5ef; border-radius:10px; background:#fff;
    color:#272d3f; cursor:pointer; transition:.15s ease;
}
.sbc-transaction-option:hover{ border-color:var(--sbc-primary); background:color-mix(in srgb,var(--sbc-primary) 6%,white); transform:translateY(-1px); }
.sbc-transaction-option strong{ font-size:12px; }
.sbc-transaction-option span{ color:#7c8291; font-size:10px; text-transform:capitalize; }
.sbc-transaction-pager{ position:sticky; top:0; z-index:3; flex:0 0 100%; display:flex; flex-wrap:wrap; gap:7px; padding:3px 2px 8px; background:#f8f9fd; }
.sbc-option-btn.sbc-option-muted{ color:#6b7280; border-color:#e5e7eb; background:#f8fafc; }
.sbc-option-btn:hover{ background:color-mix(in srgb,var(--sbc-primary) 9%,white); }

.sbc-rating{
    display:flex;
    gap:6px;
    justify-content:center;
    margin:6px 0;
}
.sbc-star{
    font-size:26px;
    cursor:pointer;
    color:#d1d5db;
    transition:.1s ease;
}
.sbc-star.active, .sbc-star:hover, .sbc-star.hover{ color:#f5b400; transform:scale(1.08); }

.sbc-input-row{
    display:flex;
    gap:8px;
    align-items:flex-end;
    flex:0 0 auto;
}

#sbc-input-field{
    width:100%;
    border:1px solid #dfe2f2;
    border-radius:12px;
    padding:10px 12px;
    box-sizing:border-box;
    resize:vertical;
    min-height:42px;
    font-family:inherit;
}
#sbc-input-field:focus{ outline:2px solid color-mix(in srgb,var(--sbc-primary) 25%,white); border-color:var(--sbc-primary); }

#sbc-input-field.sbc-textarea{
    min-height:70px;
}

.sbc-btn{
    background:linear-gradient(135deg,var(--sbc-primary),var(--sbc-secondary));
    color:#fff;
    border:0;
    border-radius:12px;
    padding:10px 16px;
    cursor:pointer;
    white-space:nowrap;
    font-weight:600;
    font-size:13px;
}
.sbc-btn:disabled{ opacity:.6; cursor:default; }

.sbc-btn-secondary{
    background:color-mix(in srgb,var(--sbc-primary) 9%,white);
    color:var(--sbc-primary);
}

.sbc-btn-danger{
    background:#fdeaea;
    color:#dc2626;
}

/* Customer support experience */
#sbc-panel{
    width:440px;
    height:min(760px, calc(100vh - 90px));
    border-radius:26px;
    background:#f8f9fd;
    box-shadow:0 25px 70px rgba(36,31,91,.24);
}
.sbc-head{
    min-height:78px;
    padding:16px 18px 30px;
    background:linear-gradient(120deg,var(--sbc-primary) 0%,var(--sbc-secondary) 100%);
    border-radius:0 0 32px 32px;
    flex-wrap:wrap;
}
.sbc-bot-avatar{
    width:48px; height:48px; font-size:21px;
    background:rgba(255,255,255,.18);
}
.sbc-head-title{ font-size:20px; letter-spacing:-.01em; }
.sbc-head-sub{ font-size:12px; opacity:.9; }
.sbc-head-icon-btn{
    width:43px; height:43px; border-radius:13px;
    background:rgba(255,255,255,.14); font-size:17px;
}
.sbc-inner{ padding:26px 17px 10px; gap:6px; background:#f8f9fd; }
.sbc-wallet{
    position:relative; z-index:2; margin:9px 0 -22px; padding:10px 12px;
    grid-template-columns:44px 1fr auto; gap:7px 10px;
    border:1px solid #eeeefa; border-radius:15px;
    box-shadow:0 8px 23px rgba(34,30,83,.08); background:#fff;
}
.sbc-head .sbc-wallet{ flex:0 0 100%; width:100%; box-sizing:border-box; color:#20243a; }
.sbc-wallet-icon{
    display:flex; align-items:center; justify-content:center;
    width:42px; height:42px; grid-row:1; border-radius:11px;
    color:var(--sbc-primary); background:color-mix(in srgb,var(--sbc-primary) 10%,white); font-size:20px; font-weight:800;
}
.sbc-wallet-main{ grid-column:2; grid-row:1; }
.sbc-wallet-main strong{ display:block; color:#20243a; font-size:11px; }
.sbc-wallet-amount{ margin-top:2px; font-size:22px; }
#sbc-wallet-note{
    grid-column:1 / -1; grid-row:2; padding:7px 3px 0 31px;
    border-top:1px solid #f0f1f5; color:#7e8493!important; font-size:11px;
}
#sbc-wallet-note:before{ content:'▣'; margin-left:-27px; margin-right:10px; color:#55d5a0; }
.sbc-wallet .sbc-btn{ grid-column:3; grid-row:1; padding:8px 11px; border:1px solid color-mix(in srgb,var(--sbc-primary) 30%,white); background:#fff; color:var(--sbc-primary); }
.sbc-banner{ border:0; background:#fff5d9; color:#7b5a08; text-align:center; }
.sbc-timer:not(:empty){ align-self:center; background:color-mix(in srgb,var(--sbc-primary) 9%,white); border-color:color-mix(in srgb,var(--sbc-primary) 22%,white); }
.sbc-status{ text-align:center; }
.sbc-inner{ overflow:hidden; }
.sbc-messages{ min-height:80px; padding:7px 2px 3px; }
.sbc-msg-row{ align-items:flex-start; gap:8px; margin-bottom:13px; }
.sbc-message-avatar{
    display:flex; align-items:center; justify-content:center; flex:0 0 31px;
    width:31px; height:31px; margin-top:3px; border-radius:50%;
    color:#fff; background:linear-gradient(135deg,var(--sbc-primary),var(--sbc-secondary)); font-size:13px; font-weight:700;
}
.sbc-msg{ max-width:76%; padding:11px 13px; border-radius:14px; box-shadow:0 3px 12px rgba(28,32,57,.07); }
.sbc-msg.bot,.sbc-msg.system,.sbc-msg.agent{ background:#fff; border:1px solid #f0f1f5; border-bottom-left-radius:5px; }
.sbc-msg.user{
    background:linear-gradient(135deg,var(--sbc-primary),var(--sbc-secondary));
    border-bottom-right-radius:5px; box-shadow:0 5px 14px rgba(91,59,225,.18);
}
.sbc-msg strong{ color:var(--sbc-primary)!important; opacity:1!important; }
.sbc-msg.user strong{ color:#fff!important; }
.sbc-msg-time{ display:inline-block; margin-right:6px; }
.sbc-delivered{ float:right; margin-top:4px; font-size:11px; opacity:.82; }
.sbc-actions{ flex:0 1 auto; max-height:min(245px, 36vh); overflow-y:auto; padding:6px 3px; gap:7px; }
.sbc-category-label{ flex:0 0 100%; color:#5f6474; font-size:11px; font-weight:600; }
.sbc-option-btn{
    padding:8px 11px; border-radius:9px; border-color:#e0e3eb;
    color:var(--sbc-primary); background:#fff; font-size:11px;
}
.sbc-input-row{
    padding:7px; border:1px solid #e7e8f0; border-radius:13px;
    background:#fff; box-shadow:0 3px 14px rgba(31,35,64,.05);
}
#sbc-input-wrap{ flex-shrink:0; }
#sbc-input-field{ min-height:40px; border:0; border-radius:8px; padding:10px; resize:none; box-shadow:none; outline:none; }
#sbc-input-field.sbc-textarea{ min-height:48px; max-height:88px; }
.sbc-input-row .sbc-btn{ align-self:center; padding:11px 14px; border-radius:9px; background:linear-gradient(135deg,var(--sbc-primary),var(--sbc-secondary)); }
.sbc-input-row .sbc-btn-danger{ padding:8px 9px; background:#fff0f0; color:#d63838; }
.sbc-help-footer{ padding-top:2px; text-align:center; color:#8a8f9d; font-size:10px; }
.sbc-help-footer span{ color:var(--sbc-accent); font-size:12px; }
.sbc-help-footer small{display:block;margin-top:1px;font-size:9px;color:#a0a6b2}

@media (max-width: 520px){
    #sbc-root{right:0;bottom:0;left:0;top:0;pointer-events:none}
    #sbc-open{position:fixed;{$widget_mobile_horizontal_css}bottom:{$widget_mobile_bottom}px;pointer-events:auto}
    #sbc-panel{position:fixed;inset:0;width:100vw;height:100dvh;max-width:none;max-height:none;margin:0;border-radius:0;box-shadow:none;pointer-events:auto;background:#fafbfe}
    .sbc-head{min-height:70px;padding:12px 14px 18px;gap:10px;border-radius:0;background:#fff;color:#171b2c;border-bottom:1px solid #eceef4;box-shadow:0 2px 14px rgba(22,28,45,.05)}
    .sbc-bot-avatar{width:46px;height:46px;background:linear-gradient(135deg,var(--sbc-primary),var(--sbc-secondary));color:#fff}
    .sbc-bot-avatar .sbc-online-dot{border-color:#fff;background:var(--sbc-accent)}
    .sbc-head-title{font-size:19px}.sbc-head-sub{font-size:12px;color:#73798a;opacity:1}
    .sbc-head-icon-btn{width:38px;height:38px;background:#f4f6fa;color:var(--sbc-primary);border-radius:50%}
    .sbc-inner{padding:27px 16px 10px;background:#fafbfe;gap:8px}
    .sbc-wallet{margin:8px 0 -18px;padding:13px 14px;grid-template-columns:46px 1fr auto;border-radius:18px;box-shadow:0 8px 28px rgba(31,41,72,.08)}
    .sbc-wallet-icon{width:44px;height:44px}.sbc-wallet .sbc-btn{padding:10px 12px;border-color:var(--sbc-primary);color:var(--sbc-primary)}
    .sbc-messages{min-height:100px;padding-top:10px}.sbc-msg{font-size:14px;line-height:1.5;padding:12px 14px;max-width:78%}
    .sbc-message-avatar{width:36px;height:36px;flex-basis:36px}.sbc-actions{max-height:38vh;padding-bottom:8px}.sbc-category-label{font-size:13px;color:#252b3c;margin:4px 0}
    .sbc-option-btn{min-height:46px;padding:11px 13px;font-size:13px;border-radius:12px}.sbc-input-row{padding:8px;border-radius:15px}.sbc-help-footer{font-size:11px;padding:5px 0}
}
CSS);
    }

    public function shortcode() {
        if($this->shortcode_rendered)return '';
        $this->shortcode_rendered=true;
        // Settings remain accessible to the site owner, while the customer
        // launcher stays hidden until a token bucket can fund a new chat.
        if (function_exists('sbc_ops') && !sbc_ops()->can_start_chat()) return '';
        $support_name=trim((string)get_option('sbc_support_name','BluPass Support'));if($support_name==='')$support_name='BluPass Support';
        $support_icon_id=absint(get_option('sbc_support_icon_id',0));$support_icon_url=$support_icon_id?wp_get_attachment_image_url($support_icon_id,'thumbnail'):'';if(!$support_icon_url)$support_icon_url=SBC_PLUGIN_URL.'assets/images/blupass-icon.png';
        ob_start(); ?>
        <div id="sbc-root">
            <button type="button" id="sbc-open">
                <span class="sbc-open-dot"></span>
                Chat with us
                <span id="sbc-open-badge"></span>
            </button>

            <div id="sbc-panel" style="display:none;">
                <div class="sbc-head">
                    <div class="sbc-bot-avatar"><img src="<?php echo esc_url($support_icon_url); ?>" alt="<?php echo esc_attr($support_name);?>"><span class="sbc-online-dot"></span></div>
                    <div>
                        <div class="sbc-head-title"><?php echo esc_html($support_name);?></div>
                        <div class="sbc-head-sub">Typically replies in a few minutes</div>
                    </div>
                    <div class="sbc-head-actions">
                        <button type="button" id="sbc-sound-toggle" class="sbc-head-icon-btn" title="Toggle sound">🔔</button>
                        <button type="button" id="sbc-minimize" class="sbc-head-icon-btn" title="Close chat" aria-label="Close chat">×</button>
                    </div>

                    <div id="sbc-wallet" class="sbc-wallet">
                        <span class="sbc-wallet-icon">▣</span>
                        <div class="sbc-wallet-main"><strong>Wallet balance</strong><div id="sbc-wallet-text" class="sbc-wallet-amount"></div></div>
                        <div id="sbc-wallet-note" style="margin-top:6px;color:#6b7280;"></div>
                        <button type="button" id="sbc-withdraw" class="sbc-btn sbc-btn-secondary" style="display:none;">Request Withdrawal</button>
                    </div>
                </div>

                <div class="sbc-inner">
                    <div id="sbc-banner" class="sbc-banner"></div>

                    <div id="sbc-timer" class="sbc-timer"></div>
                    <div id="sbc-status" class="sbc-status"></div>

                    <div id="sbc-messages" class="sbc-messages">
                        <button type="button" class="sbc-jump-bottom" id="sbc-jump-bottom">New messages ↓</button>
                    </div>
                    <div id="sbc-actions" class="sbc-actions"></div>

                    <div id="sbc-input-wrap" class="sbc-input-row">
                        <textarea id="sbc-input-field" placeholder=""></textarea>
                        <button type="button" id="sbc-send" class="sbc-btn">Send</button>
                        <button type="button" id="sbc-end-chat" class="sbc-btn sbc-btn-danger" style="display:none;">End Chat</button>
                    </div>
                    <div class="sbc-help-footer">Powered by BluPass <span>♥</span><small>Developed by Bluishgen</small></div>
                </div>
            </div>
        </div>
        <?php
        return ob_get_clean();
    }

    private function load_messages($chat_id) {
        global $wpdb;
        $t = $this->tables();

        return $wpdb->get_results(
            $wpdb->prepare(
                "SELECT id, sender_type, sender_label, sender_department, message, created_at
                 FROM {$t['msgs']}
                 WHERE chat_id = %d
                 ORDER BY id ASC",
                $chat_id
            ),
            ARRAY_A
        );
    }

    private function customer_key($email = '', $user_id = 0) {
        if ($user_id) {
            return 'u_' . absint($user_id);
        }

        $email = sanitize_email($email);
        if ($email) {
            return 'e_' . md5(strtolower($email));
        }

        return '';
    }

    private function get_user_name_by_email($email) {
        $user = get_user_by('email', $email);
        if (!$user) {
            return [0, ''];
        }

        $full_name = trim(
            (string) get_user_meta($user->ID, 'first_name', true) . ' ' .
            (string) get_user_meta($user->ID, 'last_name', true)
        );

        if (!$full_name) {
            $full_name = $user->display_name ?: $user->user_login;
        }

        return [(int) $user->ID, $full_name];
    }

    private function find_open_chat($customer_key) {
        global $wpdb;
        $t = $this->tables();

        return $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM {$t['chats']}
                 WHERE customer_key = %s
                 AND status IN ('waiting','active')
                 ORDER BY updated_at DESC, id DESC
                 LIMIT 1",
                $customer_key
            )
        );
    }

    private function chat_payload($chat) {
        $cooldown = $this->bonus_cooldown_status($chat->customer_key);
        return [
            'chat_id'              => (int) $chat->id,
            'chat_token'           => $chat->session_token,
            'status'               => $chat->status,
            'service_type'         => $chat->service_type,
            'initial_message'      => $chat->initial_message,
            'expires_ts'           => (int) $chat->timer_expires_ts,
            'eligible'             => (float) $chat->compensation_amount > 0,
            'bonus_amount'         => (float) $chat->compensation_amount,
            'compensation_awarded' => (int) $chat->compensation_awarded,
            'bonus_cooldown_message'=> (!$cooldown['available'] && !(int) $chat->compensation_awarded) ? $cooldown['message'] : '',
            'bonus_cooldown_remaining_seconds' => (int) $cooldown['remaining_seconds'],
            'agent_replied_at'     => $chat->agent_replied_at,
            'messages'             => $this->load_messages($chat->id),
        ];
    }

    /**
     * Looks up a user by email and, crucially, resumes any chat that is
     * already waiting/active for that person instead of forcing them back
     * through the intake questions every time they reconnect.
     */
    public function lookup_email() {
        check_ajax_referer('sbc_nonce', 'nonce');

        $this->require_logged_in_customer();

        $email = sanitize_email($_POST['email'] ?? '');

        if (!$email || !is_email($email)) {
            wp_send_json_error(['message' => 'Please enter a valid email address.']);
        }

        [$user_id, $full_name] = $this->get_user_name_by_email($email);

        if (!$user_id) {
            wp_send_json_error(['message' => 'Email not found. Please check and try again.']);
        }

        $user = get_user_by('id', $user_id);
        $customer_key = $this->customer_key($email, $user_id);

        $existing_chat = $this->find_open_chat($customer_key);

        wp_send_json_success([
            'user_id'       => $user_id,
            'full_name'     => $full_name,
            'email'         => $user ? $user->user_email : $email,
            'wallet'        => $this->wallet_status($user_id),
            'existing_chat' => $existing_chat ? $this->chat_payload($existing_chat) : null,
        ]);
    }

    public function get_support_context() {
        check_ajax_referer('sbc_nonce', 'nonce');
        $user_id = $this->require_logged_in_customer();
        $user = get_userdata($user_id);
        if (!$user) wp_send_json_error(['message' => 'Customer account was not found.']);

        $name = trim((string) $user->first_name . ' ' . (string) $user->last_name);
        if (!$name) $name = $user->display_name ?: $user->user_login;
        $key = $this->customer_key($user->user_email, $user_id);
        $open = $this->find_open_chat($key);
        $vtu = $this->vtupress_user($user_id);
        $cooldown = $this->bonus_cooldown_status($key);

        wp_send_json_success([
            'user_id' => $user_id,
            'full_name' => $name,
            'email' => $user->user_email,
            'wallet' => $this->wallet_status($user_id),
            'vtupress_connected' => (bool) $vtu,
            'vtupress_balance' => $vtu ? (float) $vtu->balance : 0,
            'existing_chat' => $open ? $this->chat_payload($open) : null,
            'bonus_available' => (bool) $cooldown['available'],
            'bonus_cooldown_message' => $cooldown['message'],
            'bonus_cooldown_remaining_seconds' => (int) $cooldown['remaining_seconds'],
        ]);
    }

    public function get_recent_transactions() {
        check_ajax_referer('sbc_nonce', 'nonce');
        $wp_user_id = $this->require_logged_in_customer();
        $service = sanitize_key($_POST['service'] ?? '');
        $page = max(1, min(25, absint($_POST['page'] ?? 1)));
        $per_page = 4;
        if (!isset($this->enabled_complaint_services()[$service])) {
            wp_send_json_error(['message' => 'Please choose a valid service.']);
        }

        $vtu = $this->vtupress_user($wp_user_id);
        if (!$vtu) wp_send_json_error(['message' => 'Your VTUpress account is not connected. Please contact an agent.']);

        global $wpdb;
        $table = $wpdb->prefix . 'vtupress_transactions';
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$table} WHERE user_id = %d AND service = %s ORDER BY created_at DESC, id DESC LIMIT %d OFFSET %d",
            (int) $vtu->id,
            $this->vtupress_service_value($service),
            $per_page + 1,
            ($page - 1) * $per_page
        ));

        $has_more = count($rows) > $per_page;
        if ($has_more) $rows = array_slice($rows, 0, $per_page);

        wp_send_json_success([
            'transactions' => array_map([$this, 'transaction_summary'], $rows ?: []),
            'page' => $page,
            'has_more' => $has_more,
            'has_previous' => $page > 1,
        ]);
    }

    public function start_chat() {
        // Keep AJAX output as valid JSON even when PHP warning display is enabled.
        @ini_set('display_errors', '0');
        check_ajax_referer('sbc_nonce', 'nonce');

        $authenticated_user_id = $this->require_logged_in_customer();
        $this->ensure_integration_schema();

        global $wpdb;
        $t = $this->tables();

        $chat_id          = absint($_POST['chat_id'] ?? 0);
        $token            = sanitize_text_field($_POST['chat_token'] ?? '');
        $email            = sanitize_email($_POST['email'] ?? '');
        $service          = sanitize_text_field($_POST['service'] ?? '');
        $initial_message  = sanitize_textarea_field($_POST['initial_message'] ?? '');
        $transaction_id   = absint($_POST['transaction_id'] ?? 0);
        $complaint_reason = sanitize_text_field($_POST['complaint_reason'] ?? 'Transaction support request');

        $current_user = get_userdata($authenticated_user_id);
        $email = $current_user ? $current_user->user_email : $email;

        if (!$email || !$service || !$initial_message) {
            wp_send_json_error(['message' => 'Email, service, and issue message are required.']);
        }
        if($service!=='other'&&!isset($this->enabled_complaint_services()[sanitize_key($service)]))wp_send_json_error(['message'=>'That complaint service is currently unavailable. Please choose another service.']);

        [$user_id, $full_name] = $this->get_user_name_by_email($email);

        if (!$user_id) {
            wp_send_json_error(['message' => 'We could not verify that email. Please try again.']);
        }

        $customer_key = $this->customer_key($email, $user_id);
        $transaction_snapshot = '';

        if ($transaction_id) {
            $vtu = $this->vtupress_user($authenticated_user_id);
            if (!$vtu) wp_send_json_error(['message' => 'Your VTUpress account is not connected.']);
            $tx_table = $wpdb->prefix . 'vtupress_transactions';
            $transaction = $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$tx_table} WHERE id = %d AND user_id = %d LIMIT 1",
                $transaction_id,
                (int) $vtu->id
            ));
            if (!$transaction) wp_send_json_error(['message' => 'That transaction could not be found on your account.']);
            if ($service !== sanitize_key((string) ($transaction->service ?? ''))) wp_send_json_error(['message' => 'The selected transaction does not match this service.']);

            $duplicate = $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$t['chats']} WHERE user_id = %d AND vtupress_transaction_id = %d AND status IN ('waiting','active') ORDER BY id DESC LIMIT 1",
                $authenticated_user_id,
                $transaction_id
            ));
            if ($duplicate) {
                wp_send_json_success(array_merge($this->chat_payload($duplicate), [
                    'duplicate' => true,
                    'wallet' => $this->wallet_status($authenticated_user_id),
                ]));
            }

            $transaction_snapshot = wp_json_encode([
                'summary' => $this->transaction_summary($transaction),
                'response_message' => (string) ($transaction->response_message ?? $transaction->response ?? $transaction->api_response ?? ''),
                'details' => json_decode((string) ($transaction->details ?? ''), true),
                'balance_before' => (string) ($transaction->balance_before ?? ''),
                'balance_now' => (string) ($transaction->balance_now ?? $transaction->balance_after ?? ''),
            ]);
        }
        $timeout      = max(10, (int) get_option('sbc_timeout_seconds', 60));
        $base_bonus   = max(0, (int) get_option('sbc_bonus_amount', 50));

        $cooldown_status = $this->bonus_cooldown_status($customer_key);
        $eligible_bonus = $cooldown_status['available'];
        $bonus_amount   = $eligible_bonus ? $base_bonus : 0;

        $now        = current_time('mysql');
        $expires_ts  = $eligible_bonus ? time() + $timeout : 0;
        // timer_expires_ts=0 is the authoritative no-timer signal. Supplying the
        // current timestamp also supports old tables that still reject NULL here.
        $expires_at  = $eligible_bonus ? gmdate('Y-m-d H:i:s', $expires_ts) : $now;

        $chat = null;

        if ($chat_id) {
            $possible = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
            if ($possible
                && (int) $possible->user_id === (int) $authenticated_user_id
                && !empty($possible->session_token)
                && hash_equals((string) $possible->session_token, (string) $token)
                && $possible->status !== 'closed') {
                $chat = $possible;
            }
        }

        if ($chat) {
            $saved = $wpdb->update($t['chats'], [
                'user_id'              => $user_id,
                'customer_email'       => $email,
                'full_name'            => $full_name,
                'service_type'         => $service,
                'initial_message'      => $initial_message,
                'status'               => 'waiting',
                'timer_started_at'     => $now,
                'timer_expires_at'     => $expires_at,
                'timer_expires_ts'     => $expires_ts,
                'compensation_awarded' => 0,
                'compensation_amount'  => $bonus_amount,
                'bonus_awarded_at'     => null,
                'vtupress_transaction_id' => $transaction_id,
                'complaint_reason'     => $complaint_reason,
                'transaction_snapshot'=> $transaction_snapshot,
                'admin_unread_count'   => (int) $chat->admin_unread_count + 1,
                'updated_at'           => $now,
            ], [
                'id' => $chat->id,
            ]);

            if ($saved === false) {
                error_log('BluPass could not update complaint: ' . $wpdb->last_error);
                wp_send_json_error(['message' => 'The support complaint could not be saved. Please try again.']);
            }

            $chat_id = (int) $chat->id;
            $token   = (string) $chat->session_token;
        } else {
            if (function_exists('sbc_ops') && !sbc_ops()->can_start_chat()) {
                wp_send_json_error(['message' => 'Support is temporarily unavailable. Please contact the website administrator.'], 402);
            }
            $token = wp_generate_password(32, false, false);

            $new_chat_data = [
                'user_id'              => $user_id,
                'customer_key'         => $customer_key,
                'session_token'        => $token,
                'customer_email'       => $email,
                'full_name'            => $full_name,
                'service_type'         => $service,
                'initial_message'      => $initial_message,
                'status'               => 'waiting',
                'timer_started_at'     => $now,
                'timer_expires_at'     => $expires_at,
                'timer_expires_ts'     => $expires_ts,
                'agent_replied_at'     => null,
                'closed_at'            => null,
                'admin_unread_count'   => 1,
                'compensation_awarded' => 0,
                'compensation_amount'  => $bonus_amount,
                'bonus_awarded_at'     => null,
                'customer_typing_at'   => null,
                'agent_typing_at'      => null,
                'vtupress_transaction_id' => $transaction_id,
                'complaint_reason'     => $complaint_reason,
                'transaction_snapshot'=> $transaction_snapshot,
                'created_at'           => $now,
                'updated_at'           => $now,
            ];
            $saved = $wpdb->insert($t['chats'], $new_chat_data);

            if (!$saved || !$wpdb->insert_id) {
                $first_db_error = (string) $wpdb->last_error;
                // Repair incomplete upgrades, then retry this insert exactly once.
                $this->install_tables();
                if (function_exists('sbc_ops')) sbc_ops()->repair_schema();
                $saved = $wpdb->insert($t['chats'], $new_chat_data);
                if (!$saved || !$wpdb->insert_id) {
                    $retry_db_error=(string)$wpdb->last_error;
                    update_option('sbc_last_database_error',['first'=>$first_db_error,'retry'=>$retry_db_error,'time'=>current_time('mysql')],false);
                    // Last-resort compatibility for an old table whose ALTER permission is blocked:
                    // insert every supported field instead of failing on newer optional columns.
                    $column_rows=(array)$wpdb->get_results("SHOW FULL COLUMNS FROM {$t['chats']}",ARRAY_A);
                    $available_columns=array_column($column_rows,'Field');
                    $compatible_data=array_intersect_key($new_chat_data,array_flip($available_columns));
                    foreach($column_rows as $column){
                        $field=(string)($column['Field']??'');$extra=strtolower((string)($column['Extra']??''));
                        if(!$field||strpos($extra,'auto_increment')!==false)continue;
                        $required=strtoupper((string)($column['Null']??'YES'))==='NO'&&$column['Default']===null;
                        if($required&&array_key_exists($field,$compatible_data)&&$compatible_data[$field]===null){
                            $type=strtolower((string)($column['Type']??''));
                            $compatible_data[$field]=strpos($type,'date')!==false?$now:(preg_match('/^(tinyint|smallint|mediumint|int|bigint|decimal|float|double|bit)/',$type)?0:'');
                        }elseif($required&&!array_key_exists($field,$compatible_data)){
                            $type=strtolower((string)($column['Type']??''));
                            $compatible_data[$field]=strpos($type,'date')!==false?$now:(preg_match('/^(tinyint|smallint|mediumint|int|bigint|decimal|float|double|bit)/',$type)?0:'');
                        }
                    }
                    $saved=$compatible_data?$wpdb->insert($t['chats'],$compatible_data):false;
                    if(!$saved||!$wpdb->insert_id){
                        $final_db_error=(string)$wpdb->last_error;
                        update_option('sbc_last_database_error',['first'=>$first_db_error,'retry'=>$retry_db_error,'final'=>$final_db_error,'time'=>current_time('mysql')],false);
                        error_log('BluPass database insert failed. First: '.$first_db_error.' Retry: '.$retry_db_error.' Compatibility: '.$final_db_error);
                        $public_message='The support database rejected this complaint. The administrator can now see the exact database reason in BluPass.';
                        if(current_user_can('manage_options'))$public_message.=' Database reason: '.($final_db_error?:$retry_db_error?:$first_db_error);
                        wp_send_json_error(['message'=>$public_message,'error_code'=>'SBC-DB-CHAT']);
                    }
                }
            }

            $chat_id = (int) $wpdb->insert_id;
            if (function_exists('sbc_ops')) {
                $token_debit = sbc_ops()->debit_chat($chat_id, 'chat-' . $chat_id);
                if (is_wp_error($token_debit)) {
                    $wpdb->delete($t['chats'], ['id' => $chat_id]);
                    wp_send_json_error(['message' => $token_debit->get_error_message()], 402);
                }
                $wpdb->update($t['chats'], [
                    'token_debit' => (float) $token_debit,
                    'last_human_activity_at' => $now,
                ], ['id' => $chat_id]);
            }
            $this->prune_customer_history($customer_key);
        }

        delete_option('sbc_last_database_error');

        $intro = $eligible_bonus
            ? "Please hold while I call an agent. If no agent contacts you in {$timeout} seconds, you will receive {$bonus_amount} bonus."
            : "Please hold while I call an agent. " . $cooldown_status['message'];

        $wpdb->insert($t['msgs'], [
            'chat_id'     => $chat_id,
            'sender_type' => 'customer',
            'message'     => $initial_message,
            'created_at'  => $now,
        ]);

        $wpdb->insert($t['msgs'], [
            'chat_id'     => $chat_id,
            'sender_type' => 'system',
            'message'     => $intro,
            'created_at'  => $now,
        ]);

        $this->send_mobile_push(
            'New support complaint',
            ($full_name ?: $email) . ' selected ' . $service,
            $chat_id
        );

        wp_send_json_success([
            'chat_id'      => $chat_id,
            'chat_token'   => $token,
            'status'       => 'waiting',
            'eligible'     => $eligible_bonus,
            'bonus_amount' => $bonus_amount,
            'expires_ts'   => $expires_ts,
            'bonus_cooldown_message' => $eligible_bonus ? '' : $cooldown_status['message'],
            'bonus_cooldown_remaining_seconds' => (int) $cooldown_status['remaining_seconds'],
            'messages'     => $this->load_messages($chat_id),
            'wallet'       => $this->wallet_status($user_id),
        ]);
    }

    public function database_repair_notice(){
        if(!current_user_can('manage_options'))return;$error=get_option('sbc_last_database_error');if(!is_array($error)||empty($error['retry']))return;
        $details=array_filter([$error['first']??'',$error['retry']??'',$error['final']??'']);
        echo '<div class="notice notice-error"><p><strong>BluPass database diagnostic:</strong> '.esc_html(implode(' | ',$details)).'</p><p>This notice is visible only to administrators.</p></div>';
    }

    public function send_message() {
        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();

        $chat_id = absint($_POST['chat_id'] ?? 0);
        $token   = sanitize_text_field($_POST['chat_token'] ?? '');
        $message = sanitize_textarea_field($_POST['message'] ?? '');

        if (!$chat_id || !$token || !$message) {
            wp_send_json_error(['message' => 'Chat ID, token, and message are required.']);
        }

        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
        if (!$chat) {
            wp_send_json_error(['message' => 'Chat not found.']);
        }

        $this->require_chat_owner($chat);

        if (!hash_equals((string) $chat->session_token, (string) $token)) {
            wp_send_json_error(['message' => 'Invalid chat token.']);
        }

        // Enforce the inactivity timeline during normal customer polling as
        // well as WP-Cron, which may run late on low-traffic websites.
        if(function_exists('sbc_ops')){
            sbc_ops()->check_inactivity($chat_id);
            $chat=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d",$chat_id));
        }

        if ($chat->status === 'closed' || $chat->status === 'collecting') {
            wp_send_json_error(['message' => 'This chat is not active yet.']);
        }

        $now = current_time('mysql');

        $wpdb->insert($t['msgs'], [
            'chat_id'     => $chat_id,
            'sender_type' => 'customer',
            'message'     => $message,
            'created_at'  => $now,
        ]);

        $wpdb->update($t['chats'], [
            'updated_at'         => $now,
            'customer_typing_at' => null,
            'admin_unread_count' => (int) $chat->admin_unread_count + 1,
            'last_human_activity_at' => $now,
            'inactivity_warned_at' => null,
        ], [
            'id' => $chat_id,
        ]);

        $this->send_mobile_push('New customer message', ($chat->full_name ?: $chat->customer_email) . ': ' . wp_trim_words($message, 12), $chat_id);

        wp_send_json_success([
            'messages' => $this->load_messages($chat_id),
            'status'   => $chat->status,
        ]);
    }

    public function get_status() {
        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();

        $chat_id = absint($_POST['chat_id'] ?? 0);
        $token   = sanitize_text_field($_POST['chat_token'] ?? '');

        if (!$chat_id || !$token) {
            wp_send_json_error(['message' => 'Chat ID and token are required.']);
        }

        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
        if (!$chat) {
            wp_send_json_error(['message' => 'Chat not found.']);
        }

        $this->require_chat_owner($chat);

        if (!hash_equals((string) $chat->session_token, (string) $token)) {
            wp_send_json_error(['message' => 'Invalid chat token.']);
        }

        // Do not make an open customer wait for the minute-based WP-Cron job.
        // The first status request at/after zero performs the award immediately.
        if (empty($chat->agent_replied_at)
            && !(int) $chat->compensation_awarded
            && (int) $chat->timer_expires_ts > 0
            && (int) $chat->timer_expires_ts <= time()) {
            $this->check_timeouts();
            $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
        }

        $bonus_balance = 0;
        $withdrawal_pending = false;
        $withdrawal_threshold = $this->withdrawal_threshold();

        if (!empty($chat->user_id)) {
            $bonus_balance = $this->get_user_balance((int) $chat->user_id);
            $withdrawal_pending = $this->has_pending_withdrawal((int) $chat->user_id);
        } elseif ((int) $chat->compensation_awarded === 1) {
            $bonus_balance = (int) $chat->compensation_amount;
        }

        $cooldown_status = $this->bonus_cooldown_status($chat->customer_key);

        wp_send_json_success([
            'status'               => $chat->status,
            'agent_replied_at'     => $chat->agent_replied_at,
            'closed_at'            => $chat->closed_at,
            'compensation_awarded' => (int) $chat->compensation_awarded,
            'compensation_amount'  => (float) $chat->compensation_amount,
            'bonus_balance'        => $bonus_balance,
            'withdrawal_threshold' => $withdrawal_threshold,
            'withdrawal_pending'   => $withdrawal_pending,
            'can_withdraw'         => (!$withdrawal_pending && $bonus_balance >= $withdrawal_threshold),
            'expires_ts'           => (int) $chat->timer_expires_ts,
            'agent_typing_at'      => $chat->agent_typing_at,
            'customer_typing_at'   => $chat->customer_typing_at,
            'reset_version'        => $this->reset_version(),
            'rating'               => $chat->rating !== null ? (int) $chat->rating : null,
            'bonus_cooldown_message'=> (!$cooldown_status['available'] && !(int) $chat->compensation_awarded && (float) $chat->compensation_amount <= 0) ? $cooldown_status['message'] : '',
            'bonus_cooldown_remaining_seconds' => (int) $cooldown_status['remaining_seconds'],
            'messages'             => $this->load_messages($chat_id),
        ]);
    }

    public function customer_typing() {
        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();

        $chat_id = absint($_POST['chat_id'] ?? 0);
        $token   = sanitize_text_field($_POST['chat_token'] ?? '');

        if (!$chat_id || !$token) {
            wp_send_json_error(['message' => 'Chat ID and token are required.']);
        }

        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
        if (!$chat) {
            wp_send_json_error(['message' => 'Chat not found.']);
        }

        $this->require_chat_owner($chat);

        if (!hash_equals((string) $chat->session_token, (string) $token)) {
            wp_send_json_error(['message' => 'Invalid chat token.']);
        }

        $wpdb->update($t['chats'], [
            'customer_typing_at' => current_time('mysql'),
        ], [
            'id' => $chat_id,
        ]);

        wp_send_json_success(['ok' => true]);
    }

    public function request_withdrawal() {
        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();

        $chat_id = absint($_POST['chat_id'] ?? 0);
        $token   = sanitize_text_field($_POST['chat_token'] ?? '');

        if (!$chat_id || !$token) {
            wp_send_json_error(['message' => 'Chat ID and token are required.']);
        }

        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
        if (!$chat) {
            wp_send_json_error(['message' => 'Chat not found.']);
        }

        $this->require_chat_owner($chat);

        if (!hash_equals((string) $chat->session_token, (string) $token)) {
            wp_send_json_error(['message' => 'Invalid chat token.']);
        }

        if (empty($chat->user_id)) {
            wp_send_json_error(['message' => 'This chat is not attached to a registered user.']);
        }

        $user_id  = (int) $chat->user_id;
        $balance  = $this->get_user_balance($user_id);
        $threshold = $this->withdrawal_threshold();

        if ($balance < $threshold) {
            wp_send_json_error(['message' => 'Your balance has not reached the withdrawal threshold yet.']);
        }

        if ($this->has_pending_withdrawal($user_id)) {
            wp_send_json_error(['message' => 'You already have a pending withdrawal request.']);
        }

        $amount = round($balance, 2);

        $wpdb->insert($t['withdraw'], [
            'user_id'       => $user_id,
            'customer_key'  => $chat->customer_key,
            'amount'        => $amount,
            'status'        => 'pending',
            'requested_at'  => current_time('mysql'),
            'admin_note'    => '',
        ]);

        wp_send_json_success([
            'message' => 'Withdrawal request submitted.',
            'wallet'  => $this->wallet_status($user_id),
        ]);
    }

    public function submit_rating() {
        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();

        $chat_id  = absint($_POST['chat_id'] ?? 0);
        $token    = sanitize_text_field($_POST['chat_token'] ?? '');
        $rating   = absint($_POST['rating'] ?? 0);
        $feedback = sanitize_text_field($_POST['feedback'] ?? '');

        if (!$chat_id || !$token || $rating < 1 || $rating > 5) {
            wp_send_json_error(['message' => 'A rating between 1 and 5 is required.']);
        }

        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
        if (!$chat) {
            wp_send_json_error(['message' => 'Chat not found.']);
        }

        $this->require_chat_owner($chat);

        if (!hash_equals((string) $chat->session_token, (string) $token)) {
            wp_send_json_error(['message' => 'Invalid chat token.']);
        }

        if ($chat->rating !== null) {
            wp_send_json_error(['message' => 'This chat has already been rated.']);
        }

        $wpdb->update($t['chats'], [
            'rating'              => $rating,
            'rating_feedback'     => $feedback,
            'rating_submitted_at' => current_time('mysql'),
        ], [
            'id' => $chat_id,
        ]);

        wp_send_json_success(['message' => 'Thanks for the feedback!']);
    }

    private function close_chat_record($chat_id, $system_message) {
        global $wpdb;
        $t = $this->tables();
        $now = current_time('mysql');

        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
        if (!$chat) {
            return false;
        }

        if ($chat->status === 'closed') {
            return true;
        }

        $wpdb->update($t['chats'], [
            'status'             => 'closed',
            'closed_at'          => $now,
            'admin_unread_count' => 0,
            'customer_typing_at' => null,
            'agent_typing_at'    => null,
            'updated_at'         => $now,
        ], [
            'id' => $chat_id,
        ]);

        $wpdb->insert($t['msgs'], [
            'chat_id'     => $chat_id,
            'sender_type' => 'system',
            'message'     => $system_message,
            'created_at'  => $now,
        ]);

        return true;
    }

    public function end_chat() {
        check_ajax_referer('sbc_nonce', 'nonce');

        $chat_id = absint($_POST['chat_id'] ?? 0);
        $token   = sanitize_text_field($_POST['chat_token'] ?? '');

        if (!$chat_id || !$token) {
            wp_send_json_error(['message' => 'Chat ID and token are required.']);
        }

        global $wpdb;
        $t = $this->tables();

        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
        if (!$chat) {
            wp_send_json_error(['message' => 'Chat not found.']);
        }

        $this->require_chat_owner($chat);

        if (!hash_equals((string) $chat->session_token, (string) $token)) {
            wp_send_json_error(['message' => 'Invalid chat token.']);
        }

        $this->close_chat_record($chat_id, 'Chat ended by customer.');

        wp_send_json_success([
            'status'   => 'closed',
            'messages' => $this->load_messages($chat_id),
        ]);
    }

    public function admin_reply() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();

        $chat_id = absint($_POST['chat_id'] ?? 0);
        $message = sanitize_textarea_field($_POST['message'] ?? '');

        if (!$chat_id || !$message) {
            wp_send_json_error(['message' => 'Chat ID and message are required.']);
        }

        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
        if (!$chat) {
            wp_send_json_error(['message' => 'Chat not found.']);
        }

        if ($chat->status === 'closed') {
            wp_send_json_error(['message' => 'This chat is closed.']);
        }

        $now = current_time('mysql');

        $wpdb->insert($t['msgs'], [
            'chat_id'     => $chat_id,
            'sender_type' => 'agent',
            'sender_label'=> function_exists('sbc_ops') ? sbc_ops()->staff_identity()['display_name'] : wp_get_current_user()->display_name,
            'sender_department'=> function_exists('sbc_ops') ? sbc_ops()->staff_identity()['department'] : '',
            'message'     => $message,
            'created_at'  => $now,
        ]);

        if (empty($chat->agent_replied_at)) {
            $wpdb->update($t['chats'], [
                'status'             => 'active',
                'agent_replied_at'   => $now,
                'admin_unread_count' => 0,
                'agent_typing_at'    => null,
                'updated_at'         => $now,
                'last_human_activity_at' => $now,
                'inactivity_warned_at' => null,
            ], [
                'id' => $chat_id,
            ]);
        } else {
            $wpdb->update($t['chats'], [
                'admin_unread_count' => 0,
                'agent_typing_at'    => null,
                'updated_at'         => $now,
                'last_human_activity_at' => $now,
                'inactivity_warned_at' => null,
            ], [
                'id' => $chat_id,
            ]);
        }

        wp_send_json_success([
            'messages' => $this->load_messages($chat_id),
        ]);
    }

    public function admin_close_chat() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        check_ajax_referer('sbc_nonce', 'nonce');

        $chat_id = absint($_POST['chat_id'] ?? 0);
        if (!$chat_id) {
            wp_send_json_error(['message' => 'Chat ID is required.']);
        }

        $ok = $this->close_chat_record($chat_id, 'Chat closed by admin.');
        if (!$ok) {
            wp_send_json_error(['message' => 'Could not close chat.']);
        }

        global $wpdb;
        $t = $this->tables();

        wp_send_json_success([
            'messages' => $this->load_messages($chat_id),
            'chat'     => $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id)),
        ]);
    }

    public function admin_deadzone_reset() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        check_ajax_referer('sbc_nonce', 'nonce');

        $version = $this->bump_reset_version();

        wp_send_json_success([
            'message'       => 'Frontend cache reset version bumped.',
            'reset_version' => $version,
        ]);
    }

    public function admin_clear_all() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();

        $wpdb->query("TRUNCATE TABLE {$t['msgs']}");
        $wpdb->query("TRUNCATE TABLE {$t['chats']}");
        $wpdb->query("TRUNCATE TABLE {$t['comp']}");
        $wpdb->query("TRUNCATE TABLE {$t['wallet_tx']}");
        $wpdb->query("TRUNCATE TABLE {$t['withdraw']}");

        $version = $this->bump_reset_version();
        wp_cache_flush();

        wp_send_json_success([
            'message'       => 'All chats/messages cleared.',
            'reset_version' => $version,
        ]);
    }

    public function check_timeouts() {
        global $wpdb;
        $t = $this->tables();

        $now_ts = time();
        $now    = current_time('mysql');

        $chats = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT * FROM {$t['chats']}
                 WHERE status IN ('waiting','active')
                 AND compensation_awarded = 0
                 AND timer_expires_ts > 0
                 AND timer_expires_ts <= %d",
                $now_ts
            )
        );

        foreach ($chats as $chat) {
            if (!empty($chat->agent_replied_at)) {
                continue;
            }

            if ((int) $chat->compensation_awarded === 1) {
                continue;
            }

            if (!$this->bonus_is_available($chat->customer_key)) {
                continue;
            }

            $amount = (float) $chat->compensation_amount;
            if ($amount <= 0) {
                continue;
            }

            // Atomically claim this award so concurrent cron/AJAX checks cannot
            // credit the same chat twice.
            $claimed = $wpdb->query($wpdb->prepare(
                "UPDATE {$t['chats']}
                 SET compensation_awarded = 1, bonus_awarded_at = %s, updated_at = %s
                 WHERE id = %d AND compensation_awarded = 0 AND agent_replied_at IS NULL",
                $now,
                $now,
                $chat->id
            ));

            if (!$claimed) {
                continue;
            }

            $wpdb->insert($t['comp'], [
                'chat_id'      => $chat->id,
                'customer_key' => $chat->customer_key,
                'award_date'   => current_time('Y-m-d'),
                'amount'       => $amount,
                'created_at'   => $now,
            ]);

            if (!empty($chat->user_id)) {
                $user_id = (int) $chat->user_id;
                $this->add_wallet_transaction($user_id, $chat->id, 'bonus', $amount, 'Chat compensation bonus');
            }

            $wpdb->insert($t['msgs'], [
                'chat_id'     => $chat->id,
                'sender_type' => 'system',
                'message'     => "Bonus ₦{$amount} has been added because no agent replied in time.",
                'created_at'  => $now,
            ]);
        }
    }

    public function admin_get_inbox() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();

        $search = sanitize_text_field($_POST['search'] ?? '');
        $status_filter = sanitize_text_field($_POST['status'] ?? 'all');
        $selected_chat_id = absint($_POST['selected_chat_id'] ?? 0);
        $selected_customer_key = sanitize_text_field($_POST['selected_customer_key'] ?? '');
        $mark_read = !empty($_POST['mark_read']);

        if ($mark_read && $selected_chat_id) {
            $selected_row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $selected_chat_id));
            if ($selected_row) {
                $group_key = $selected_customer_key ?: $selected_row->customer_key;
                if ($group_key) {
                    $wpdb->query(
                        $wpdb->prepare(
                            "UPDATE {$t['chats']} SET admin_unread_count = 0 WHERE customer_key = %s",
                            $group_key
                        )
                    );
                }
            }
        }

        $rows = $wpdb->get_results("SELECT * FROM {$t['chats']} ORDER BY updated_at DESC, id DESC LIMIT 500");

        // Load sidebar balances and pending withdrawals in two bulk queries
        // instead of running two queries for every customer on every poll.
        $sidebar_balances = [];
        $sidebar_pending = [];
        $sidebar_user_ids = array_values(array_unique(array_filter(array_map(function ($row) {
            return absint($row->user_id);
        }, $rows ?: []))));

        if ($sidebar_user_ids) {
            $ids_sql = implode(',', $sidebar_user_ids);
            $balance_rows = $wpdb->get_results(
                "SELECT user_id, meta_value FROM {$wpdb->usermeta}
                 WHERE meta_key = 'sbc_bonus_balance' AND user_id IN ({$ids_sql})"
            );
            foreach ($balance_rows as $balance_row) {
                $sidebar_balances[(int) $balance_row->user_id] = (float) $balance_row->meta_value;
            }

            $pending_rows = $wpdb->get_results(
                "SELECT user_id FROM {$t['withdraw']}
                 WHERE status = 'pending' AND user_id IN ({$ids_sql}) GROUP BY user_id"
            );
            foreach ($pending_rows as $pending_row) {
                $sidebar_pending[(int) $pending_row->user_id] = true;
            }
        }

        $groups = [];

        foreach ($rows as $chat) {
            if ($status_filter && $status_filter !== 'all' && $chat->status !== $status_filter) {
                continue;
            }

            $haystack = strtolower(
                implode(' ', [
                    $chat->full_name,
                    $chat->customer_email,
                    $chat->service_type,
                    $chat->status,
                    (string) $chat->initial_message,
                    (string) $chat->id,
                ])
            );

            if ($search && strpos($haystack, strtolower($search)) === false) {
                continue;
            }

            $key = $chat->customer_key ?: ('chat_' . $chat->id);

            if (!isset($groups[$key])) {
                $groups[$key] = [
                    'customer_key' => $key,
                    'user_id' => (int) $chat->user_id,
                    'customer_email' => $chat->customer_email,
                    'full_name' => $chat->full_name,
                    'balance' => $chat->user_id ? ($sidebar_balances[(int) $chat->user_id] ?? 0) : 0,
                    'pending_withdrawal' => $chat->user_id ? !empty($sidebar_pending[(int) $chat->user_id]) : false,
                    'chats_count' => 0,
                    'unread_count' => 0,
                    'latest_chat_id' => (int) $chat->id,
                    'latest_status' => $chat->status,
                    'latest_service' => $chat->service_type,
                    'latest_updated_at' => $chat->updated_at,
                    'history' => [],
                ];
            }

            $groups[$key]['chats_count']++;
            $groups[$key]['unread_count'] += (int) $chat->admin_unread_count;

            $groups[$key]['history'][] = [
                'chat_id' => (int) $chat->id,
                'status' => $chat->status,
                'service_type' => $chat->service_type,
                'updated_at' => $chat->updated_at,
                'unread_count' => (int) $chat->admin_unread_count,
            ];
        }

        uasort($groups, function ($a, $b) {
            return strcmp((string) $b['latest_updated_at'], (string) $a['latest_updated_at']);
        });

        $customers = array_values($groups);

        if (!$selected_chat_id && !empty($customers)) {
            $selected_chat_id = (int) $customers[0]['latest_chat_id'];
            $selected_customer_key = (string) $customers[0]['customer_key'];
        }

        $selected_chat = null;
        $selected_messages = [];
        $selected_history = [];

        if ($selected_chat_id) {
            $selected_chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $selected_chat_id));
            if ($selected_chat) {
                $selected_messages = $this->load_messages($selected_chat_id);

                $selected_history_rows = $wpdb->get_results(
                    $wpdb->prepare(
                        "SELECT * FROM {$t['chats']} WHERE customer_key = %s ORDER BY updated_at DESC, id DESC LIMIT 3",
                        $selected_chat->customer_key
                    )
                );

                foreach ($selected_history_rows as $h) {
                    $selected_history[] = [
                        'chat_id' => (int) $h->id,
                        'status' => $h->status,
                        'service_type' => $h->service_type,
                        'updated_at' => $h->updated_at,
                        'admin_unread_count' => (int) $h->admin_unread_count,
                    ];
                }
            }
        }

        wp_send_json_success([
            'reset_version' => $this->reset_version(),
            'global_unread_count' => (int) $wpdb->get_var("SELECT COALESCE(SUM(admin_unread_count), 0) FROM {$t['chats']}"),
            'customers' => $customers,
            'selected_chat_id' => $selected_chat ? (int) $selected_chat->id : 0,
            'selected_customer_key' => $selected_chat ? $selected_chat->customer_key : '',
            'selected' => $selected_chat ? [
                'chat_id' => (int) $selected_chat->id,
                'customer_key' => $selected_chat->customer_key,
                'user_id' => (int) $selected_chat->user_id,
                'customer_email' => $selected_chat->customer_email,
                'full_name' => $selected_chat->full_name,
                'service_type' => $selected_chat->service_type,
                'initial_message' => $selected_chat->initial_message,
                'status' => $selected_chat->status,
                'admin_unread_count' => (int) $selected_chat->admin_unread_count,
                'compensation_awarded' => (int) $selected_chat->compensation_awarded,
                'compensation_amount' => (float) $selected_chat->compensation_amount,
                'timer_expires_ts' => (int) $selected_chat->timer_expires_ts,
                'timer_expires_at' => $selected_chat->timer_expires_at,
                'customer_typing_at' => $selected_chat->customer_typing_at,
                'agent_typing_at' => $selected_chat->agent_typing_at,
                'rating' => $selected_chat->rating !== null ? (int) $selected_chat->rating : null,
                'rating_feedback' => $selected_chat->rating_feedback,
                'vtupress_transaction_id' => (int) $selected_chat->vtupress_transaction_id,
                'complaint_reason' => $selected_chat->complaint_reason,
                'transaction_snapshot' => $selected_chat->transaction_snapshot ? json_decode($selected_chat->transaction_snapshot, true) : null,
                'balance' => $selected_chat->user_id ? $this->get_user_balance((int) $selected_chat->user_id) : 0,
                'pending_withdrawal' => $selected_chat->user_id ? $this->has_pending_withdrawal((int) $selected_chat->user_id) : false,
                'created_at' => $selected_chat->created_at,
                'updated_at' => $selected_chat->updated_at,
                'history' => $selected_history,
                'messages' => $selected_messages,
            ] : null,
        ]);
    }

    public function admin_get_customers() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();

        $search = sanitize_text_field($_POST['search'] ?? '');

        $rows = $wpdb->get_results(
            "SELECT user_id, MAX(updated_at) AS last_activity, COUNT(*) AS chats_count,
                    SUBSTRING_INDEX(GROUP_CONCAT(customer_email ORDER BY updated_at DESC), ',', 1) AS latest_email,
                    SUBSTRING_INDEX(GROUP_CONCAT(full_name ORDER BY updated_at DESC), ',', 1) AS latest_name
             FROM {$t['chats']}
             WHERE user_id > 0
             GROUP BY user_id
             ORDER BY last_activity DESC
             LIMIT 500"
        );

        $customers = [];
        foreach ($rows as $r) {
            $user_id = (int) $r->user_id;

            if ($search) {
                $haystack = strtolower($r->latest_email . ' ' . $r->latest_name);
                if (strpos($haystack, strtolower($search)) === false) {
                    continue;
                }
            }

            $customers[] = [
                'user_id'      => $user_id,
                'email'        => $r->latest_email,
                'full_name'    => $r->latest_name,
                'balance'      => $this->get_user_balance($user_id),
                'threshold'    => $this->withdrawal_threshold(),
                'pending'      => $this->has_pending_withdrawal($user_id),
                'chats_count'  => (int) $r->chats_count,
                'last_activity'=> $r->last_activity,
            ];
        }

        wp_send_json_success(['customers' => $customers]);
    }

    public function admin_adjust_balance() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        check_ajax_referer('sbc_nonce', 'nonce');

        $user_id = absint($_POST['user_id'] ?? 0);
        $amount  = (float) ($_POST['amount'] ?? 0);
        $note    = sanitize_text_field($_POST['note'] ?? 'Manual admin adjustment');

        if (!$user_id || !$amount) {
            wp_send_json_error(['message' => 'A user and non-zero amount are required.']);
        }

        $user = get_user_by('id', $user_id);
        if (!$user) {
            wp_send_json_error(['message' => 'User not found.']);
        }

        $new_balance = $this->add_wallet_transaction($user_id, 0, 'adjustment', $amount, $note ?: 'Manual admin adjustment');

        wp_send_json_success([
            'message' => 'Balance updated.',
            'balance' => $new_balance,
        ]);
    }

    public function admin_refresh_transaction() {
        if (!current_user_can('manage_options')) wp_send_json_error(['message' => 'Unauthorized']);
        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();
        $chat_id = absint($_POST['chat_id'] ?? 0);
        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
        if (!$chat || !(int) $chat->vtupress_transaction_id) wp_send_json_error(['message' => 'No VTUpress transaction is attached.']);
        $vtu = $this->vtupress_user((int) $chat->user_id);
        if (!$vtu) wp_send_json_error(['message' => 'The customer VTUpress account is not connected.']);

        $tx_table = $wpdb->prefix . 'vtupress_transactions';
        $transaction = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$tx_table} WHERE id = %d AND user_id = %d LIMIT 1",
            (int) $chat->vtupress_transaction_id,
            (int) $vtu->id
        ));
        if (!$transaction) wp_send_json_error(['message' => 'The attached transaction no longer exists.']);

        $snapshot = wp_json_encode([
            'summary' => $this->transaction_summary($transaction),
            'response_message' => (string) $transaction->response_message,
            'details' => json_decode((string) $transaction->details, true),
            'balance_before' => (string) $transaction->balance_before,
            'balance_now' => (string) $transaction->balance_now,
        ]);
        $wpdb->update($t['chats'], ['transaction_snapshot' => $snapshot], ['id' => $chat_id]);
        wp_send_json_success(['message' => 'Transaction details refreshed.']);
    }

    public function admin_get_withdrawals() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();

        $rows = $wpdb->get_results("SELECT * FROM {$t['withdraw']} ORDER BY id DESC LIMIT 200", ARRAY_A);

        wp_send_json_success([
            'withdrawals' => $rows,
            'threshold'   => $this->withdrawal_threshold(),
        ]);
    }

    public function admin_approve_withdrawal() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();

        $id = absint($_POST['id'] ?? 0);
        if (!$id) {
            wp_send_json_error(['message' => 'Withdrawal ID required.']);
        }

        $req = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['withdraw']} WHERE id = %d", $id));
        if (!$req) {
            wp_send_json_error(['message' => 'Request not found.']);
        }

        if ($req->status !== 'pending') {
            wp_send_json_error(['message' => 'Request is not pending.']);
        }

        $balance = $this->get_user_balance((int) $req->user_id);
        if ($balance < (float) $req->amount) {
            wp_send_json_error(['message' => 'User balance is lower than request amount.']);
        }

        if (!class_exists('Users')) {
            wp_send_json_error(['message' => 'VTUpress is not active. The support balance was not changed.']);
        }

        $vtu_user = $this->vtupress_user((int) $req->user_id);
        if (!$vtu_user) {
            wp_send_json_error(['message' => 'This customer has no linked VTUpress wallet. The support balance was not changed.']);
        }

        $reference = 'sbc_withdrawal_' . (int) $req->id;
        $claimed = $wpdb->query($wpdb->prepare(
            "UPDATE {$t['withdraw']} SET status = 'processing', external_reference = %s, failure_reason = '' WHERE id = %d AND status = 'pending'",
            $reference,
            (int) $req->id
        ));
        if ($claimed !== 1) wp_send_json_error(['message' => 'This request is already being processed.']);

        $users = new Users();
        $credited = $users->credit(
            (int) $vtu_user->id,
            (float) $req->amount,
            'support_bonus',
            'Support bonus withdrawal ' . $reference,
            ['reference' => $reference, 'support_withdrawal_id' => (int) $req->id]
        );

        if (!$credited) {
            $wpdb->update($t['withdraw'], [
                'status' => 'pending',
                'failure_reason' => 'VTUpress wallet credit failed',
            ], ['id' => $id]);
            wp_send_json_error(['message' => 'VTUpress could not credit the wallet. The request remains pending and the support balance was not changed.']);
        }

        $new_balance = round($balance - (float) $req->amount, 2);
        $this->set_user_balance((int) $req->user_id, $new_balance);

        $wpdb->insert($t['wallet_tx'], [
            'user_id'        => (int) $req->user_id,
            'chat_id'        => 0,
            'tx_type'        => 'withdrawal',
            'amount'         => (float) (0 - (float) $req->amount),
            'balance_before'  => $balance,
            'balance_after'   => $new_balance,
            'note'           => 'Credited to VTUpress: ' . $reference,
            'created_at'      => current_time('mysql'),
        ]);

        $wpdb->update($t['withdraw'], [
            'status'       => 'approved',
            'resolved_at'  => current_time('mysql'),
            'admin_note'   => 'Approved by admin',
            'external_reference' => $reference,
            'failure_reason' => '',
        ], [
            'id' => $id,
        ]);

        wp_send_json_success(['message' => 'Withdrawal approved.']);
    }

    public function admin_decline_withdrawal() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();

        $id = absint($_POST['id'] ?? 0);
        if (!$id) {
            wp_send_json_error(['message' => 'Withdrawal ID required.']);
        }

        $req = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['withdraw']} WHERE id = %d", $id));
        if (!$req) {
            wp_send_json_error(['message' => 'Request not found.']);
        }

        if ($req->status !== 'pending') {
            wp_send_json_error(['message' => 'Request is not pending.']);
        }

        $wpdb->update($t['withdraw'], [
            'status'       => 'declined',
            'resolved_at'  => current_time('mysql'),
            'admin_note'   => 'Declined by admin',
        ], [
            'id' => $id,
        ]);

        wp_send_json_success(['message' => 'Withdrawal declined.']);
    }

    public function withdrawals_page() {
        ?>
        <div class="wrap">
            <h1>Withdrawal Requests</h1>
            <p>Withdrawal threshold: <strong>₦<?php echo esc_html($this->withdrawal_threshold()); ?></strong></p>

            <table class="widefat fixed striped" id="sbc-withdrawals-table">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>User</th>
                        <th>Amount</th>
                        <th>Status</th>
                        <th>Requested</th>
                        <th>Resolved</th>
                        <th>VTUpress Reference / Error</th>
                        <th>Actions</th>
                    </tr>
                </thead>
                <tbody>
                    <tr><td colspan="8">Loading...</td></tr>
                </tbody>
            </table>

            <script>
            jQuery(function($){
                function loadWithdrawals(){
                    $.post(ajaxurl, {
                        action: 'sbc_admin_get_withdrawals',
                        nonce: '<?php echo esc_js(wp_create_nonce('sbc_nonce')); ?>'
                    }, function(res){
                        if(!res || !res.success) return;

                        const rows = (res.data.withdrawals || []).map(function(r){
                            const actions = r.status === 'pending'
                                ? `<button class="button sbc-approve" data-id="${r.id}">Approve</button>
                                   <button class="button sbc-decline" data-id="${r.id}">Decline</button>`
                                : '-';

                            return `<tr>
                                <td>#${r.id}</td>
                                <td>${r.user_id}</td>
                                <td>₦${r.amount}</td>
                                <td>${r.status}</td>
                                <td>${r.requested_at}</td>
                                <td>${r.resolved_at || ''}</td>
                                <td>${r.external_reference || r.failure_reason || '—'}</td>
                                <td>${actions}</td>
                            </tr>`;
                        }).join('');

                        $('#sbc-withdrawals-table tbody').html(rows || '<tr><td colspan="8">No requests.</td></tr>');
                    });
                }

                $('#sbc-withdrawals-table').on('click', '.sbc-approve', function(){
                    const id = $(this).data('id');
                    $.post(ajaxurl, {
                        action: 'sbc_admin_approve_withdrawal',
                        nonce: '<?php echo esc_js(wp_create_nonce('sbc_nonce')); ?>',
                        id: id
                    }, function(res){
                        if(!res || !res.success){
                            alert((res && res.data && res.data.message) ? res.data.message : 'Failed.');
                            return;
                        }
                        loadWithdrawals();
                    });
                });

                $('#sbc-withdrawals-table').on('click', '.sbc-decline', function(){
                    const id = $(this).data('id');
                    $.post(ajaxurl, {
                        action: 'sbc_admin_decline_withdrawal',
                        nonce: '<?php echo esc_js(wp_create_nonce('sbc_nonce')); ?>',
                        id: id
                    }, function(res){
                        if(!res || !res.success){
                            alert((res && res.data && res.data.message) ? res.data.message : 'Failed.');
                            return;
                        }
                        loadWithdrawals();
                    });
                });

                loadWithdrawals();
            });
            </script>
        </div>
        <?php
    }

    public function customers_page() {
        ?>
        <div class="wrap">
            <h1>Customers &amp; Balances</h1>
            <p>Every registered customer who has opened a chat, with their current wallet balance. Use this to credit or debit a balance directly.</p>

            <input type="text" id="sbc-cust-search" placeholder="Search name or email..." style="min-width:280px;padding:8px 10px;border-radius:8px;border:1px solid #cfd6e4;margin-bottom:14px;" />

            <table class="widefat fixed striped" id="sbc-customers-table">
                <thead>
                    <tr>
                        <th>Customer</th>
                        <th>Balance</th>
                        <th>Threshold</th>
                        <th>Chats</th>
                        <th>Last activity</th>
                        <th>Adjust balance</th>
                    </tr>
                </thead>
                <tbody>
                    <tr><td colspan="6">Loading...</td></tr>
                </tbody>
            </table>

            <script>
            jQuery(function($){
                const nonce = '<?php echo esc_js(wp_create_nonce('sbc_nonce')); ?>';

                function loadCustomers(){
                    $.post(ajaxurl, {
                        action: 'sbc_admin_get_customers',
                        nonce: nonce,
                        search: $('#sbc-cust-search').val() || ''
                    }, function(res){
                        if(!res || !res.success) return;

                        const rows = (res.data.customers || []).map(function(c){
                            return `<tr data-user-id="${c.user_id}">
                                <td><strong>${$('<div>').text(c.full_name || c.email || 'Unknown').html()}</strong><br><span style="color:#6b7280;">${$('<div>').text(c.email || '').html()}</span></td>
                                <td><strong>₦${Number(c.balance).toFixed(2)}</strong>${c.pending ? ' <span style="color:#dc2626;">(withdrawal pending)</span>' : ''}</td>
                                <td>₦${Number(c.threshold).toFixed(2)}</td>
                                <td>${c.chats_count}</td>
                                <td>${c.last_activity || ''}</td>
                                <td>
                                    <input type="number" class="sbc-adjust-amount" placeholder="e.g. 100 or -100" style="width:110px;">
                                    <input type="text" class="sbc-adjust-note" placeholder="Note (optional)" style="width:150px;">
                                    <button class="button button-primary sbc-adjust-btn">Apply</button>
                                </td>
                            </tr>`;
                        }).join('');

                        $('#sbc-customers-table tbody').html(rows || '<tr><td colspan="6">No customers yet.</td></tr>');
                    });
                }

                $('#sbc-cust-search').on('input', function(){
                    clearTimeout(window.__sbcCustDebounce);
                    window.__sbcCustDebounce = setTimeout(loadCustomers, 300);
                });

                $('#sbc-customers-table').on('click', '.sbc-adjust-btn', function(){
                    const row = $(this).closest('tr');
                    const userId = row.data('user-id');
                    const amount = parseFloat(row.find('.sbc-adjust-amount').val());
                    const note = row.find('.sbc-adjust-note').val();

                    if (!amount) {
                        alert('Enter a non-zero amount (negative to deduct).');
                        return;
                    }

                    $.post(ajaxurl, {
                        action: 'sbc_admin_adjust_balance',
                        nonce: nonce,
                        user_id: userId,
                        amount: amount,
                        note: note
                    }, function(res){
                        if (!res || !res.success) {
                            alert((res && res.data && res.data.message) ? res.data.message : 'Could not adjust balance.');
                            return;
                        }
                        loadCustomers();
                    });
                });

                loadCustomers();
            });
            </script>
        </div>
        <?php
    }

    public function inbox_page() {
        wp_enqueue_script(
            'sbc-admin',
            plugins_url('assets/js/sbc-admin.js', __FILE__),
            ['jquery'],
            '1.0.1',
            true
        );

        wp_localize_script('sbc-admin', 'SBC_ADMIN', [
            'ajaxUrl'      => admin_url('admin-ajax.php'),
            'nonce'        => wp_create_nonce('sbc_nonce'),
            'currency'     => '₦',
            'resetVersion' => $this->reset_version(),
            'cannedReplies'=> $this->canned_replies(),
            'withdrawalsUrl'=> admin_url('admin.php?page=sbc-withdrawals'),
        ]);

        ?>
        <div class="wrap sbc-support-app">
            <div class="sbc-app-title"><span class="sbc-app-brand-icon"><img src="<?php echo esc_url(SBC_PLUGIN_URL . 'assets/images/blupass-icon.png'); ?>" alt=""></span><div><h1>BluPass Inbox</h1><small>Customer engagement workspace</small></div><div class="sbc-title-live">● Live refresh ON</div></div>
            <?php $db_error=get_option('sbc_last_database_error');if(current_user_can('manage_options')&&is_array($db_error)&&array_filter([$db_error['first']??'',$db_error['retry']??'',$db_error['final']??''])): ?>
                <div class="notice notice-error inline" style="display:block;margin:14px 0;padding:12px 16px;position:relative;z-index:50">
                    <p><strong>BluPass database diagnostic</strong></p>
                    <p style="white-space:pre-wrap;word-break:break-word"><?php echo esc_html(implode("\n",array_filter([$db_error['first']??'',$db_error['retry']??'',$db_error['final']??'']))); ?></p>
                    <p><small>Recorded: <?php echo esc_html($db_error['time']??''); ?></small></p>
                </div>
            <?php endif; ?>

            <style>
                .sbc-support-app{
                    --sbc-primary:var(--sbc-blue);
                    --sbc-primary-dark:var(--sbc-secondary);
                    --sbc-border:#e2e8f0;
                    --sbc-surface:#fff;
                    max-width:1500px;
                }
                .sbc-support-app > h1{
                    margin:20px 0 4px;
                    font-size:28px;
                    letter-spacing:-.03em;
                }
                .sbc-admin-toolbar{
                    display:flex;
                    gap:10px;
                    flex-wrap:wrap;
                    margin:18px 0 14px;
                    align-items:center;
                    padding:12px;
                    border:1px solid var(--sbc-border);
                    border-radius:14px;
                    background:rgba(255,255,255,.9);
                    box-shadow:0 8px 30px rgba(15,23,42,.05);
                    position:sticky;
                    top:32px;
                    z-index:20;
                }
                .sbc-admin-toolbar input,
                .sbc-admin-toolbar select{
                    min-width:220px;
                    padding:8px 10px;
                    border:1px solid #cfd6e4;
                    border-radius:10px;
                }
                .sbc-admin-live{
                    margin-left:auto;
                    font-size:13px;
                    color:#16a34a;
                    font-weight:600;
                }
                .sbc-admin-live:before{
                    content:'';
                    display:inline-block;
                    width:8px;
                    height:8px;
                    margin-right:7px;
                    border-radius:50%;
                    background:#22c55e;
                    box-shadow:0 0 0 4px rgba(34,197,94,.13);
                }
                .sbc-admin-grid{
                    display:grid;
                    grid-template-columns:360px 1fr;
                    gap:16px;
                    align-items:start;
                }
                .sbc-admin-box{
                    background:#fff;
                    border:1px solid var(--sbc-border);
                    border-radius:18px;
                    padding:16px;
                    box-shadow:0 12px 35px rgba(15,23,42,.06);
                    min-height:200px;
                }
                #sbc-admin-customer-list{
                    display:flex;
                    flex-direction:column;
                    gap:10px;
                    max-height:72vh;
                    overflow:auto;
                    padding-right:4px;
                }
                .sbc-admin-grid > .sbc-admin-box:first-child{
                    position:sticky;
                    top:116px;
                }
                .sbc-admin-customer-item{
                    width:100%;
                    text-align:left;
                    border:1px solid #e5e7eb;
                    border-radius:14px;
                    padding:12px;
                    background:#fff;
                    cursor:pointer;
                    transition:.15s ease;
                }
                .sbc-admin-customer-item:hover{
                    border-color:var(--sbc-primary);
                    background:color-mix(in srgb,var(--sbc-primary) 4%,white);
                }
                .sbc-admin-customer-item.active{
                    border-color:var(--sbc-primary);
                    background:linear-gradient(135deg,color-mix(in srgb,var(--sbc-primary) 10%,white),color-mix(in srgb,var(--sbc-secondary) 5%,white));
                    box-shadow:inset 3px 0 0 var(--sbc-primary);
                }
                .sbc-admin-row-top{
                    display:flex;
                    justify-content:space-between;
                    gap:10px;
                    align-items:flex-start;
                }
                .sbc-admin-name{
                    font-weight:700;
                    color:#111827;
                }
                .sbc-admin-muted{
                    color:#6b7280;
                    font-size:13px;
                    margin-top:4px;
                }
                .sbc-admin-footer{
                    margin-top:10px;
                    display:flex;
                    justify-content:space-between;
                    gap:10px;
                    font-size:12px;
                    color:#6b7280;
                }
                .sbc-balance-chip{
                    display:inline-block;
                    margin-top:6px;
                    padding:3px 9px;
                    border-radius:999px;
                    font-size:11px;
                    font-weight:700;
                    background:color-mix(in srgb,var(--sbc-primary) 10%,white);
                    color:var(--sbc-primary);
                }
                .sbc-withdrawal-chip{
                    display:inline-flex;
                    align-items:center;
                    margin:6px 0 0 5px;
                    padding:3px 8px;
                    border-radius:999px;
                    font-size:11px;
                    font-weight:800;
                    color:#9a3412;
                    background:#ffedd5;
                    border:1px solid #fed7aa;
                }
                .sbc-admin-withdrawal-alert{
                    margin-top:10px;
                    padding:10px 12px;
                    color:#9a3412;
                    background:#fff7ed;
                    border:1px solid #fed7aa;
                    border-radius:11px;
                    font-size:13px;
                    font-weight:700;
                }
                .sbc-badge{
                    display:inline-block;
                    padding:4px 9px;
                    border-radius:999px;
                    font-size:11px;
                    font-weight:700;
                    color:#fff;
                    white-space:nowrap;
                }
                .sbc-status-collecting{ background:var(--sbc-secondary); }
                .sbc-status-waiting{ background:var(--sbc-primary); }
                .sbc-status-active{ background:#16a34a; }
                .sbc-status-closed{ background:#6b7280; }
                .sbc-unread{
                    background:#dc2626;
                    color:#fff;
                    padding:4px 8px;
                    border-radius:999px;
                    font-size:11px;
                    font-weight:700;
                }
                #sbc-admin-thread-header{
                    display:flex;
                    justify-content:space-between;
                    gap:12px;
                    align-items:flex-start;
                    padding-bottom:14px;
                    border-bottom:1px solid var(--sbc-border);
                }
                #sbc-admin-thread-header h2{
                    margin:0 0 4px;
                }
                #sbc-admin-thread-meta{
                    margin-top:6px;
                    color:#4b5563;
                    font-size:13px;
                    line-height:1.5;
                }
                #sbc-admin-balance-box{
                    margin-top:10px;
                    padding:10px 12px;
                    background:color-mix(in srgb,var(--sbc-primary) 5%,white);
                    border:1px solid color-mix(in srgb,var(--sbc-primary) 20%,white);
                    border-radius:12px;
                    font-size:13px;
                }
                #sbc-admin-balance-box .sbc-balance-amount{
                    font-size:18px;
                    font-weight:800;
                    color:var(--sbc-primary);
                }
                #sbc-admin-adjust-row{
                    display:flex;
                    gap:6px;
                    margin-top:8px;
                    flex-wrap:wrap;
                }
                #sbc-admin-adjust-row input{
                    padding:6px 8px;
                    border:1px solid #cbd5e1;
                    border-radius:8px;
                }
                #sbc-admin-thread-typing{
                    margin:12px 0;
                    color:var(--sbc-primary);
                    font-size:13px;
                    font-weight:700;
                    min-height:18px;
                }
                #sbc-admin-history{
                    margin:14px 0;
                    padding:12px;
                    background:#f8fafc;
                    border:1px solid #e5e7eb;
                    border-radius:12px;
                }
                .sbc-history-list{
                    display:flex;
                    flex-wrap:wrap;
                    gap:8px;
                    margin-top:8px;
                }
                .sbc-history-btn{
                    border:1px solid #cbd5e1;
                    background:#fff;
                    color:#111827;
                    border-radius:999px;
                    padding:7px 11px;
                    cursor:pointer;
                }
                .sbc-history-btn.active{
                    border-color:var(--sbc-primary);
                    color:var(--sbc-primary);
                    background:color-mix(in srgb,var(--sbc-primary) 9%,white);
                }
                #sbc-admin-messages{
                    margin:14px 0;
                    max-height:42vh;
                    overflow:auto;
                    border:1px solid #e5e7eb;
                    border-radius:12px;
                    background:linear-gradient(180deg,#f8fafc,#f1f5f9);
                    padding:18px;
                }
                .sbc-admin-msg{
                    margin-bottom:12px;
                    padding:10px 12px;
                    border-radius:12px;
                    border:1px solid #e5e7eb;
                    background:#fff;
                    max-width:88%;
                    word-break:break-word;
                }
                .sbc-admin-msg.user{
                    background:#fff;
                    margin-left:auto;
                    border-color:#cbd5e1;
                    border-bottom-right-radius:4px;
                }
                .sbc-admin-msg.agent{
                    background:linear-gradient(135deg,var(--sbc-primary),var(--sbc-primary-dark));
                    color:#fff;
                    border-color:transparent;
                    border-bottom-left-radius:4px;
                }
                .sbc-admin-msg.agent strong{ color:color-mix(in srgb,var(--sbc-accent) 45%,white); }
                .sbc-admin-msg.system{
                    background:#f8f8f8;
                    border-style:dashed;
                    max-width:100%;
                }
                .sbc-admin-msg.warning{
                    background:#fff0f0;
                    color:#941b1b;
                    border:1px solid #f3a1a1;
                    border-left:5px solid #dc2626;
                    max-width:100%;
                    text-align:center;
                    font-weight:700;
                }
                .sbc-admin-msg strong{
                    display:block;
                    font-size:12px;
                    color:#6b7280;
                    margin-bottom:4px;
                }
                #sbc-admin-canned{
                    display:flex;
                    flex-wrap:wrap;
                    gap:6px;
                    margin-bottom:8px;
                }
                .sbc-canned-btn{
                    border:1px solid #cbd5e1;
                    background:#f8fafc;
                    color:#374151;
                    border-radius:999px;
                    padding:5px 10px;
                    font-size:12px;
                    cursor:pointer;
                }
                .sbc-canned-btn:hover{ background:color-mix(in srgb,var(--sbc-primary) 8%,white); border-color:var(--sbc-primary); }
                .sbc-admin-actions{
                    display:flex;
                    gap:10px;
                    flex-wrap:wrap;
                    margin-top:10px;
                    align-items:flex-start;
                }
                #sbc-admin-reply{
                    width:100%;
                    min-height:110px;
                    border-radius:12px;
                    border:1px solid #cbd5e1;
                    padding:10px;
                    box-sizing:border-box;
                    resize:vertical;
                    flex:1;
                    font-size:14px;
                    transition:border-color .15s, box-shadow .15s;
                }
                #sbc-admin-reply:focus{
                    border-color:var(--sbc-primary);
                    box-shadow:0 0 0 3px color-mix(in srgb,var(--sbc-primary) 16%,transparent);
                    outline:none;
                }
                .sbc-admin-action-buttons{
                    display:flex;
                    gap:8px;
                    flex-wrap:wrap;
                }
                .sbc-admin-action-buttons .button{
                    border-radius:10px;
                    padding:6px 12px;
                }
                .sbc-admin-empty{
                    padding:24px;
                    border:1px dashed #cbd5e1;
                    border-radius:12px;
                    background:#fafafa;
                    color:#6b7280;
                }
                #sbc-admin-thread{ transition:opacity .12s ease; }
                #sbc-admin-thread.sbc-thread-loading{ opacity:.58; pointer-events:none; }
                .sbc-admin-rating{
                    margin-top:6px;
                    color:#b45309;
                    font-weight:700;
                }
                /* Three-panel support workspace, based on the supplied UI reference. */
                .sbc-support-app{ color:#172033; }
                .sbc-app-title{
                    display:flex; align-items:center; gap:12px; padding:16px 4px 12px;
                }
                .sbc-app-brand-icon{width:38px;height:38px;border-radius:9px;background:#fff;display:grid;place-items:center;box-shadow:0 5px 14px color-mix(in srgb,var(--sbc-primary) 25%,transparent);overflow:hidden}
                .sbc-app-brand-icon img{width:34px;height:34px;object-fit:contain;display:block}
                .sbc-app-title h1{ margin:0; padding:0; font-size:24px; letter-spacing:-.025em; }
                .sbc-app-title small{ color:#7b8497; }
                .sbc-title-live{
                    margin-left:auto; padding:8px 12px; border-radius:8px;
                    color:#15935c; background:#effcf5; font-size:12px; font-weight:700;
                }
                .sbc-admin-live{ display:none; }
                .sbc-admin-toolbar{ top:32px; box-shadow:0 3px 14px rgba(15,23,42,.04); }
                .sbc-admin-toolbar input, .sbc-admin-toolbar select{
                    height:40px; border-radius:7px; background:#fff;
                }
                .sbc-admin-toolbar .button{ height:40px; line-height:38px; border-radius:7px; color:var(--sbc-primary); border-color:color-mix(in srgb,var(--sbc-primary) 20%,white); }
                .sbc-admin-grid{ grid-template-columns:290px minmax(0,1fr); gap:12px; }
                .sbc-admin-grid > .sbc-admin-box{ border-radius:10px; box-shadow:0 5px 22px rgba(15,23,42,.045); }
                .sbc-admin-grid > .sbc-admin-box:last-child{ padding:0; overflow:hidden; }
                #sbc-admin-customer-list{ gap:6px; max-height:calc(100vh - 205px); }
                .sbc-admin-customer-item{ border-color:transparent; border-radius:8px; padding:13px 12px; }
                .sbc-admin-customer-item + .sbc-admin-customer-item{ border-top:1px solid #edf0f5; }
                .sbc-admin-customer-item.active{
                    border-color:var(--sbc-primary); background:color-mix(in srgb,var(--sbc-primary) 5%,white); box-shadow:inset 0 0 0 1px var(--sbc-primary);
                }
                .sbc-thread-layout{ display:grid; grid-template-columns:minmax(0,1fr) 280px; min-height:690px; background:#f8fafc; }
                .sbc-chat-workspace{ min-width:0; display:flex; flex-direction:column; padding:0 14px 14px; background:#fff; border-right:1px solid #e8edf4; }
                .sbc-customer-sidebar{ padding:10px; display:flex; flex-direction:column; gap:10px; background:#f7f9fc; }
                #sbc-admin-thread-header{ margin:0 -14px; padding:13px 15px; min-height:54px; align-items:center; }
                .sbc-customer-identity, .sbc-side-profile{ display:flex; align-items:center; gap:10px; }
                .sbc-avatar{
                    display:inline-flex; align-items:center; justify-content:center; flex:0 0 36px;
                    width:36px; height:36px; border-radius:50%; color:#fff; background:linear-gradient(135deg,var(--sbc-primary),var(--sbc-secondary));
                    font-size:16px; font-weight:800;
                }
                .sbc-thread-head-meta{ display:flex; gap:12px; align-items:center; color:#5d6678; font-size:12px; }
                #sbc-admin-thread-meta{
                    display:grid; grid-template-columns:1.6fr .8fr .8fr; gap:14px;
                    margin:12px 0 4px; padding:13px 15px; background:color-mix(in srgb,var(--sbc-primary) 5%,white);
                    border:1px solid color-mix(in srgb,var(--sbc-primary) 18%,white); border-radius:9px; line-height:1.3;
                }
                #sbc-admin-thread-meta > div{ display:flex; flex-direction:column; gap:4px; }
                #sbc-admin-thread-meta small{ color:#6e788b; }
                #sbc-admin-thread-meta strong{ color:#283347; font-size:12px; font-weight:600; }
                #sbc-admin-thread-typing{ margin:4px 2px; }
                #sbc-admin-messages{
                    flex:1; min-height:330px; max-height:410px; margin:4px 0 10px;
                    border:0; border-radius:0; background:#fff; padding:12px 8px;
                }
                .sbc-admin-msg{ max-width:70%; border-radius:7px; box-shadow:none; }
                .sbc-admin-msg.user{ margin-left:0; background:#f3f4f6; border-color:#eef0f3; border-bottom-left-radius:2px; }
                .sbc-admin-msg.agent{ margin-left:auto; background:linear-gradient(135deg,color-mix(in srgb,var(--sbc-primary) 15%,white),color-mix(in srgb,var(--sbc-secondary) 10%,white)); color:#243247; border:1px solid color-mix(in srgb,var(--sbc-primary) 22%,white); border-bottom-right-radius:2px; }
                .sbc-admin-msg.agent strong{ color:var(--sbc-primary); }
                .sbc-admin-actions{
                    display:flex; flex-direction:column; gap:7px; margin-top:auto; padding:0;
                    border:1px solid #dfe5ee; border-radius:8px; overflow:hidden; background:#fff;
                }
                .sbc-reply-tabs{ display:flex; gap:28px; height:36px; align-items:center; padding:0 13px; border-bottom:1px solid #e8ecf2; color:#6b7485; font-size:12px; }
                .sbc-reply-tabs strong{ align-self:stretch; display:flex; align-items:center; color:var(--sbc-primary); border-bottom:2px solid var(--sbc-primary); }
                #sbc-admin-canned{ padding:0 10px; margin:0; }
                #sbc-admin-reply{ min-height:70px; padding:8px 12px; border:0; box-shadow:none!important; }
                .sbc-admin-action-buttons{ justify-content:flex-end; padding:0 10px 10px; }
                .sbc-admin-action-buttons #sbc-admin-send{ min-width:125px; background:linear-gradient(135deg,var(--sbc-primary),var(--sbc-secondary)); border-color:var(--sbc-primary); }
                .sbc-side-card{ background:#fff; border:1px solid #e6ebf2; border-radius:8px; padding:12px; }
                .sbc-side-card h3{ margin:-12px -12px 12px; padding:11px 12px; border-bottom:1px solid #e9edf3; font-size:13px; }
                .sbc-side-profile strong, .sbc-side-profile small{ display:block; }
                .sbc-side-profile small{ margin-top:3px; color:#7c8596; font-size:11px; overflow-wrap:anywhere; }
                .sbc-transaction-card dl{ margin:0; display:grid; gap:7px; }
                .sbc-transaction-card dl div{ display:flex; justify-content:space-between; gap:10px; border-bottom:1px solid #edf0f4; padding-bottom:6px; }
                .sbc-transaction-card dt{ color:#7b8495; }
                .sbc-transaction-card dd{ margin:0; max-width:62%; text-align:right; font-weight:700; overflow-wrap:anywhere; }
                .sbc-transaction-card details{ margin-top:9px; }
                .sbc-transaction-card summary{ color:var(--sbc-primary); cursor:pointer; font-weight:700; }
                .sbc-transaction-card pre{ max-height:160px; overflow:auto; padding:8px; background:#111827; color:#dbeafe; border-radius:6px; white-space:pre-wrap; word-break:break-word; font-size:10px; }
                #sbc-admin-balance-box{ margin:0; padding:0; border:0; background:#fff; }
                #sbc-admin-balance-box > div:first-child{ display:flex; flex-direction:column; gap:4px; }
                #sbc-admin-balance-box .sbc-balance-amount{ font-size:22px; }
                #sbc-admin-adjust-row{ display:grid; grid-template-columns:1fr; gap:7px; }
                #sbc-admin-adjust-row input{ width:100%!important; box-sizing:border-box; }
                #sbc-adjust-apply{ width:100%; color:#fff; background:linear-gradient(135deg,var(--sbc-primary),var(--sbc-secondary)); border-color:var(--sbc-primary); }
                .sbc-admin-withdrawal-alert{ display:flex; flex-direction:column; gap:7px; margin:0; }
                .sbc-admin-withdrawal-alert span{ font-weight:400; color:#7b4b24; }
                .sbc-admin-withdrawal-alert .button{ text-align:center; color:#fff; background:#e86b08; border-color:#e86b08; }
                #sbc-admin-history{ margin:0; padding:0; border:0; background:#fff; }
                .sbc-history-list{ display:flex; flex-direction:column; gap:0; margin:0; }
                .sbc-history-btn{ display:flex; justify-content:space-between; width:100%; padding:10px 4px; border:0; border-bottom:1px solid #edf0f4; border-radius:0; text-align:left; }
                .sbc-history-btn span{ font-weight:700; text-transform:capitalize; }
                .sbc-history-btn i{ display:inline-block; width:7px; height:7px; margin-right:7px; border:2px solid #23a869; border-radius:50%; }
                .sbc-history-btn small{ color:#8b93a2; font-size:9px; }
                .sbc-history-btn.active{ color:var(--sbc-primary); background:color-mix(in srgb,var(--sbc-primary) 5%,white); }
                @media (max-width:1200px){
                    .sbc-thread-layout{ grid-template-columns:1fr; }
                    .sbc-customer-sidebar{ display:grid; grid-template-columns:1fr 1fr; }
                    .sbc-chat-workspace{ border-right:0; }
                }
                @media (max-width: 900px){
                    .sbc-admin-grid{
                        grid-template-columns:1fr;
                    }
                    #sbc-admin-customer-list{
                        max-height:none;
                    }
                    .sbc-admin-grid > .sbc-admin-box:first-child,
                    .sbc-admin-toolbar{ position:static; }
                    .sbc-customer-sidebar{ grid-template-columns:1fr; }
                    .sbc-thread-head-meta{ flex-wrap:wrap; justify-content:flex-end; }
                }
            </style>

            <div class="sbc-admin-toolbar">
                <input type="text" id="sbc-admin-search" placeholder="Search email, name, service..." />
                <select id="sbc-admin-filter-status">
                    <option value="all">All statuses</option>
                    <option value="collecting">Collecting</option>
                    <option value="waiting">Waiting</option>
                    <option value="active">Active</option>
                    <option value="closed">Closed</option>
                </select>
                <button class="button" id="sbc-admin-refresh">Refresh</button>
                <button class="button button-secondary" id="sbc-admin-deadzone">Deadzone / Reset Frontend Cache</button>
                <button class="button button-danger" id="sbc-admin-clear-all">Clear All Chats / Messages</button>
                <div class="sbc-admin-live">Live refresh ON</div>
            </div>

            <div class="sbc-admin-grid">
                <div class="sbc-admin-box">
                    <h2 style="margin-top:0;">Customers</h2>
                    <div id="sbc-admin-customer-list"></div>
                </div>

                <div class="sbc-admin-box">
                    <div id="sbc-admin-thread">
                        <div class="sbc-admin-empty">Select a customer to view the thread.</div>
                    </div>
                </div>
            </div>
        </div>
        <?php
    }

    private function commercial_server_url() {
        return untrailingslashit((string) get_option('sbc_license_server_url', ''));
    }

    private function commercial_license_required() {
        // Version 4 is funded by prepaid support tokens, not paid licenses.
        return false;
    }

    private function commercial_license_status($force = false) {
        $cached = get_transient('sbc_commercial_license_status');
        if (!$force && is_array($cached)) return $cached;
        $server = $this->commercial_server_url();
        $key = (string) get_option('sbc_commercial_license_key', '');
        if (!$server || !$key) return ['license_active' => false, 'updates_active' => false, 'updates_status' => 'disabled'];
        $response = wp_remote_post($server . '/wp-json/support-chat-licenses/v1/validate', [
            'timeout' => 15,
            'headers' => ['Content-Type' => 'application/json'],
            'body' => wp_json_encode(['license_key' => $key, 'domain' => home_url('/')]),
        ]);
        if (is_wp_error($response)) {
            $last = get_option('sbc_commercial_last_valid_status', []);
            if (is_array($last) && !empty($last['license_active']) && (int)($last['last_validated_ts'] ?? 0) >= time() - (3 * DAY_IN_SECONDS)) {
                $last['validation_deferred'] = true;
                return $last;
            }
            return ['license_active' => false, 'updates_active' => false, 'updates_status' => 'validation_unavailable'];
        }
        $data = json_decode(wp_remote_retrieve_body($response), true);
        if (wp_remote_retrieve_response_code($response) !== 200 || !is_array($data)) {
            return ['license_active' => false, 'updates_active' => false, 'updates_status' => 'disabled'];
        }
        $data['last_validated_ts'] = time();
        set_transient('sbc_commercial_license_status', $data, 6 * HOUR_IN_SECONDS);
        if (!empty($data['license_active'])) update_option('sbc_commercial_last_valid_status', $data, false);
        return $data;
    }

    private function commercial_license_active() {
        return !empty($this->commercial_license_status()['license_active']);
    }

    public function commercial_license_page() {
        if (!current_user_can('manage_options')) return;
        if (!empty($_GET['sbc_paystack_reference']) && !empty($_GET['sbc_delivery_token']) && $this->commercial_server_url()) {
            $reference = sanitize_text_field(wp_unslash($_GET['sbc_paystack_reference']));
            $delivery_token = sanitize_text_field(wp_unslash($_GET['sbc_delivery_token']));
            $payment_response = wp_remote_post($this->commercial_server_url() . '/wp-json/support-chat-licenses/v1/payment-status', [
                'timeout'=>20, 'headers'=>['Content-Type'=>'application/json'], 'body'=>wp_json_encode(['reference'=>$reference,'delivery_token'=>$delivery_token]),
            ]);
            $payment_data = is_wp_error($payment_response) ? [] : json_decode(wp_remote_retrieve_body($payment_response), true);
            if (!empty($payment_data['license_key'])) {
                update_option('sbc_commercial_license_key', sanitize_text_field($payment_data['license_key']), false);
                delete_transient('sbc_commercial_license_status');
            }
        }
        $status = $this->commercial_license_status(!empty($_GET['refresh']));
        $server = $this->commercial_server_url();
        $key = (string) get_option('sbc_commercial_license_key', '');
        ?>
        <div class="wrap"><h1>License & Updates</h1>
        <?php if (isset($_GET['sbc_license_message'])) : ?><div class="notice notice-info"><p><?php echo esc_html(sanitize_text_field(wp_unslash($_GET['sbc_license_message']))); ?></p></div><?php endif; ?>
        <table class="widefat striped" style="max-width:760px"><tbody>
        <tr><th>Lifetime license</th><td><?php echo !empty($status['license_active']) ? '<strong style="color:#138a4b">Active</strong>' : '<strong style="color:#b42318">Not active</strong>'; ?></td></tr>
        <tr><th>Update plan</th><td><?php echo !empty($status['updates_active']) ? '<strong style="color:#138a4b">Active</strong>' : '<strong>Disabled</strong>'; ?></td></tr>
        <tr><th>Updates expire</th><td><?php echo esc_html($status['updates_expires_at'] ?? '—'); ?></td></tr>
        <tr><th>Licensed website</th><td><?php echo esc_html(home_url('/')); ?></td></tr>
        </tbody></table>
        <h2>Connection</h2><form method="post" action="options.php"><?php settings_fields('sbc_license_settings'); ?>
        <p><label>Licensing server URL<br><input class="regular-text" name="sbc_license_server_url" value="<?php echo esc_attr($server); ?>" placeholder="https://licenses.example.com"></label></p><?php submit_button('Save server'); ?></form>
        <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
        <input type="hidden" name="action" value="sbc_activate_license"><?php wp_nonce_field('sbc_activate_license'); ?>
        <p><label>License key<br><input class="regular-text" name="license_key" value="<?php echo esc_attr($key); ?>" autocomplete="off"></label></p><button class="button button-primary">Activate license</button></form>
        <?php if ($server) : ?><h2>Buy or renew</h2><p>A lifetime purchase keeps this installed bot working. Monthly or yearly maintenance enables feature updates.</p>
        <?php foreach (['lifetime'=>'Buy lifetime license','updates_monthly'=>'Monthly updates','updates_yearly'=>'Yearly updates'] as $type=>$label) : ?>
        <form style="display:inline-block;margin-right:8px" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"><input type="hidden" name="action" value="sbc_license_checkout"><input type="hidden" name="purchase_type" value="<?php echo esc_attr($type); ?>"><?php wp_nonce_field('sbc_license_checkout'); ?><input type="email" name="email" required placeholder="Billing email"><button class="button"><?php echo esc_html($label); ?></button></form>
        <?php endforeach; endif; ?></div><?php
    }

    public function activate_commercial_license() {
        if (!current_user_can('manage_options')) wp_die('Unauthorized');
        check_admin_referer('sbc_activate_license');
        $key = sanitize_text_field(wp_unslash($_POST['license_key'] ?? ''));
        update_option('sbc_commercial_license_key', $key, false);
        delete_transient('sbc_commercial_license_status');
        $status = $this->commercial_license_status(true);
        $message = !empty($status['license_active']) ? 'License activated successfully.' : 'The license could not be activated for this website.';
        wp_safe_redirect(add_query_arg(['page'=>'sbc-license','sbc_license_message'=>$message], admin_url('admin.php'))); exit;
    }

    public function commercial_checkout() {
        if (!current_user_can('manage_options')) wp_die('Unauthorized');
        check_admin_referer('sbc_license_checkout');
        $server = $this->commercial_server_url();
        $response = wp_remote_post($server . '/wp-json/support-chat-licenses/v1/checkout', [
            'timeout'=>20, 'headers'=>['Content-Type'=>'application/json'],
            'body'=>wp_json_encode(['email'=>sanitize_email(wp_unslash($_POST['email']??'')),'domain'=>home_url('/'),'callback_url'=>admin_url('admin.php?page=sbc-license'),'purchase_type'=>sanitize_key($_POST['purchase_type']??''),'license_key'=>(string)get_option('sbc_commercial_license_key','')]),
        ]);
        $data = is_wp_error($response) ? [] : json_decode(wp_remote_retrieve_body($response), true);
        if (!empty($data['authorization_url'])) { wp_redirect(esc_url_raw($data['authorization_url'])); exit; }
        $message = is_wp_error($response) ? $response->get_error_message() : ($data['message'] ?? 'Checkout could not be started.');
        wp_safe_redirect(add_query_arg(['page'=>'sbc-license','sbc_license_message'=>$message], admin_url('admin.php'))); exit;
    }

    public function commercial_update_check($transient) {
        if(!is_object($transient))return $transient;
        $server=untrailingslashit((string)get_option('sbc_token_server_url',''));$secret=(string)get_option('sbc_token_site_secret','');if(!$server||strlen($secret)<32)return $transient;
        $site_url=home_url('/');$domain=strtolower((string)wp_parse_url($site_url,PHP_URL_HOST));$canonical='update|'.$domain.'|'.SBC_PLUGIN_VERSION;
        $response=wp_remote_post($server.'/wp-json/support-chat-sales/v2/update-check',['timeout'=>15,'headers'=>['Content-Type'=>'application/json'],'body'=>wp_json_encode(['site_url'=>$site_url,'current_version'=>SBC_PLUGIN_VERSION,'signature'=>hash_hmac('sha256',$canonical,$secret)])]);
        if(is_wp_error($response)||wp_remote_retrieve_response_code($response)!==200)return $transient;
        $data=json_decode(wp_remote_retrieve_body($response),true);if(empty($data['version']))return $transient;
        $file=plugin_basename(__FILE__);$item=(object)['slug'=>'support-bonus-chat','plugin'=>$file,'new_version'=>sanitize_text_field($data['version']),'url'=>$server,'package'=>esc_url_raw($data['download_url']??''),'upgrade_notice'=>sanitize_textarea_field($data['release_notes']??'')];
        if(version_compare($item->new_version,SBC_PLUGIN_VERSION,'>')&&!empty($item->package))$transient->response[$file]=$item;else{$item->package='';$transient->no_update[$file]=$item;}
        return $transient;
    }

    public function plugin_action_links($links){
        if(!current_user_can('update_plugins'))return $links;
        $check_url=wp_nonce_url(admin_url('admin-post.php?action=sbc_check_updates'),'sbc_check_updates');
        array_unshift($links,'<a href="'.esc_url(admin_url('admin.php?page=sbc-settings')).'">Settings</a>');
        $links[]='<a href="'.esc_url($check_url).'">Check for updates</a>';
        return $links;
    }

    public function plugin_row_meta($links,$file){
        if($file!==plugin_basename(__FILE__))return $links;
        $links[]='<a href="https://bluishgen.com.ng" target="_blank" rel="noopener noreferrer">Contact us</a>';
        return $links;
    }

    public function check_for_updates_now(){
        if(!current_user_can('update_plugins'))wp_die('You are not allowed to check for plugin updates.');
        check_admin_referer('sbc_check_updates');
        delete_site_transient('update_plugins');
        wp_clean_plugins_cache(true);
        if(!function_exists('wp_update_plugins'))require_once ABSPATH.'wp-admin/includes/update.php';
        wp_update_plugins();
        wp_safe_redirect(add_query_arg('sbc_update_checked','1',admin_url('plugins.php')));exit;
    }

    public function commercial_update_notice() {
        if (!current_user_can('manage_options') || !$this->commercial_license_required()) return;
        $status=$this->commercial_license_status();
        if (empty($status['license_active'])) {
            echo '<div class="notice notice-error"><p>BluPass requires lifetime license activation. <a href="' . esc_url(admin_url('admin.php?page=sbc-license')) . '">Open License & Updates</a></p></div>'; return;
        }
        $expires=!empty($status['updates_expires_at'])?strtotime($status['updates_expires_at']):0;
        if (empty($status['updates_active'])) {
            echo '<div class="notice notice-warning"><p>BluPass update plan is disabled. The installed bot still works, but feature updates are unavailable. <a href="' . esc_url(admin_url('admin.php?page=sbc-license')) . '">Renew updates</a></p></div>';
        } elseif ($expires && $expires <= time() + 7 * DAY_IN_SECONDS) {
            echo '<div class="notice notice-info"><p>BluPass updates expire soon. <a href="' . esc_url(admin_url('admin.php?page=sbc-license')) . '">Renew now</a></p></div>';
        }
    }

    public function mobile_app_page() {
        if (!current_user_can('manage_options')) return;
        global $wpdb;
        $t = $this->tables();
        $devices = $wpdb->get_results("SELECT * FROM {$t['devices']} ORDER BY created_at DESC");
        $pairing_code = isset($_GET['sbc_pairing_code']) ? sanitize_text_field(wp_unslash($_GET['sbc_pairing_code'])) : '';
        ?>
        <div class="wrap">
            <h1>BluPass Agent Mobile App</h1>
            <p>Pair an Android phone without sharing a WordPress password. Pairing codes expire after five minutes and work once.</p>
            <?php if ($pairing_code) : ?>
                <div class="notice notice-success"><p><strong>Pairing code: <?php echo esc_html($pairing_code); ?></strong></p></div>
            <?php endif; ?>
            <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
                <input type="hidden" name="action" value="sbc_generate_pairing_code" />
                <?php wp_nonce_field('sbc_generate_pairing_code'); ?>
                <button class="button button-primary">Generate pairing code</button>
            </form>
            <h2>Push notifications</h2>
            <form method="post" action="options.php"><?php settings_fields('sbc_push_settings'); ?>
                <p><label>OneSignal App ID<br><input class="regular-text" name="sbc_onesignal_app_id" value="<?php echo esc_attr(get_option('sbc_onesignal_app_id', '')); ?>"></label></p>
                <p><label>OneSignal REST API key<br><input type="password" autocomplete="new-password" class="regular-text" name="sbc_onesignal_rest_api_key" value="<?php echo esc_attr(get_option('sbc_onesignal_rest_api_key', '')); ?>"></label></p>
                <?php submit_button('Save push settings'); ?>
            </form>
            <h2>Paired devices</h2>
            <table class="widefat striped">
                <thead><tr><th>Device</th><th>Created</th><th>Last seen</th><th>Status</th><th>Action</th></tr></thead>
                <tbody>
                <?php if (!$devices) : ?><tr><td colspan="5">No phones paired yet.</td></tr><?php endif; ?>
                <?php foreach ($devices as $device) : ?>
                    <tr>
                        <td><?php echo esc_html($device->device_name ?: 'Android device'); ?></td>
                        <td><?php echo esc_html($device->created_at); ?></td>
                        <td><?php echo esc_html($device->last_seen_at ?: 'Never'); ?></td>
                        <td><?php echo $device->revoked_at ? 'Revoked' : 'Active'; ?></td>
                        <td><?php if (!$device->revoked_at) : ?><form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"><input type="hidden" name="action" value="sbc_revoke_mobile_device"><input type="hidden" name="device_id" value="<?php echo (int)$device->id; ?>"><?php wp_nonce_field('sbc_revoke_mobile_device_'.$device->id); ?><button class="button">Revoke</button></form><?php endif; ?></td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
            </table>
        </div>
        <?php
    }

    public function generate_mobile_pairing_code() {
        if (!current_user_can('manage_options')) wp_die('Unauthorized');
        check_admin_referer('sbc_generate_pairing_code');
        $code = (string) random_int(10000000, 99999999);
        set_transient('sbc_mobile_pairing_' . hash('sha256', $code), [
            'admin_user_id' => get_current_user_id(),
            'created_at' => time(),
        ], 5 * MINUTE_IN_SECONDS);
        wp_safe_redirect(add_query_arg([
            'page' => 'sbc-mobile-app',
            'sbc_pairing_code' => $code,
        ], admin_url('admin.php')));
        exit;
    }

    public function revoke_mobile_device() {
        if (!current_user_can('manage_options')) wp_die('Unauthorized');
        $id = absint($_POST['device_id'] ?? 0);
        check_admin_referer('sbc_revoke_mobile_device_' . $id);
        global $wpdb; $t = $this->tables();
        $wpdb->update($t['devices'], ['revoked_at' => current_time('mysql')], ['id' => $id]);
        wp_safe_redirect(admin_url('admin.php?page=sbc-mobile-app')); exit;
    }

    private function send_mobile_push($title, $message, $chat_id, $target_user_id=0) {
        $app_id = trim((string) get_option('sbc_onesignal_app_id', ''));
        $api_key = trim((string) get_option('sbc_onesignal_rest_api_key', ''));
        if (!$app_id || !$api_key) return false;
        global $wpdb;
        $t = $this->tables();
        $where="revoked_at IS NULL AND push_subscription_id <> ''";
        if($target_user_id)$where.=$wpdb->prepare(' AND admin_user_id=%d',absint($target_user_id));
        $ids = $wpdb->get_col("SELECT push_subscription_id FROM {$t['devices']} WHERE {$where}");
        $ids = array_values(array_unique(array_filter(array_map('sanitize_text_field', $ids ?: []))));
        if (!$ids) return false;
        $response = wp_remote_post('https://api.onesignal.com/notifications', [
            'timeout' => 15,
            'headers' => ['Authorization' => 'Key ' . $api_key, 'Content-Type' => 'application/json'],
            'body' => wp_json_encode([
                'app_id' => $app_id,
                'include_subscription_ids' => $ids,
                'headings' => ['en' => sanitize_text_field($title)],
                'contents' => ['en' => sanitize_text_field($message)],
                'data' => ['chat_id' => absint($chat_id), 'type' => 'support_chat'],
                'priority' => 10,
                'android_sound' => 'sbc_alert',
                'existing_android_channel_id' => 'blupass_support_alerts_v2',
                'android_visibility' => 1,
            ]),
        ]);
        if (is_wp_error($response) || wp_remote_retrieve_response_code($response) >= 300) {
            error_log('BluPass push notification failed.');
            return false;
        }
        return true;
    }

    public function notify_transferred_agent($chat_id,$target_user_id){
        if($target_user_id)$this->send_mobile_push('Chat transferred to you','A customer conversation is waiting for you.',absint($chat_id),absint($target_user_id));
    }

    public function register_mobile_rest_routes() {
        $ns = 'support-bonus-chat/v1';
        register_rest_route($ns, '/pair', [
            'methods' => 'POST', 'callback' => [$this, 'mobile_pair'], 'permission_callback' => '__return_true',
        ]);
        register_rest_route($ns, '/login', [
            'methods' => 'POST', 'callback' => [$this, 'mobile_login'], 'permission_callback' => '__return_true',
        ]);
        register_rest_route($ns, '/me', [
            'methods' => 'GET', 'callback' => [$this, 'mobile_me'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/inbox', [
            'methods' => 'GET', 'callback' => [$this, 'mobile_inbox'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/chats/(?P<id>\d+)', [
            'methods' => 'GET', 'callback' => [$this, 'mobile_chat'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/chats/(?P<id>\d+)/read', [
            'methods' => 'POST', 'callback' => [$this, 'mobile_mark_read'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/chats/(?P<id>\d+)/reply', [
            'methods' => 'POST', 'callback' => [$this, 'mobile_reply'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/chats/(?P<id>\d+)/close', [
            'methods' => 'POST', 'callback' => [$this, 'mobile_close'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/chats/(?P<id>\d+)/transfer', [
            'methods' => 'POST', 'callback' => [$this, 'mobile_transfer'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/team', [
            'methods' => 'GET', 'callback' => [$this, 'mobile_team'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/admin/directory', [
            'methods' => 'GET', 'callback' => [$this, 'mobile_admin_directory'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/admin/agents', [
            'methods' => 'POST', 'callback' => [$this, 'mobile_admin_save_agent'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/admin/agents/(?P<id>\d+)', [
            'methods' => 'DELETE', 'callback' => [$this, 'mobile_admin_delete_agent'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/admin/canned', [
            'methods' => 'POST', 'callback' => [$this, 'mobile_admin_save_canned'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/admin/canned/(?P<id>\d+)', [
            'methods' => 'DELETE', 'callback' => [$this, 'mobile_admin_delete_canned'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/devices/push', [
            'methods' => 'POST', 'callback' => [$this, 'mobile_save_push'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
        register_rest_route($ns, '/devices/current', [
            'methods' => 'DELETE', 'callback' => [$this, 'mobile_revoke'], 'permission_callback' => [$this, 'mobile_permission'],
        ]);
    }

    private function mobile_bearer_token($request) {
        $header = $request->get_header('authorization');
        if (!$header && isset($_SERVER['HTTP_AUTHORIZATION'])) $header = wp_unslash($_SERVER['HTTP_AUTHORIZATION']);
        return preg_match('/^Bearer\s+(.+)$/i', (string) $header, $m) ? trim($m[1]) : '';
    }

    public function mobile_permission($request) {
        if ($this->commercial_license_required() && !$this->commercial_license_active()) {
            return new WP_Error('sbc_license_required', 'The website license is not active.', ['status' => 403]);
        }
        global $wpdb;
        $token = $this->mobile_bearer_token($request);
        if (strlen($token) < 32) return new WP_Error('sbc_mobile_unauthorized', 'A valid device token is required.', ['status' => 401]);
        $t = $this->tables();
        $hash = hash('sha256', $token);
        $device = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$t['devices']} WHERE token_hash = %s AND revoked_at IS NULL LIMIT 1", $hash
        ));
        if (!$device || !hash_equals((string) $device->token_hash, $hash)) {
            return new WP_Error('sbc_mobile_unauthorized', 'This phone is not paired or has been revoked.', ['status' => 401]);
        }
        if (!function_exists('sbc_ops') || !sbc_ops()->is_registered_staff((int)$device->admin_user_id)) {
            return new WP_Error('sbc_mobile_forbidden', 'The paired administrator no longer has access.', ['status' => 403]);
        }
        $this->current_mobile_device = $device;
        $wpdb->update($t['devices'], ['last_seen_at' => current_time('mysql')], ['id' => $device->id]);
        return true;
    }

    public function mobile_pair($request) {
        if (wp_parse_url(home_url('/'), PHP_URL_SCHEME) !== 'https' && !(defined('WP_DEBUG') && WP_DEBUG)) {
            return new WP_Error('sbc_https_required', 'HTTPS is required for mobile pairing.', ['status' => 403]);
        }
        $code = preg_replace('/\D+/', '', (string) $request->get_param('code'));
        $device_name = sanitize_text_field((string) $request->get_param('device_name'));
        $ip = sanitize_key(substr((string) ($_SERVER['REMOTE_ADDR'] ?? 'unknown'), 0, 45));
        $rate_key = 'sbc_pair_attempt_' . md5($ip);
        $attempts = (int) get_transient($rate_key);
        if ($attempts >= 10) return new WP_Error('sbc_pair_rate_limited', 'Too many pairing attempts. Try again later.', ['status' => 429]);
        set_transient($rate_key, $attempts + 1, 10 * MINUTE_IN_SECONDS);
        if (strlen($code) !== 8) return new WP_Error('sbc_pair_invalid', 'The pairing code is invalid or expired.', ['status' => 400]);
        $pair_key = 'sbc_mobile_pairing_' . hash('sha256', $code);
        $pair = get_transient($pair_key);
        if (!$pair || empty($pair['admin_user_id']) || !user_can((int)$pair['admin_user_id'],'manage_options')) {
            return new WP_Error('sbc_pair_invalid', 'The pairing code is invalid or expired.', ['status' => 400]);
        }
        delete_transient($pair_key);
        delete_transient($rate_key);
        $token = rtrim(strtr(base64_encode(random_bytes(48)), '+/', '-_'), '=');
        global $wpdb;
        $t = $this->tables();
        $ok = $wpdb->insert($t['devices'], [
            'admin_user_id' => (int) $pair['admin_user_id'],
            'device_name' => $device_name ?: 'Android device',
            'token_hash' => hash('sha256', $token),
            'created_at' => current_time('mysql'),
        ]);
        if (!$ok) return new WP_Error('sbc_pair_failed', 'The phone could not be paired.', ['status' => 500]);
        return new WP_REST_Response([
            'token' => $token,
            'site_name' => get_bloginfo('name'),
            'site_url' => home_url('/'),
            'device_id' => (int) $wpdb->insert_id,
            'onesignal_app_id' => sanitize_text_field((string) get_option('sbc_onesignal_app_id', '')),
            'palette'=>$this->mobile_palette(),
        ], 201);
    }

    public function mobile_login($request) {
        if (wp_parse_url(home_url('/'), PHP_URL_SCHEME) !== 'https' && !(defined('WP_DEBUG') && WP_DEBUG)) {
            return new WP_Error('sbc_https_required', 'HTTPS is required for app login.', ['status' => 403]);
        }
        $username=sanitize_user((string)$request->get_param('username'));
        $password=(string)$request->get_param('password');
        $device_name=sanitize_text_field((string)$request->get_param('device_name'));
        $ip=sanitize_text_field(substr((string)($_SERVER['REMOTE_ADDR']??'unknown'),0,45));
        $rate_key='sbc_mobile_login_'.hash('sha256',$ip.'|'.strtolower($username));
        $attempts=(int)get_transient($rate_key);
        if($attempts>=8)return new WP_Error('sbc_login_rate_limited','Too many login attempts. Try again in fifteen minutes.',['status'=>429]);
        set_transient($rate_key,$attempts+1,15*MINUTE_IN_SECONDS);
        if(!$username||!$password)return new WP_Error('sbc_login_required','WordPress username and password are required.',['status'=>400]);
        $user=wp_authenticate($username,$password);
        if(is_wp_error($user)||!sbc_ops()->is_registered_staff((int)$user->ID))return new WP_Error('sbc_login_invalid','The login is incorrect or this account is not registered as an active support agent.',['status'=>401]);
        delete_transient($rate_key);
        $token=rtrim(strtr(base64_encode(random_bytes(48)),'+/','-_'),'=');
        global $wpdb;$t=$this->tables();
        $ok=$wpdb->insert($t['devices'],['admin_user_id'=>(int)$user->ID,'device_name'=>$device_name?:'Android device','token_hash'=>hash('sha256',$token),'created_at'=>current_time('mysql')]);
        if(!$ok)return new WP_Error('sbc_login_failed','The secure app session could not be created.',['status'=>500]);
        return new WP_REST_Response([
            'token'=>$token,'site_name'=>get_bloginfo('name'),'site_url'=>home_url('/'),'device_id'=>(int)$wpdb->insert_id,
            'onesignal_app_id'=>sanitize_text_field((string)get_option('sbc_onesignal_app_id','')),
            'palette'=>$this->mobile_palette(),
            'staff'=>sbc_ops()->staff_identity((int)$user->ID),
        ],201);
    }

    private function mobile_palette(){return ['primary'=>sanitize_hex_color(get_option('sbc_primary_color','#0867ed'))?:'#0867ed','secondary'=>sanitize_hex_color(get_option('sbc_secondary_color','#4f46e5'))?:'#4f46e5','accent'=>sanitize_hex_color(get_option('sbc_accent_color','#14b8a6'))?:'#14b8a6'];}

    public function mobile_me() {
        return rest_ensure_response(array_merge([
            'site_name' => get_bloginfo('name'),
            'site_url' => home_url('/'),
            'device_name' => $this->current_mobile_device->device_name,
            'token_balance' => function_exists('sbc_ops') ? sbc_ops()->token_balance() : 0,
            'palette'=>$this->mobile_palette(),
        ], function_exists('sbc_ops') ? sbc_ops()->staff_identity((int)$this->current_mobile_device->admin_user_id) : []));
    }

    private function mobile_audit($action, $chat_id = 0) {
        if (!$this->current_mobile_device) return;
        global $wpdb; $t = $this->tables();
        $wpdb->insert($t['mobile_audit'], [
            'device_id' => (int) $this->current_mobile_device->id,
            'admin_user_id' => (int) $this->current_mobile_device->admin_user_id,
            'chat_id' => absint($chat_id),
            'action_name' => sanitize_key($action),
            'created_at' => current_time('mysql'),
        ]);
    }

    public function mobile_inbox($request) {
        global $wpdb;
        $t = $this->tables();
        $rows = $wpdb->get_results("SELECT * FROM {$t['chats']} ORDER BY updated_at DESC, id DESC LIMIT 200");
        $groups = [];
        foreach ($rows as $chat) {
            if (function_exists('sbc_ops') && !sbc_ops()->can_access_chat($chat, (int)$this->current_mobile_device->admin_user_id)) continue;
            $key = $chat->customer_key ?: ('chat_' . $chat->id);
            if (isset($groups[$key])) continue;
            $groups[$key] = [
                'customer_key' => $key,
                'chat_id' => (int) $chat->id,
                'full_name' => $chat->full_name,
                'customer_email' => $chat->customer_email,
                'service_type' => $chat->service_type,
                'status' => $chat->status,
                'unread_count' => (int) $wpdb->get_var($wpdb->prepare(
                    "SELECT COALESCE(SUM(admin_unread_count),0) FROM {$t['chats']} WHERE customer_key = %s", $key
                )),
                'pending_withdrawal' => $chat->user_id ? $this->has_pending_withdrawal((int) $chat->user_id) : false,
                'updated_at' => $chat->updated_at,
                'assigned_agent_id' => (int)($chat->assigned_agent_id ?? 0),
            ];
        }
        return rest_ensure_response([
            'global_unread_count' => (int) $wpdb->get_var("SELECT COALESCE(SUM(admin_unread_count),0) FROM {$t['chats']}"),
            'customers' => array_values($groups),
        ]);
    }

    private function mobile_chat_data($chat) {
        global $wpdb;
        $t = $this->tables();
        $history = $wpdb->get_results($wpdb->prepare(
            "SELECT id, status, service_type, updated_at, admin_unread_count FROM {$t['chats']} WHERE customer_key = %s ORDER BY updated_at DESC, id DESC LIMIT 3",
            $chat->customer_key
        ), ARRAY_A);
        return [
            'chat_id' => (int) $chat->id,
            'full_name' => $chat->full_name,
            'customer_email' => $chat->customer_email,
            'service_type' => $chat->service_type,
            'initial_message' => $chat->initial_message,
            'status' => $chat->status,
            'unread_count' => (int) $chat->admin_unread_count,
            'timer_expires_ts' => (int) $chat->timer_expires_ts,
            'compensation_awarded' => (bool) $chat->compensation_awarded,
            'compensation_amount' => (float) $chat->compensation_amount,
            'balance' => $chat->user_id ? $this->get_user_balance((int) $chat->user_id) : 0,
            'pending_withdrawal' => $chat->user_id ? $this->has_pending_withdrawal((int) $chat->user_id) : false,
            'complaint_reason' => $chat->complaint_reason,
            'transaction' => $chat->transaction_snapshot ? json_decode($chat->transaction_snapshot, true) : null,
            'messages' => $this->load_messages((int) $chat->id),
            'history' => $history ?: [],
            'created_at' => $chat->created_at,
            'updated_at' => $chat->updated_at,
            'assigned_agent_id' => (int)($chat->assigned_agent_id ?? 0),
            'department_id' => (int)($chat->department_id ?? 0),
            'staff' => function_exists('sbc_ops') ? sbc_ops()->staff_identity((int)$this->current_mobile_device->admin_user_id) : null,
            'agents' => function_exists('sbc_ops') ? sbc_ops()->agent_choices() : [],
            'canned_replies' => function_exists('sbc_ops') ? sbc_ops()->visible_canned_for_mobile((int)$this->current_mobile_device->admin_user_id) : [],
        ];
    }

    public function mobile_chat($request) {
        global $wpdb;
        $t = $this->tables();
        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", absint($request['id'])));
        if (!$chat) return new WP_Error('sbc_chat_missing', 'Chat not found.', ['status' => 404]);
        if (function_exists('sbc_ops')) {
            sbc_ops()->check_inactivity((int)$chat->id);
            $chat=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id=%d",$chat->id));
            if($chat->status!=='closed'){
                $claim=sbc_ops()->claim_chat((int)$chat->id,(int)$this->current_mobile_device->admin_user_id,user_can((int)$this->current_mobile_device->admin_user_id,'sbc_manage_support'));
                if(is_wp_error($claim))return new WP_Error($claim->get_error_code(),$claim->get_error_message(),['status'=>409]);
                $chat=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id=%d",$chat->id));
            }
        }
        return rest_ensure_response($this->mobile_chat_data($chat));
    }

    public function mobile_mark_read($request) {
        global $wpdb;
        $t = $this->tables();
        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", absint($request['id'])));
        if (!$chat) return new WP_Error('sbc_chat_missing', 'Chat not found.', ['status' => 404]);
        $wpdb->update($t['chats'], ['admin_unread_count' => 0], ['customer_key' => $chat->customer_key]);
        $this->mobile_audit('mark_read', $chat->id);
        return rest_ensure_response(['success' => true]);
    }

    public function mobile_reply($request) {
        global $wpdb;
        $t = $this->tables();
        $chat_id = absint($request['id']);
        $message = sanitize_textarea_field((string) $request->get_param('message'));
        $request_key = sanitize_text_field((string) $request->get_param('request_key'));
        if (!$message) return new WP_Error('sbc_message_required', 'A reply is required.', ['status' => 400]);
        if (!$request_key || strlen($request_key) > 64) return new WP_Error('sbc_request_key_required', 'A valid request key is required.', ['status' => 400]);
        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
        if (!$chat) return new WP_Error('sbc_chat_missing', 'Chat not found.', ['status' => 404]);
        if ($chat->status === 'closed') return new WP_Error('sbc_chat_closed', 'This chat is closed.', ['status' => 409]);
        if (function_exists('sbc_ops')) {
            $claim=sbc_ops()->claim_chat($chat_id,(int)$this->current_mobile_device->admin_user_id,user_can((int)$this->current_mobile_device->admin_user_id,'sbc_manage_support'));
            if(is_wp_error($claim))return new WP_Error($claim->get_error_code(),$claim->get_error_message(),['status'=>409]);
        }
        $existing = $wpdb->get_var($wpdb->prepare("SELECT id FROM {$t['msgs']} WHERE request_key = %s LIMIT 1", $request_key));
        if ($existing) return rest_ensure_response($this->mobile_chat_data($chat));
        $now = current_time('mysql');
        $identity=sbc_ops()->staff_identity((int)$this->current_mobile_device->admin_user_id);
        $ok = $wpdb->insert($t['msgs'], ['chat_id' => $chat_id, 'sender_type' => 'agent', 'sender_label'=>$identity['display_name'], 'sender_department'=>$identity['department'], 'message' => $message, 'request_key' => $request_key, 'created_at' => $now]);
        if (!$ok) return new WP_Error('sbc_reply_failed', 'The reply could not be saved.', ['status' => 500]);
        $wpdb->update($t['chats'], [
            'status' => 'active',
            'agent_replied_at' => $chat->agent_replied_at ?: $now,
            'admin_unread_count' => 0,
            'agent_typing_at' => null,
            'updated_at' => $now,
            'last_human_activity_at' => $now,
            'inactivity_warned_at' => null,
        ], ['id' => $chat_id]);
        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
        $this->mobile_audit('reply', $chat_id);
        return rest_ensure_response($this->mobile_chat_data($chat));
    }

    public function mobile_close($request) {
        $chat_id = absint($request['id']);
        global $wpdb; $t=$this->tables(); $chat=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id=%d",$chat_id));
        if(!$chat||!sbc_ops()->can_access_chat($chat,(int)$this->current_mobile_device->admin_user_id))return new WP_Error('sbc_chat_forbidden','You cannot close this chat.',['status'=>403]);
        if (!$this->close_chat_record($chat_id, 'Chat closed from Android app.')) {
            return new WP_Error('sbc_close_failed', 'The chat could not be closed.', ['status' => 404]);
        }
        $this->mobile_audit('close', $chat_id);
        return rest_ensure_response(['success' => true, 'status' => 'closed']);
    }

    public function mobile_team() {
        $staff=sbc_ops()->staff_identity((int)$this->current_mobile_device->admin_user_id);
        return rest_ensure_response([
            'staff' => $staff,
            'agents' => !empty($staff['can_manage']) ? sbc_ops()->agent_performance() : sbc_ops()->agent_choices(),
            'token_balance' => sbc_ops()->token_balance(),
            'canned_replies' => sbc_ops()->visible_canned_for_mobile((int)$this->current_mobile_device->admin_user_id),
            'palette'=>$this->mobile_palette(),
        ]);
    }

    private function mobile_admin_user_id(){return $this->current_mobile_device?(int)$this->current_mobile_device->admin_user_id:0;}
    private function mobile_admin_allowed(){return $this->mobile_admin_user_id()&&user_can($this->mobile_admin_user_id(),'sbc_manage_support');}
    public function mobile_admin_directory(){if(!$this->mobile_admin_allowed())return new WP_Error('sbc_admin_only','Administrator access is required.',['status'=>403]);return rest_ensure_response(sbc_ops()->admin_directory());}
    public function mobile_admin_save_agent($request){if(!$this->mobile_admin_allowed())return new WP_Error('sbc_admin_only','Administrator access is required.',['status'=>403]);$result=sbc_ops()->api_save_agent($request->get_param('user_id'),$request->get_param('tag_name'),$request->get_param('department_id'));return is_wp_error($result)?$result:rest_ensure_response(['success'=>true]);}
    public function mobile_admin_delete_agent($request){if(!$this->mobile_admin_allowed())return new WP_Error('sbc_admin_only','Administrator access is required.',['status'=>403]);$result=sbc_ops()->api_delete_agent($request['id']);return is_wp_error($result)?$result:rest_ensure_response(['success'=>true]);}
    public function mobile_admin_save_canned($request){if(!$this->mobile_admin_allowed())return new WP_Error('sbc_admin_only','Administrator access is required.',['status'=>403]);$result=sbc_ops()->api_save_canned($request->get_param('id'),$request->get_param('title'),$request->get_param('message'),$request->get_param('scope'));return is_wp_error($result)?$result:rest_ensure_response(['success'=>true]);}
    public function mobile_admin_delete_canned($request){if(!$this->mobile_admin_allowed())return new WP_Error('sbc_admin_only','Administrator access is required.',['status'=>403]);$result=sbc_ops()->api_delete_canned($request['id']);return is_wp_error($result)?$result:rest_ensure_response(['success'=>true]);}

    public function mobile_transfer($request) {
        $result=sbc_ops()->transfer_chat(absint($request['id']),absint($request->get_param('target_user_id')),absint($request->get_param('department_id')),(int)$this->current_mobile_device->admin_user_id);
        if(is_wp_error($result))return new WP_Error($result->get_error_code(),$result->get_error_message(),['status'=>409]);
        return rest_ensure_response(['success'=>true]);
    }

    public function mobile_save_push($request) {
        $push_id = sanitize_text_field((string) $request->get_param('push_subscription_id'));
        if (!$push_id) return new WP_Error('sbc_push_required', 'A push subscription identifier is required.', ['status' => 400]);
        global $wpdb;
        $t = $this->tables();
        $wpdb->update($t['devices'], ['push_subscription_id' => $push_id], ['id' => $this->current_mobile_device->id]);
        $this->mobile_audit('register_push');
        return rest_ensure_response(['success' => true]);
    }

    public function mobile_revoke() {
        global $wpdb;
        $t = $this->tables();
        $wpdb->update($t['devices'], ['revoked_at' => current_time('mysql')], ['id' => $this->current_mobile_device->id]);
        $this->mobile_audit('self_revoke');
        return rest_ensure_response(['success' => true]);
    }

    public function admin_typing() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Unauthorized']);
        }

        check_ajax_referer('sbc_nonce', 'nonce');

        global $wpdb;
        $t = $this->tables();

        $chat_id = absint($_POST['chat_id'] ?? 0);
        if (!$chat_id) {
            wp_send_json_error(['message' => 'Chat ID required.']);
        }

        $chat = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id = %d", $chat_id));
        if (!$chat) {
            wp_send_json_error(['message' => 'Chat not found.']);
        }

        $wpdb->update($t['chats'], [
            'agent_typing_at' => current_time('mysql'),
        ], [
            'id' => $chat_id,
        ]);

        wp_send_json_success(['ok' => true]);
    }

}

new SBC_Plugin();
