<?php
/**
 * BluPass operations layer. Internal SBC identifiers remain stable for upgrades.
 * Token metering, staff roles, chat ownership/transfers and desktop portal.
 */
if (!defined('ABSPATH')) exit;

final class SBC_Operations {
    const DB_VERSION = '4.2.0';
    const AGENT_CAP = 'sbc_support_access';
    const MANAGE_CAP = 'sbc_manage_support';
    private static $instance;

    public static function instance() {
        return self::$instance ?: (self::$instance = new self());
    }

    private function __construct() {
        add_action('plugins_loaded', [$this, 'maybe_install'], 20);
        add_action('admin_menu', [$this, 'admin_menu'], 30);
        add_action('admin_head', [$this, 'workspace_admin_head']);
        add_action('admin_footer', [$this, 'workspace_admin_footer']);
        add_filter('admin_body_class', [$this, 'workspace_admin_body_class']);
        add_action('admin_init', [$this, 'register_settings']);
        add_action('admin_post_sbc_ops_save_agent', [$this, 'save_agent']);
        add_action('admin_post_sbc_ops_delete_agent', [$this, 'delete_agent']);
        add_action('admin_post_sbc_ops_save_department', [$this, 'save_department']);
        add_action('admin_post_sbc_ops_save_canned', [$this, 'save_canned']);
        add_action('admin_post_sbc_ops_delete_canned', [$this, 'delete_canned']);
        add_action('admin_post_sbc_ops_delete_ledger', [$this, 'delete_ledger']);
        add_action('admin_post_sbc_ops_manual_tokens', [$this, 'manual_tokens']);
        add_action('admin_post_sbc_ops_checkout', [$this, 'checkout']);
        add_action('admin_post_sbc_ops_dismiss_token_warning', [$this, 'dismiss_token_warning']);
        add_action('wp_ajax_sbc_ops_portal', [$this, 'portal_ajax']);
        add_shortcode('support_bonus_agent_portal', [$this, 'portal_shortcode']);
        add_action('rest_api_init', [$this, 'rest_routes']);
        add_action('sbc_check_timeouts', [$this, 'check_inactivity'], 20);
    }

    private function tables() {
        global $wpdb;
        return [
            'chats' => $wpdb->prefix . 'sbc_chats',
            'msgs' => $wpdb->prefix . 'sbc_messages',
            'buckets' => $wpdb->prefix . 'sbc_token_buckets',
            'ledger' => $wpdb->prefix . 'sbc_token_ledger',
            'departments' => $wpdb->prefix . 'sbc_departments',
            'agents' => $wpdb->prefix . 'sbc_agent_profiles',
            'canned' => $wpdb->prefix . 'sbc_canned_replies',
            'audit' => $wpdb->prefix . 'sbc_operations_audit',
        ];
    }

    public function maybe_install() {
        if (get_option('sbc_ops_db_version') === self::DB_VERSION) return;
        $this->install();
    }

    private function install() {
        global $wpdb;
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        $t = $this->tables();
        $cc = $wpdb->get_charset_collate();
        dbDelta("CREATE TABLE {$t['buckets']} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            purchase_reference varchar(100) NOT NULL,
            package_code varchar(30) NOT NULL,
            purchased_tokens decimal(12,2) NOT NULL DEFAULT 0,
            remaining_tokens decimal(12,2) NOT NULL DEFAULT 0,
            tokens_per_chat decimal(6,2) NOT NULL DEFAULT 0,
            amount_paid decimal(12,2) NOT NULL DEFAULT 0,
            status varchar(20) NOT NULL DEFAULT 'active',
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY purchase_reference (purchase_reference),
            KEY status_created (status,created_at)
        ) $cc;");
        dbDelta("CREATE TABLE {$t['ledger']} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            bucket_id bigint(20) unsigned NOT NULL DEFAULT 0,
            chat_id bigint(20) unsigned NOT NULL DEFAULT 0,
            reference varchar(100) NOT NULL,
            entry_type varchar(30) NOT NULL,
            tokens decimal(12,2) NOT NULL DEFAULT 0,
            balance_after decimal(12,2) NOT NULL DEFAULT 0,
            note varchar(255) NOT NULL DEFAULT '',
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY reference (reference),
            KEY chat_id (chat_id)
        ) $cc;");
        dbDelta("CREATE TABLE {$t['departments']} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            name varchar(100) NOT NULL,
            slug varchar(100) NOT NULL,
            active tinyint(1) NOT NULL DEFAULT 1,
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY slug (slug)
        ) $cc;");
        dbDelta("CREATE TABLE {$t['agents']} (
            user_id bigint(20) unsigned NOT NULL,
            tag_name varchar(100) NOT NULL DEFAULT '',
            department_id bigint(20) unsigned NOT NULL DEFAULT 0,
            active tinyint(1) NOT NULL DEFAULT 1,
            updated_at datetime NOT NULL,
            PRIMARY KEY  (user_id),
            KEY department_id (department_id)
        ) $cc;");
        dbDelta("CREATE TABLE {$t['canned']} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            owner_user_id bigint(20) unsigned NOT NULL DEFAULT 0,
            department_id bigint(20) unsigned NOT NULL DEFAULT 0,
            scope varchar(20) NOT NULL DEFAULT 'global',
            title varchar(100) NOT NULL,
            message text NOT NULL,
            active tinyint(1) NOT NULL DEFAULT 1,
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            KEY visibility (scope,department_id,owner_user_id)
        ) $cc;");
        dbDelta("CREATE TABLE {$t['audit']} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            actor_user_id bigint(20) unsigned NOT NULL DEFAULT 0,
            chat_id bigint(20) unsigned NOT NULL DEFAULT 0,
            action_name varchar(50) NOT NULL,
            details text NULL,
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            KEY chat_id (chat_id),
            KEY actor_created (actor_user_id,created_at)
        ) $cc;");

        $columns = [
            '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",
        ];
        foreach ($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}");
            }
        }
        add_role('sbc_support_agent', 'BluPass Agent', ['read' => true, self::AGENT_CAP => true]);
        foreach (['administrator'] as $role_name) {
            $role = get_role($role_name);
            if ($role) { $role->add_cap(self::AGENT_CAP); $role->add_cap(self::MANAGE_CAP); }
        }
        add_option('sbc_token_server_url', '', '', false);
        add_option('sbc_token_site_secret', wp_generate_password(64, true, true), '', false);
        update_option('sbc_ops_db_version', self::DB_VERSION, false);
    }

    public function repair_schema(){ $this->install(); }

    public function packages() {
        return [
            'starter' => ['tokens' => 100.0, 'price' => 3000.0, 'rate' => 2.0, 'label' => 'Starter'],
            'growth' => ['tokens' => 300.0, 'price' => 7500.0, 'rate' => 1.5, 'label' => 'Growth'],
            'business' => ['tokens' => 500.0, 'price' => 12500.0, 'rate' => 1.0, 'label' => 'Business'],
        ];
    }

    private function bucket_labels() {
        return ['starter'=>'Starter','growth'=>'Growth','business'=>'Business','admin_custom'=>'Admin Custom Tokens'];
    }

    public function token_balance() {
        global $wpdb; $t = $this->tables();
        return (float) $wpdb->get_var("SELECT COALESCE(SUM(remaining_tokens),0) FROM {$t['buckets']} WHERE status='active'");
    }

    public function can_start_chat() { return $this->token_balance() >= $this->next_chat_rate(); }

    public function next_chat_rate() {
        global $wpdb; $t = $this->tables();
        $rate = $wpdb->get_var("SELECT tokens_per_chat FROM {$t['buckets']} WHERE status='active' AND remaining_tokens >= tokens_per_chat ORDER BY created_at ASC,id ASC LIMIT 1");
        return $rate === null ? INF : (float) $rate;
    }

    /** FIFO bucket debit. The unique reference makes a retried chat creation idempotent. */
    public function debit_chat($chat_id, $reference) {
        global $wpdb; $t = $this->tables();
        $existing = $wpdb->get_var($wpdb->prepare("SELECT tokens FROM {$t['ledger']} WHERE reference=%s", $reference));
        if ($existing !== null) return abs((float) $existing);
        $wpdb->query('START TRANSACTION');
        $bucket = $wpdb->get_row("SELECT * FROM {$t['buckets']} WHERE status='active' AND remaining_tokens >= tokens_per_chat ORDER BY created_at ASC,id ASC LIMIT 1 FOR UPDATE");
        if (!$bucket) { $wpdb->query('ROLLBACK'); return new WP_Error('sbc_no_tokens', 'Support is temporarily unavailable because the website token wallet is empty.'); }
        $rate = (float) $bucket->tokens_per_chat;
        $remaining = round((float) $bucket->remaining_tokens - $rate, 2);
        $ok1 = $wpdb->update($t['buckets'], ['remaining_tokens'=>$remaining, 'status'=>$remaining < $rate ? 'depleted' : 'active'], ['id'=>(int)$bucket->id]);
        $balance = round($this->token_balance(), 2);
        $ok2 = $wpdb->insert($t['ledger'], ['bucket_id'=>(int)$bucket->id,'chat_id'=>(int)$chat_id,'reference'=>$reference,'entry_type'=>'chat_debit','tokens'=>-$rate,'balance_after'=>$balance,'note'=>'New support conversation','created_at'=>current_time('mysql')]);
        if ($ok1 === false || !$ok2) { $wpdb->query('ROLLBACK'); return new WP_Error('sbc_token_debit_failed', 'The token charge could not be recorded.'); }
        $wpdb->query('COMMIT');
        return $rate;
    }

    public function credit_package($package_code, $reference, $amount_paid = 0) {
        $packages = $this->packages();
        if (empty($packages[$package_code])) return new WP_Error('invalid_package', 'Unknown token package.');
        $p = $packages[$package_code];
        return $this->credit_tokens($package_code,$reference,$p['tokens'],$p['rate'],$amount_paid,$p['label'].' token package');
    }

    public function credit_tokens($package_code,$reference,$tokens,$rate,$amount_paid=0,$note='Token credit') {
        $package_code=sanitize_key($package_code);$reference=sanitize_text_field($reference);$tokens=round((float)$tokens,2);$rate=round((float)$rate,2);
        if(!$reference||$tokens<=0||$tokens>1000000||$rate<=0)return new WP_Error('invalid_credit','Token quantity and chat rate must be valid.');
        global $wpdb; $t = $this->tables();
        $ok = $wpdb->insert($t['buckets'], ['purchase_reference'=>$reference,'package_code'=>$package_code,'purchased_tokens'=>$tokens,'remaining_tokens'=>$tokens,'tokens_per_chat'=>$rate,'amount_paid'=>(float)$amount_paid,'status'=>'active','created_at'=>current_time('mysql')]);
        if (!$ok) {
            $existing = $wpdb->get_var($wpdb->prepare("SELECT id FROM {$t['buckets']} WHERE purchase_reference=%s", $reference));
            return $existing ? true : new WP_Error('credit_failed', 'Token credit could not be recorded.');
        }
        $wpdb->insert($t['ledger'], ['bucket_id'=>(int)$wpdb->insert_id,'reference'=>'credit-'.$reference,'entry_type'=>'purchase','tokens'=>$tokens,'balance_after'=>$this->token_balance(),'note'=>sanitize_text_field($note),'created_at'=>current_time('mysql')]);
        return true;
    }

    public function remove_tokens($tokens,$reference){
        $tokens=round((float)$tokens,2);$reference='remote-debit-'.sanitize_text_field($reference);if($tokens<=0||$tokens>1000000)return new WP_Error('invalid_debit','Enter a valid token amount to remove.');global $wpdb;$t=$this->tables();
        $existing=$wpdb->get_var($wpdb->prepare("SELECT id FROM {$t['ledger']} WHERE reference=%s",$reference));if($existing)return true;
        $wpdb->query('START TRANSACTION');$rows=$wpdb->get_results("SELECT * FROM {$t['buckets']} WHERE status='active' AND remaining_tokens>0 ORDER BY created_at ASC,id ASC FOR UPDATE");$available=0.0;foreach($rows as $row)$available+=(float)$row->remaining_tokens;
        if($available+0.001<$tokens){$wpdb->query('ROLLBACK');return new WP_Error('insufficient_tokens','This website does not have enough tokens. Available: '.number_format($available,2));}
        $left=$tokens;foreach($rows as $row){if($left<=0)break;$take=min((float)$row->remaining_tokens,$left);$remaining=round((float)$row->remaining_tokens-$take,2);$status=$remaining>0&&$remaining>=(float)$row->tokens_per_chat?'active':'depleted';if($wpdb->update($t['buckets'],['remaining_tokens'=>$remaining,'status'=>$status],['id'=>(int)$row->id])===false){$wpdb->query('ROLLBACK');return new WP_Error('debit_failed','The token buckets could not be updated.');}$left=round($left-$take,2);}
        $balance=(float)$wpdb->get_var("SELECT COALESCE(SUM(remaining_tokens),0) FROM {$t['buckets']} WHERE status='active'");$ok=$wpdb->insert($t['ledger'],['bucket_id'=>0,'reference'=>$reference,'entry_type'=>'admin_debit','tokens'=>-$tokens,'balance_after'=>$balance,'note'=>'Tokens removed by private server administrator','created_at'=>current_time('mysql')]);if(!$ok){$wpdb->query('ROLLBACK');return new WP_Error('debit_history_failed','The token removal could not be recorded.');}$wpdb->query('COMMIT');return true;
    }

    public function admin_menu() {
        $cap = self::MANAGE_CAP;
        add_submenu_page('sbc-settings', 'Token Wallet & Billing', 'Token Wallet & Billing', $cap, 'sbc-token-wallet', [$this,'token_page']);
        add_submenu_page('sbc-settings', 'Agents & Departments', 'Agents & Departments', $cap, 'sbc-agents', [$this,'agents_page']);
        add_submenu_page('sbc-settings', 'Canned Replies', 'Canned Replies', self::AGENT_CAP, 'sbc-canned-replies', [$this,'canned_page']);
        add_submenu_page('sbc-settings', 'Desktop Agent Portal', 'Desktop Agent Portal', self::AGENT_CAP, 'sbc-agent-portal-help', [$this,'portal_help_page']);
        add_submenu_page('sbc-settings', 'Activity Log', 'Activity Log', self::MANAGE_CAP, 'sbc-activity', [$this,'activity_page']);
    }

    private function is_workspace_page(){return is_admin()&&strpos(sanitize_key($_GET['page']??''),'sbc-')===0;}
    public function workspace_admin_body_class($classes){if(!$this->is_workspace_page())return $classes;return trim($classes.' sbc-workspace');}
    public function workspace_admin_head(){
        if(!$this->is_workspace_page())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';
        ?><style>
        body.sbc-workspace{--sbc-blue:<?php echo esc_attr($primary);?>;--sbc-secondary:<?php echo esc_attr($secondary);?>;--sbc-accent:<?php echo esc_attr($accent);?>;--sbc-ink:#111c35;--sbc-muted:#697386;--sbc-line:#e5eaf2;--sbc-bg:#f5f7fb;background:var(--sbc-bg)}
        html.wp-toolbar:has(body.sbc-workspace){padding-top:0!important}body.sbc-workspace #adminmenumain,body.sbc-workspace #wpadminbar,body.sbc-workspace #wpfooter{display:none!important}body.sbc-workspace #wpcontent,body.sbc-workspace #wpfooter{margin-left:250px}body.sbc-workspace #wpcontent{padding:0;min-height:100vh;transition:margin .2s;box-sizing:border-box}body.sbc-workspace.sbc-nav-collapsed #wpcontent{margin-left:78px}
        .sbc-ws-top{height:64px;background:#fff;border-bottom:1px solid var(--sbc-line);position:fixed;left:250px;right:0;top:0;z-index:9990;display:flex;align-items:center;padding:0 22px;transition:left .2s}.sbc-nav-collapsed .sbc-ws-top{left:78px}.sbc-hamburger{border:0;background:color-mix(in srgb,var(--sbc-blue) 10%,white);color:var(--sbc-blue);width:38px;height:38px;border-radius:10px;font-size:20px;cursor:pointer}.sbc-live{margin-left:auto;color:#0a8f4d;background:#eafaf1;padding:8px 12px;border-radius:20px;font-weight:700}
        .sbc-ws-side{position:fixed;left:0;top:0;bottom:0;width:250px;background:#fff;border-right:1px solid var(--sbc-line);z-index:9991;box-sizing:border-box;padding:18px 12px;display:flex;flex-direction:column;transition:width .2s}.sbc-nav-collapsed .sbc-ws-side{width:78px}.sbc-brand{display:flex;align-items:center;gap:11px;padding:0 7px 20px}.sbc-brand-icon{width:38px;height:38px;border-radius:10px;background:#fff;display:grid;place-items:center;overflow:hidden;flex:0 0 auto}.sbc-brand-icon img{width:36px;height:36px;object-fit:contain;display:block}.sbc-brand strong{font-size:18px;color:var(--sbc-ink)}.sbc-brand small{display:block;color:var(--sbc-muted)}.sbc-ws-nav{display:grid;gap:5px}.sbc-ws-nav a{display:flex;align-items:center;gap:12px;padding:11px 12px;border-radius:9px;text-decoration:none;color:#344054;font-weight:600}.sbc-ws-nav a:hover,.sbc-ws-nav a.active{background:color-mix(in srgb,var(--sbc-blue) 10%,white);color:var(--sbc-blue)}.sbc-ws-nav .dashicons{font-size:19px}.sbc-nav-divider{height:1px;background:var(--sbc-line);margin:10px}.sbc-ws-user{margin-top:auto;border:1px solid var(--sbc-line);border-radius:12px;padding:11px;display:flex;align-items:center;gap:10px}.sbc-avatar{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--sbc-blue),var(--sbc-secondary));color:#fff;display:grid;place-items:center;font-weight:800}.sbc-developed{text-align:center;padding:10px 4px 0;color:var(--sbc-muted);font-size:11px}.sbc-developed strong{color:var(--sbc-blue)}.sbc-nav-collapsed .sbc-nav-label,.sbc-nav-collapsed .sbc-brand-copy,.sbc-nav-collapsed .sbc-user-copy,.sbc-nav-collapsed .sbc-developed{display:none}.sbc-nav-collapsed .sbc-ws-nav a{justify-content:center}
        body.sbc-workspace #wpbody{padding-top:64px}body.sbc-workspace #wpbody-content{padding-bottom:30px}body.sbc-workspace .wrap{margin:0!important;padding:28px 32px!important;max-width:none!important;box-sizing:border-box;width:100%}body.sbc-workspace .wrap>h1{font-size:27px;font-weight:800;color:var(--sbc-ink);margin-bottom:8px}body.sbc-workspace .card{border:1px solid var(--sbc-line);border-radius:14px;background:#fff;box-shadow:0 5px 18px #173b6810;padding:20px;max-width:none}body.sbc-workspace .widefat{border:1px solid var(--sbc-line);border-radius:12px;overflow:hidden;box-shadow:0 5px 18px #173b680b}body.sbc-workspace .widefat th{font-weight:700;color:#475467;padding:13px}body.sbc-workspace .widefat td{padding:13px}body.sbc-workspace input:not([type=checkbox]):not([type=radio]),body.sbc-workspace select,body.sbc-workspace textarea{border:1px solid #d8e0ec;border-radius:8px;padding:9px 11px}body.sbc-workspace .button-primary{background:var(--sbc-blue)!important;border-color:var(--sbc-blue)!important;border-radius:8px;padding:4px 16px}body.sbc-workspace .notice{border-radius:9px}body.sbc-workspace .sbc-title-live{display:none!important}.sbc-token-alert{position:fixed;top:76px;left:270px;right:20px;padding:13px 15px;border:1px solid #fecaca;border-left:4px solid #dc2626;border-radius:10px;background:#fff1f2;color:#7f1d1d;display:flex;align-items:center;gap:12px;z-index:9992;box-shadow:0 10px 30px rgba(127,29,29,.12)}.sbc-token-alert p{margin:0;flex:1}.sbc-token-alert form{margin:0}.sbc-token-alert a{font-weight:700;color:#b91c1c}.sbc-nav-collapsed .sbc-token-alert{left:98px}
        @media(max-width:782px){body.sbc-workspace #wpcontent{margin-left:0!important}.sbc-ws-side{width:250px;transform:translateX(-100%)}.sbc-ws-top{left:0!important}.sbc-mobile-nav-open .sbc-ws-side{transform:translateX(0)}body.sbc-workspace .wrap{padding:20px 12px}.sbc-nav-collapsed .sbc-ws-side{width:250px}.sbc-nav-collapsed .sbc-nav-label,.sbc-nav-collapsed .sbc-brand-copy,.sbc-nav-collapsed .sbc-user-copy{display:block}.sbc-token-alert,.sbc-nav-collapsed .sbc-token-alert{left:12px;right:12px;top:74px;align-items:flex-start;flex-wrap:wrap}.sbc-token-alert form{width:100%}}
        </style><script>document.addEventListener('DOMContentLoaded',function(){if(localStorage.getItem('sbcNavCollapsed')==='1')document.body.classList.add('sbc-nav-collapsed')});</script><?php
    }
    public function workspace_admin_footer(){
        if(!$this->is_workspace_page())return;$page=sanitize_key($_GET['page']??'');$user=wp_get_current_user();if(current_user_can(self::MANAGE_CAP)&&!$this->can_start_chat()&&!get_user_meta(get_current_user_id(),'sbc_hide_empty_token_warning',true)){?><div class="sbc-token-alert"><span class="dashicons dashicons-warning"></span><p><strong>Your support tokens are finished.</strong> Recharge to make the customer chatbot available and accept new conversations. <a href="<?php echo esc_url(admin_url('admin.php?page=sbc-token-wallet'));?>">Recharge tokens</a></p><form method="post" action="<?php echo esc_url(admin_url('admin-post.php'));?>"><input type="hidden" name="action" value="sbc_ops_dismiss_token_warning"><?php wp_nonce_field('sbc_ops_dismiss_token_warning');?><button class="button" type="submit">Don’t show again</button></form></div><?php }$items=[['sbc-inbox','dashicons-format-chat','Inbox'],['sbc-customers','dashicons-groups',current_user_can(self::MANAGE_CAP)?'Customers & Balances':'Customers']];if(current_user_can(self::MANAGE_CAP)){$items=array_merge($items,[['sbc-mobile-app','dashicons-smartphone','Mobile App'],['sbc-token-wallet','dashicons-money-alt','Token Wallet & Billing'],['sbc-agents','dashicons-businessperson','Agents & Departments'],['sbc-canned-replies','dashicons-testimonial','Canned Replies'],['sbc-settings','dashicons-admin-generic','Settings'],['sbc-activity','dashicons-media-text','Activity Log']]);}
        ?><aside class="sbc-ws-side"><div class="sbc-brand"><span class="sbc-brand-icon"><img src="<?php echo esc_url(SBC_PLUGIN_URL . 'assets/images/blupass-icon.png');?>" alt=""></span><span class="sbc-brand-copy"><strong>BluPass</strong><small>Customer engagement workspace</small></span></div><nav class="sbc-ws-nav"><?php foreach($items as $item):?><a class="<?php echo $page===$item[0]?'active':'';?>" href="<?php echo esc_url(admin_url('admin.php?page='.$item[0]));?>"><span class="dashicons <?php echo esc_attr($item[1]);?>"></span><span class="sbc-nav-label"><?php echo esc_html($item[2]);?></span></a><?php endforeach;?><div class="sbc-nav-divider"></div><a href="<?php echo esc_url(wp_logout_url());?>"><span class="dashicons dashicons-exit"></span><span class="sbc-nav-label">Logout</span></a></nav><div class="sbc-ws-user"><span class="sbc-avatar"><?php echo esc_html(strtoupper(substr($user->display_name,0,1)));?></span><span class="sbc-user-copy"><strong><?php echo esc_html($user->display_name);?></strong><small><?php echo esc_html($user->user_email);?></small></span></div><div class="sbc-developed">Developed by <strong>Bluishgen</strong></div></aside><div class="sbc-ws-top"><button class="sbc-hamburger" id="sbc-ws-toggle" aria-label="Toggle menu">☰</button><span class="sbc-live">● Live refresh ON</span></div><script>(function(){var b=document.getElementById('sbc-ws-toggle');if(!b)return;b.onclick=function(){if(innerWidth<=782){document.body.classList.toggle('sbc-mobile-nav-open');return}document.body.classList.toggle('sbc-nav-collapsed');localStorage.setItem('sbcNavCollapsed',document.body.classList.contains('sbc-nav-collapsed')?'1':'0')}})();</script><?php
    }
    public function dismiss_token_warning(){if(!current_user_can(self::MANAGE_CAP))wp_die('Unauthorized');check_admin_referer('sbc_ops_dismiss_token_warning');update_user_meta(get_current_user_id(),'sbc_hide_empty_token_warning',1);wp_safe_redirect(wp_get_referer()?:admin_url('admin.php?page=sbc-token-wallet'));exit;}

    public function activity_page(){
        $this->admin_guard();global $wpdb;$t=$this->tables();$rows=$wpdb->get_results("SELECT a.*,u.display_name FROM {$t['audit']} a LEFT JOIN {$wpdb->users} u ON u.ID=a.actor_user_id ORDER BY a.id DESC LIMIT 200");?><div class="wrap"><h1>Activity Log</h1><p>Recent support administration and conversation actions.</p><table class="widefat striped"><thead><tr><th>Date</th><th>User</th><th>Action</th><th>Chat</th><th>Details</th></tr></thead><tbody><?php if(!$rows):?><tr><td colspan="5">No activity recorded yet.</td></tr><?php endif;foreach($rows as $r):?><tr><td><?php echo esc_html($r->created_at);?></td><td><?php echo esc_html($r->display_name?:'System');?></td><td><?php echo esc_html(str_replace('_',' ',$r->action_name));?></td><td><?php echo $r->chat_id?'#'.(int)$r->chat_id:'—';?></td><td><code><?php echo esc_html($r->details);?></code></td></tr><?php endforeach;?></tbody></table></div><?php
    }

    public function register_settings() {
        register_setting('sbc_token_settings','sbc_token_server_url',['sanitize_callback'=>'esc_url_raw']);
        register_setting('sbc_token_settings','sbc_token_site_secret',['sanitize_callback'=>function($value){$value=(string)$value;return strlen($value)>=32?$value:get_option('sbc_token_site_secret');}]);
    }

    private function admin_guard($cap = self::MANAGE_CAP) { if (!current_user_can($cap)) wp_die('Unauthorized'); }
    private function redirect($page, $notice='saved') { wp_safe_redirect(add_query_arg(['page'=>$page,'sbc_notice'=>$notice], admin_url('admin.php'))); exit; }

    public function token_page() {
        $this->admin_guard(); global $wpdb; $t=$this->tables();
        $server = trim((string)get_option('sbc_token_server_url',''));
        $payment_notice='';$returned_reference=sanitize_text_field(wp_unslash($_GET['sbc_token_reference']??''));if($returned_reference&&$server)$payment_notice=$this->verify_returned_payment($returned_reference,$server);
        $page=max(1,absint($_GET['history_page']??1));$per_page=20;$offset=($page-1)*$per_page;
        $ledger=$wpdb->get_results($wpdb->prepare("SELECT * FROM {$t['ledger']} ORDER BY id DESC LIMIT %d OFFSET %d",$per_page,$offset));
        $ledger_total=(int)$wpdb->get_var("SELECT COUNT(*) FROM {$t['ledger']}");$total_pages=max(1,(int)ceil($ledger_total/$per_page));
        $buckets=$wpdb->get_results("SELECT * FROM {$t['buckets']} ORDER BY created_at ASC,id ASC");$labels=$this->bucket_labels();
        $bucket_totals=[];foreach($labels as $code=>$label)$bucket_totals[$code]=0.0;foreach($buckets as $bucket){if(isset($bucket_totals[$bucket->package_code]))$bucket_totals[$bucket->package_code]+=(float)$bucket->remaining_tokens;}
        ?> <div class="wrap sbc-ops"><h1>Token Wallet &amp; Billing</h1><?php if($payment_notice):?><div class="notice <?php echo strpos($payment_notice,'credited')!==false?'notice-success':'notice-warning'; ?>"><p><?php echo esc_html($payment_notice); ?></p></div><?php endif;?><div class="notice notice-info"><p><strong>Available tokens: <?php echo esc_html(number_format($this->token_balance(),2)); ?></strong>. New conversations use the oldest purchased package first. Existing chats and replies are never charged again.</p></div>
        <details><summary><strong>Private sales server connection</strong></summary><form method="post" action="options.php"><?php settings_fields('sbc_token_settings'); ?><table class="form-table"><tr><th>Token server URL</th><td><input class="regular-text" type="url" name="sbc_token_server_url" value="<?php echo esc_attr(get_option('sbc_token_server_url','')); ?>" placeholder="https://sales.yourdomain.com"></td></tr><tr><th>Site secret</th><td><input class="large-text code" type="text" readonly name="sbc_token_site_secret" value="<?php echo esc_attr(get_option('sbc_token_site_secret','')); ?>"><p class="description">Copy this to the customer-site record on your private sales server. Do not expose it publicly.</p></td></tr></table><?php submit_button('Save connection'); ?></form></details>
        <div style="display:flex;gap:16px;flex-wrap:wrap"><?php foreach($this->packages() as $code=>$p): ?><div class="card" style="min-width:220px"><h2><?php echo esc_html($p['label']); ?></h2><p><strong><?php echo esc_html($p['tokens']); ?> tokens — ₦<?php echo esc_html(number_format($p['price'])); ?></strong></p><p><?php echo esc_html($p['rate']); ?> token(s) per new chat</p><?php if($server): ?><form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"><input type="hidden" name="action" value="sbc_ops_checkout"><input type="hidden" name="package" value="<?php echo esc_attr($code); ?>"><?php wp_nonce_field('sbc_ops_checkout'); ?><button class="button button-primary">Buy with Paystack</button></form><?php else: ?><p><em>Connect the private token server to enable checkout.</em></p><?php endif; ?></div><?php endforeach; ?></div>
        <h2>Live token buckets</h2><div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px"><?php foreach($labels as $code=>$label): ?><div class="card"><h3><?php echo esc_html($label); ?></h3><p style="font-size:26px;margin:8px 0"><strong><?php echo esc_html(number_format($bucket_totals[$code],2)); ?></strong></p><p><?php echo $code==='admin_custom'?'2 tokens per chat':'Remaining across this package'; ?></p></div><?php endforeach; ?></div>
        <h3>Bucket usage order</h3><table class="widefat striped"><thead><tr><th>Created</th><th>Bucket</th><th>Purchased</th><th>Remaining</th><th>Rate/chat</th><th>Status</th></tr></thead><tbody><?php if(!$buckets):?><tr><td colspan="6">No token buckets yet.</td></tr><?php endif;foreach($buckets as $b):?><tr><td><?php echo esc_html($b->created_at);?></td><td><?php echo esc_html($labels[$b->package_code]??ucwords(str_replace('_',' ',$b->package_code)));?></td><td><?php echo esc_html($b->purchased_tokens);?></td><td><strong><?php echo esc_html($b->remaining_tokens);?></strong></td><td><?php echo esc_html($b->tokens_per_chat);?></td><td><?php echo esc_html($b->status);?></td></tr><?php endforeach;?></tbody></table>
        <h2>Token history</h2><form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return confirm('Delete the selected history records? Token balances and buckets will not be changed.');"><input type="hidden" name="action" value="sbc_ops_delete_ledger"><?php wp_nonce_field('sbc_ops_delete_ledger');?><table class="widefat striped"><thead><tr><th style="width:36px"><input type="checkbox" onclick="document.querySelectorAll('.sbc-ledger-check').forEach(x=>x.checked=this.checked)"></th><th>Date</th><th>Type</th><th>Tokens</th><th>Balance</th><th>Reference</th><th>Note</th></tr></thead><tbody><?php if(!$ledger):?><tr><td colspan="7">No token history on this page.</td></tr><?php endif;foreach($ledger as $e): ?><tr><td><input class="sbc-ledger-check" type="checkbox" name="ledger_ids[]" value="<?php echo (int)$e->id;?>"></td><td><?php echo esc_html($e->created_at); ?></td><td><?php echo esc_html($e->entry_type); ?></td><td><?php echo esc_html($e->tokens); ?></td><td><?php echo esc_html($e->balance_after); ?></td><td><?php echo esc_html($e->reference); ?></td><td><?php echo esc_html($e->note); ?></td></tr><?php endforeach; ?></tbody></table><p><button class="button button-link-delete">Delete selected history</button></p></form><?php echo wp_kses_post(paginate_links(['base'=>add_query_arg('history_page','%#%',admin_url('admin.php?page=sbc-token-wallet')),'format'=>'','current'=>$page,'total'=>$total_pages,'prev_text'=>'‹ Previous','next_text'=>'Next ›','type'=>'list']));?></div><?php
    }

    public function delete_ledger(){
        $this->admin_guard();check_admin_referer('sbc_ops_delete_ledger');$ids=array_values(array_filter(array_map('absint',(array)($_POST['ledger_ids']??[]))));
        if($ids){global $wpdb;$t=$this->tables();$placeholders=implode(',',array_fill(0,count($ids),'%d'));$wpdb->query($wpdb->prepare("DELETE FROM {$t['ledger']} WHERE id IN ($placeholders)",$ids));}
        $this->redirect('sbc-token-wallet','history_deleted');
    }

    public function checkout() {
        $this->admin_guard(); check_admin_referer('sbc_ops_checkout');
        $package=sanitize_key($_POST['package']??''); if(!isset($this->packages()[$package])) wp_die('Invalid package.');
        $server=untrailingslashit(esc_url_raw(get_option('sbc_token_server_url',''))); if(!$server) wp_die('Token server is not configured.');
        $body=['package'=>$package,'site_url'=>trailingslashit(home_url('/')),'site_name'=>get_bloginfo('name'),'email'=>wp_get_current_user()->user_email,'callback_url'=>admin_url('admin.php?page=sbc-token-wallet')];
        $site_domain=strtolower((string)wp_parse_url(home_url('/'),PHP_URL_HOST));
        $body['signature']=hash_hmac('sha256',$body['package'].'|'.$site_domain,get_option('sbc_token_site_secret'));
        $response=wp_remote_post($server.'/wp-json/support-chat-sales/v2/token-checkout',['timeout'=>20,'headers'=>['Content-Type'=>'application/json'],'body'=>wp_json_encode($body)]);
        if(is_wp_error($response)) wp_die(esc_html($response->get_error_message()));
        $data=json_decode(wp_remote_retrieve_body($response),true); if(empty($data['authorization_url'])){$message=sanitize_text_field($data['message']??'Checkout could not be started.');$code=wp_remote_retrieve_response_code($response);wp_die(esc_html($message.' (Sales server response '.$code.')'));}
        wp_redirect(esc_url_raw($data['authorization_url'])); exit;
    }

    private function verify_returned_payment($reference,$server) {
        if(strpos($reference,'SBCT-')!==0)return 'The returned Paystack reference is invalid.';
        $site_url=trailingslashit(home_url('/'));$domain=strtolower((string)wp_parse_url($site_url,PHP_URL_HOST));
        $body=['reference'=>$reference,'site_url'=>$site_url,'signature'=>hash_hmac('sha256',$reference.'|'.$domain,(string)get_option('sbc_token_site_secret'))];
        $response=wp_remote_post(untrailingslashit($server).'/wp-json/support-chat-sales/v2/token-status',['timeout'=>25,'headers'=>['Content-Type'=>'application/json'],'body'=>wp_json_encode($body)]);
        if(is_wp_error($response))return 'Payment was received, but verification could not connect to the sales server. It will retry automatically.';
        $data=json_decode(wp_remote_retrieve_body($response),true)?:[];$status=sanitize_key($data['status']??'');
        if($status==='delivered')return 'Payment verified and '.number_format((float)($data['tokens']??0),2).' tokens were credited successfully.';
        if($status==='paid_pending_delivery')return 'Payment was verified. Token delivery is retrying automatically.';
        $message=sanitize_text_field($data['message']??'');return $message?:'Payment is still pending confirmation from Paystack. Refresh this page shortly.';
    }

    public function manual_tokens() {
        $this->admin_guard(); check_admin_referer('sbc_ops_manual_tokens');
        $code=sanitize_key($_POST['package']??''); $result=$this->credit_package($code,'manual-'.wp_generate_uuid4(),0);
        $this->redirect('sbc-token-wallet',is_wp_error($result)?'error':'credited');
    }

    public function agents_page() {
        $this->admin_guard(); global $wpdb; $t=$this->tables();
        $deps=$wpdb->get_results("SELECT * FROM {$t['departments']} ORDER BY name");
        $agents=$wpdb->get_results("SELECT p.*,u.user_login,u.display_name,u.user_email,d.name department_name FROM {$t['agents']} p JOIN {$wpdb->users} u ON u.ID=p.user_id LEFT JOIN {$t['departments']} d ON d.id=p.department_id ORDER BY u.display_name");
        foreach($agents as $agent){$stats=$wpdb->get_row($wpdb->prepare("SELECT COUNT(*) handled,COUNT(rating) reviews,COALESCE(AVG(rating),0) average_rating FROM {$t['chats']} WHERE assigned_agent_id=%d",$agent->user_id));$agent->handled=(int)$stats->handled;$agent->reviews=(int)$stats->reviews;$agent->average_rating=(float)$stats->average_rating;}
        ?> <div class="wrap"><h1>Agents &amp; Departments</h1><p>Manage support departments, agent access, and customer review performance.</p><div style="display:grid;grid-template-columns:minmax(300px,1fr) minmax(300px,1fr);gap:24px"><div class="card"><h2>Add department</h2><form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"><input type="hidden" name="action" value="sbc_ops_save_department"><?php wp_nonce_field('sbc_ops_save_department'); ?><input required name="name" placeholder="Sales Department"><button class="button button-primary">Add</button></form><ul><?php foreach($deps as $d): ?><li><?php echo esc_html($d->name); ?></li><?php endforeach; ?></ul></div><div class="card"><h2>Register support agent</h2><form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"><input type="hidden" name="action" value="sbc_ops_save_agent"><?php wp_nonce_field('sbc_ops_save_agent'); ?><p><select name="user_id" required><option value="">Choose WordPress user</option><?php foreach(get_users(['orderby'=>'display_name']) as $u): ?><option value="<?php echo (int)$u->ID; ?>"><?php echo esc_html($u->display_name.' ('.$u->user_email.')'); ?></option><?php endforeach; ?></select></p><p><input required name="tag_name" placeholder="Divine Levi"></p><p><select name="department_id"><option value="0">No department</option><?php foreach($deps as $d): ?><option value="<?php echo (int)$d->id; ?>"><?php echo esc_html($d->name); ?></option><?php endforeach; ?></select></p><button class="button button-primary">Register agent</button></form></div></div><h2>Active agents</h2><table class="widefat striped"><thead><tr><th>WordPress user</th><th>Agent settings</th><th>Average review</th><th>Performance</th><th>Action</th></tr></thead><tbody><?php if(!$agents):?><tr><td colspan="5">No agents registered.</td></tr><?php endif;foreach($agents as $a): ?><tr><td><strong><?php echo esc_html($a->display_name); ?></strong><br><?php echo esc_html($a->user_email); ?></td><td><form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap"><input type="hidden" name="action" value="sbc_ops_save_agent"><input type="hidden" name="user_id" value="<?php echo (int)$a->user_id;?>"><?php wp_nonce_field('sbc_ops_save_agent');?><input required name="tag_name" value="<?php echo esc_attr($a->tag_name);?>"><select name="department_id"><option value="0">No department</option><?php foreach($deps as $d):?><option value="<?php echo (int)$d->id;?>" <?php selected((int)$a->department_id,(int)$d->id);?>><?php echo esc_html($d->name);?></option><?php endforeach;?></select><button class="button">Save changes</button></form></td><td><strong style="color:#f79009">★ <?php echo esc_html(number_format($a->average_rating,1));?> / 5</strong><br><small><?php echo (int)$a->reviews;?> customer reviews</small></td><td><?php echo (int)$a->handled;?> handled chats</td><td><form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return confirm('Remove this user as a support agent?');"><input type="hidden" name="action" value="sbc_ops_delete_agent"><input type="hidden" name="user_id" value="<?php echo (int)$a->user_id;?>"><?php wp_nonce_field('sbc_ops_delete_agent_'.$a->user_id);?><button class="button button-link-delete">Remove agent</button></form></td></tr><?php endforeach; ?></tbody></table></div><?php
    }

    public function save_department(){ $this->admin_guard(); check_admin_referer('sbc_ops_save_department'); global $wpdb;$t=$this->tables();$name=sanitize_text_field($_POST['name']??'');if($name)$wpdb->replace($t['departments'],['name'=>$name,'slug'=>sanitize_title($name),'active'=>1,'created_at'=>current_time('mysql')]);$this->redirect('sbc-agents'); }
    public function save_agent(){ $this->admin_guard();check_admin_referer('sbc_ops_save_agent');$uid=absint($_POST['user_id']??0);if(!$uid||!get_userdata($uid))wp_die('Choose a valid WordPress user.');global $wpdb;$t=$this->tables();$wpdb->replace($t['agents'],['user_id'=>$uid,'tag_name'=>sanitize_text_field($_POST['tag_name']??''),'department_id'=>absint($_POST['department_id']??0),'active'=>1,'updated_at'=>current_time('mysql')]);$u=new WP_User($uid);$u->add_cap(self::AGENT_CAP);$this->redirect('sbc-agents'); }
    public function delete_agent(){ $this->admin_guard();$uid=absint($_POST['user_id']??0);check_admin_referer('sbc_ops_delete_agent_'.$uid);if(!$uid)wp_die('Invalid agent.');global $wpdb;$t=$this->tables();$wpdb->delete($t['agents'],['user_id'=>$uid]);$wpdb->update($t['chats'],['assigned_agent_id'=>0,'assignment_heartbeat_at'=>null],['assigned_agent_id'=>$uid]);$u=new WP_User($uid);$u->remove_cap(self::AGENT_CAP);$this->redirect('sbc-agents','agent_removed'); }

    private function profile($user_id=0){ global $wpdb;$t=$this->tables();$uid=$user_id?:get_current_user_id();return $wpdb->get_row($wpdb->prepare("SELECT p.*,d.name department_name FROM {$t['agents']} p LEFT JOIN {$t['departments']} d ON d.id=p.department_id WHERE p.user_id=%d AND p.active=1",$uid)); }
    public function staff_identity($user_id=0){$uid=$user_id?:get_current_user_id();$u=get_userdata($uid);$p=$this->profile($uid);return ['user_id'=>$uid,'display_name'=>$p&&$p->tag_name?$p->tag_name:($u?$u->display_name:''),'email'=>$u?$u->user_email:'','role'=>user_can($uid,self::MANAGE_CAP)?'admin':'agent','department_id'=>$p?(int)$p->department_id:0,'department'=>$p?(string)$p->department_name:'','can_manage'=>user_can($uid,self::MANAGE_CAP)];}
    public function is_registered_staff($user_id){return user_can($user_id,self::MANAGE_CAP)||(bool)$this->profile($user_id);}
    public function agent_choices(){global $wpdb;$t=$this->tables();$rows=$wpdb->get_results("SELECT p.user_id,p.tag_name,p.department_id,d.name department_name FROM {$t['agents']} p LEFT JOIN {$t['departments']} d ON d.id=p.department_id WHERE p.active=1 ORDER BY p.tag_name",ARRAY_A);return $rows?:[];}
    public function agent_performance(){global $wpdb;$t=$this->tables();$rows=$this->agent_choices();foreach($rows as &$row){$stats=$wpdb->get_row($wpdb->prepare("SELECT COUNT(*) handled,COUNT(rating) reviews,COALESCE(AVG(rating),0) average_rating FROM {$t['chats']} WHERE assigned_agent_id=%d",$row['user_id']),ARRAY_A);$row=array_merge($row,$stats?:['handled'=>0,'reviews'=>0,'average_rating'=>0]);}unset($row);return $rows;}
    public function admin_directory(){global $wpdb;$t=$this->tables();$deps=$wpdb->get_results("SELECT id,name FROM {$t['departments']} WHERE active=1 ORDER BY name",ARRAY_A);$users=get_users(['orderby'=>'display_name','fields'=>['ID','display_name','user_email']]);$user_rows=array_map(function($u){return ['id'=>(int)$u->ID,'name'=>$u->display_name,'email'=>$u->user_email];},$users);$canned=$wpdb->get_results("SELECT id,title,message,scope,department_id,owner_user_id FROM {$t['canned']} WHERE active=1 ORDER BY scope,title",ARRAY_A);return ['departments'=>$deps?:[],'users'=>$user_rows,'agents'=>$this->agent_performance(),'canned'=>$canned?:[]];}
    public function api_save_agent($user_id,$tag_name,$department_id){$uid=absint($user_id);if(!$uid||!get_userdata($uid))return new WP_Error('invalid_agent','Choose a valid WordPress user.');global $wpdb;$t=$this->tables();$ok=$wpdb->replace($t['agents'],['user_id'=>$uid,'tag_name'=>sanitize_text_field($tag_name),'department_id'=>absint($department_id),'active'=>1,'updated_at'=>current_time('mysql')]);if($ok===false)return new WP_Error('agent_save_failed','The agent could not be saved.');$u=new WP_User($uid);$u->add_cap(self::AGENT_CAP);return true;}
    public function api_delete_agent($user_id){$uid=absint($user_id);if(!$uid)return new WP_Error('invalid_agent','Invalid support agent.');global $wpdb;$t=$this->tables();$wpdb->delete($t['agents'],['user_id'=>$uid]);$wpdb->update($t['chats'],['assigned_agent_id'=>0,'assignment_heartbeat_at'=>null],['assigned_agent_id'=>$uid]);$u=new WP_User($uid);$u->remove_cap(self::AGENT_CAP);return true;}
    public function api_save_canned($id,$title,$message,$scope='global'){$title=sanitize_text_field($title);$message=sanitize_textarea_field($message);$scope=in_array($scope,['global','personal','department'],true)?$scope:'global';if(!$title||!$message)return new WP_Error('invalid_reply','Title and reply are required.');global $wpdb;$t=$this->tables();$data=['owner_user_id'=>$scope==='personal'?get_current_user_id():0,'department_id'=>0,'scope'=>$scope,'title'=>$title,'message'=>$message,'active'=>1];if(absint($id))$ok=$wpdb->update($t['canned'],$data,['id'=>absint($id)]);else{$data['created_at']=current_time('mysql');$ok=$wpdb->insert($t['canned'],$data);}return $ok===false?new WP_Error('reply_save_failed','The canned reply could not be saved.'):true;}
    public function api_delete_canned($id){global $wpdb;$t=$this->tables();return $wpdb->delete($t['canned'],['id'=>absint($id)])===false?new WP_Error('reply_delete_failed','The canned reply could not be deleted.'):true;}
    public function can_access_chat($chat,$user_id=0){ $uid=$user_id?:get_current_user_id();if(user_can($uid,self::MANAGE_CAP))return true;$p=$this->profile($uid);if(!$p)return false;return !(int)$chat->department_id||(int)$chat->department_id===(int)$p->department_id||(int)$chat->assigned_agent_id===$uid; }
    private function audit($action,$chat_id=0,$details=[],$actor=0){global $wpdb;$t=$this->tables();$wpdb->insert($t['audit'],['actor_user_id'=>$actor?:get_current_user_id(),'chat_id'=>(int)$chat_id,'action_name'=>sanitize_key($action),'details'=>wp_json_encode($details),'created_at'=>current_time('mysql')]);}

    public function claim_chat($chat_id,$user_id=0,$override=false){global $wpdb;$t=$this->tables();$uid=$user_id?:get_current_user_id();$chat=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id=%d",$chat_id));if(!$chat||!$this->can_access_chat($chat,$uid))return new WP_Error('forbidden','You cannot access this chat.');$expired=!$chat->assignment_heartbeat_at||strtotime($chat->assignment_heartbeat_at)<time()-60;if(user_can($uid,self::MANAGE_CAP)&&(int)$chat->assigned_agent_id&&$chat->assigned_agent_id!=$uid&&!$expired){$this->audit('admin_view_override',$chat_id,[],$uid);return true;}if((int)$chat->assigned_agent_id&&$chat->assigned_agent_id!=$uid&&!$expired&&!$override){return new WP_Error('chat_busy',$this->agent_label($chat->assigned_agent_id).' is currently handling this conversation.');}$now=current_time('mysql');$claimed=$wpdb->query($wpdb->prepare("UPDATE {$t['chats']} SET assigned_agent_id=%d,assignment_heartbeat_at=%s,last_human_activity_at=COALESCE(last_human_activity_at,%s) WHERE id=%d AND (assigned_agent_id=0 OR assigned_agent_id=%d OR assignment_heartbeat_at IS NULL OR assignment_heartbeat_at<%s)",$uid,$now,$now,$chat_id,$uid,wp_date('Y-m-d H:i:s',time()-60,wp_timezone())));if(!$claimed)return new WP_Error('chat_busy','Another agent claimed this conversation.');$this->audit('claim',$chat_id,[],$uid);return true;}
    private function agent_label($uid){$p=$this->profile($uid);$u=get_userdata($uid);$name=$p&&$p->tag_name?$p->tag_name:($u?$u->display_name:'Another agent');return $name.($p&&$p->department_name?' — '.$p->department_name:'');}
    public function transfer_chat($chat_id,$target_user_id=0,$department_id=0,$actor=0){global $wpdb;$t=$this->tables();$actor=$actor?:get_current_user_id();$chat=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id=%d",$chat_id));if(!$chat||!$this->can_access_chat($chat,$actor))return new WP_Error('forbidden','You cannot transfer this chat.');if($chat->status==='closed')return new WP_Error('chat_closed','A closed chat cannot be transferred.');if(!user_can($actor,self::MANAGE_CAP)&&(int)$chat->assigned_agent_id!==$actor)return new WP_Error('not_owner','Only the active agent or an administrator can transfer this chat.');if($target_user_id){$target=$this->profile($target_user_id);if(!$target)return new WP_Error('invalid_agent','Choose an active support agent.');$department_id=(int)$target->department_id;}$now=current_time('mysql');$wpdb->update($t['chats'],['assigned_agent_id'=>(int)$target_user_id,'department_id'=>(int)$department_id,'assignment_heartbeat_at'=>$target_user_id?$now:null,'updated_at'=>$now],['id'=>$chat_id]);$label=$target_user_id?$this->agent_label($target_user_id):($department_id?'department queue':'unassigned queue');$wpdb->insert($t['msgs'],['chat_id'=>$chat_id,'sender_type'=>'system','message'=>'Conversation transferred to '.$label.'.','created_at'=>$now]);$this->audit('transfer',$chat_id,['target_user_id'=>(int)$target_user_id,'department_id'=>(int)$department_id],$actor);if($target_user_id)do_action('sbc_chat_transferred',(int)$chat_id,(int)$target_user_id);return true;}

    public function canned_page(){ $this->admin_guard(self::AGENT_CAP);global $wpdb;$t=$this->tables();$rows=$this->visible_canned();?><div class="wrap"><h1>Canned Replies</h1><form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"><input type="hidden" name="action" value="sbc_ops_save_canned"><?php wp_nonce_field('sbc_ops_save_canned'); ?><p><input name="title" required placeholder="Quick title"></p><p><textarea name="message" required rows="4" cols="70" placeholder="Reply text"></textarea></p><p><select name="scope"><option value="personal">Personal</option><?php if(current_user_can(self::MANAGE_CAP)): ?><option value="global">Global</option><option value="department">Department</option><?php endif; ?></select></p><button class="button button-primary">Add reply</button></form><h2>Available replies</h2><table class="widefat striped"><thead><tr><th>Title and reply</th><th>Scope</th><th>Actions</th></tr></thead><tbody><?php if(!$rows):?><tr><td colspan="3">No canned replies yet.</td></tr><?php endif;foreach($rows as $r):$can_manage=current_user_can(self::MANAGE_CAP)||($r->scope==='personal'&&(int)$r->owner_user_id===get_current_user_id());?><tr><td><?php if($can_manage):?><form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"><input type="hidden" name="action" value="sbc_ops_save_canned"><input type="hidden" name="canned_id" value="<?php echo (int)$r->id;?>"><?php wp_nonce_field('sbc_ops_save_canned');?><p><input required name="title" value="<?php echo esc_attr($r->title);?>" style="width:100%;max-width:420px"></p><p><textarea required name="message" rows="3" style="width:100%;max-width:620px"><?php echo esc_textarea($r->message);?></textarea></p><input type="hidden" name="scope" value="<?php echo esc_attr($r->scope);?>"><button class="button">Save changes</button></form><?php else:?><strong><?php echo esc_html($r->title);?></strong><p><?php echo esc_html($r->message);?></p><?php endif;?></td><td><?php echo esc_html($r->scope);?></td><td><?php if($can_manage):?><form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return confirm('Delete this canned reply?');"><input type="hidden" name="action" value="sbc_ops_delete_canned"><input type="hidden" name="canned_id" value="<?php echo (int)$r->id;?>"><?php wp_nonce_field('sbc_ops_delete_canned_'.$r->id);?><button class="button button-link-delete">Delete</button></form><?php endif;?></td></tr><?php endforeach;?></tbody></table></div><?php }
    public function save_canned(){ $this->admin_guard(self::AGENT_CAP);check_admin_referer('sbc_ops_save_canned');$scope=sanitize_key($_POST['scope']??'personal');if(!current_user_can(self::MANAGE_CAP))$scope='personal';$p=$this->profile();global $wpdb;$t=$this->tables();$id=absint($_POST['canned_id']??0);$data=['owner_user_id'=>$scope==='personal'?get_current_user_id():0,'department_id'=>$scope==='department'&&$p?(int)$p->department_id:0,'scope'=>$scope,'title'=>sanitize_text_field($_POST['title']??''),'message'=>sanitize_textarea_field($_POST['message']??''),'active'=>1];if(!$data['title']||!$data['message'])wp_die('A title and reply are required.');if($id){$row=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['canned']} WHERE id=%d",$id));if(!$row||(!current_user_can(self::MANAGE_CAP)&&($row->scope!=='personal'||(int)$row->owner_user_id!==get_current_user_id())))wp_die('You cannot edit this reply.');$wpdb->update($t['canned'],$data,['id'=>$id]);}else{$data['created_at']=current_time('mysql');$wpdb->insert($t['canned'],$data);}$this->redirect('sbc-canned-replies'); }
    public function delete_canned(){ $this->admin_guard(self::AGENT_CAP);$id=absint($_POST['canned_id']??0);check_admin_referer('sbc_ops_delete_canned_'.$id);global $wpdb;$t=$this->tables();$row=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['canned']} WHERE id=%d",$id));if(!$row||(!current_user_can(self::MANAGE_CAP)&&($row->scope!=='personal'||(int)$row->owner_user_id!==get_current_user_id())))wp_die('You cannot delete this reply.');$wpdb->delete($t['canned'],['id'=>$id]);$this->redirect('sbc-canned-replies','deleted'); }
    private function visible_canned($uid=0){global $wpdb;$t=$this->tables();$uid=$uid?:get_current_user_id();$p=$this->profile($uid);$dep=$p?(int)$p->department_id:0;return $wpdb->get_results($wpdb->prepare("SELECT * FROM {$t['canned']} WHERE active=1 AND (scope='global' OR (scope='personal' AND owner_user_id=%d) OR (scope='department' AND department_id=%d)) ORDER BY scope,title",$uid,$dep));}
    public function visible_canned_for_mobile($uid){$rows=$this->visible_canned($uid);return array_map(function($r){return ['id'=>(int)$r->id,'title'=>$r->title,'message'=>$r->message,'scope'=>$r->scope];},$rows?:[]);}

    public function check_inactivity($chat_id=0){
        global $wpdb;$t=$this->tables();$now=current_time('mysql');$warn_cut=wp_date('Y-m-d H:i:s',time()-120,wp_timezone());$warning_grace_cut=wp_date('Y-m-d H:i:s',time()-120,wp_timezone());$id_sql=$chat_id?$wpdb->prepare(' AND id=%d',absint($chat_id)):'';
        $warn=$wpdb->get_results($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE status IN ('waiting','active') AND assigned_agent_id>0 AND inactivity_warned_at IS NULL AND COALESCE(last_human_activity_at,updated_at)<=%s{$id_sql}",$warn_cut));
        foreach($warn as $c){$claimed=$wpdb->query($wpdb->prepare("UPDATE {$t['chats']} SET inactivity_warned_at=%s WHERE id=%d AND status IN ('waiting','active') AND inactivity_warned_at IS NULL AND COALESCE(last_human_activity_at,updated_at)<=%s",$now,$c->id,$warn_cut));if(!$claimed)continue;$wpdb->insert($t['msgs'],['chat_id'=>$c->id,'sender_type'=>'warning','message'=>'No recent activity. If no message is sent, this chat will close after two minutes.','created_at'=>$now]);$this->audit('inactivity_warning',$c->id);}
        // Close only after the warning itself has been visible for two full
        // minutes. A late cron run can therefore never warn and close at once.
        $close=$wpdb->get_results($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE status IN ('waiting','active') AND assigned_agent_id>0 AND inactivity_warned_at IS NOT NULL AND inactivity_warned_at<=%s AND COALESCE(last_human_activity_at,updated_at)<=inactivity_warned_at{$id_sql}",$warning_grace_cut));
        foreach($close as $c){$closed=$wpdb->query($wpdb->prepare("UPDATE {$t['chats']} SET status='closed',closed_at=%s,assigned_agent_id=0,assignment_heartbeat_at=NULL,updated_at=%s WHERE id=%d AND status=%s AND inactivity_warned_at=%s AND COALESCE(last_human_activity_at,updated_at)<=inactivity_warned_at",$now,$now,$c->id,$c->status,$c->inactivity_warned_at));if(!$closed)continue;$wpdb->insert($t['msgs'],['chat_id'=>$c->id,'sender_type'=>'system','message'=>'This chat was automatically closed after four minutes without a message.','created_at'=>$now]);$this->audit('auto_close',$c->id);}
    }

    public function rest_routes(){register_rest_route('support-bonus-chat/v1','/tokens/credit',['methods'=>'POST','callback'=>[$this,'rest_credit_tokens'],'permission_callback'=>'__return_true']);}
    public function rest_credit_tokens($request){$raw=$request->get_body();$sig=(string)$request->get_header('x-sbc-signature');$secret=(string)get_option('sbc_token_site_secret');if(!$sig||!hash_equals(hash_hmac('sha256',$raw,$secret),$sig))return new WP_Error('invalid_signature','Invalid token adjustment signature.',['status'=>401]);$data=json_decode($raw,true)?:[];$package=sanitize_key($data['package']??'');$reference=sanitize_text_field($data['reference']??'');$operation=sanitize_key($data['operation']??'credit');if($operation==='debit'){$result=$this->remove_tokens((float)($data['tokens']??0),$reference);}elseif($package==='admin_custom'){$result=$this->credit_tokens('admin_custom',$reference,(float)($data['tokens']??0),2.0,0,'Admin custom token credit');}else{$result=$this->credit_package($package,$reference,(float)($data['amount']??0));}return is_wp_error($result)?new WP_Error($result->get_error_code(),$result->get_error_message(),['status'=>$result->get_error_code()==='insufficient_tokens'?409:400]):rest_ensure_response(['success'=>true,'balance'=>$this->token_balance()]);}

    public function portal_help_page(){ $this->admin_guard(self::AGENT_CAP);?><div class="wrap"><h1>Desktop Agent Portal</h1><p>Create a private WordPress page and place this shortcode in it:</p><p><code>[support_bonus_agent_portal]</code></p><p>Only logged-in administrators and registered support agents can open it. Administrators see every conversation and can override agent locks.</p></div><?php }
    public function portal_shortcode(){if(!is_user_logged_in())return '<div class="sbc-portal-login">Please log in to access the support desk.</div>';if(!current_user_can(self::AGENT_CAP))return '<div class="sbc-portal-denied">Your account is not registered as a support agent.</div>';wp_enqueue_script('jquery');$nonce=wp_create_nonce('sbc_ops_portal');$primary=sanitize_hex_color(get_option('sbc_primary_color','#0867ed'))?:'#0867ed';$secondary=sanitize_hex_color(get_option('sbc_secondary_color','#4f46e5'))?:'#4f46e5';ob_start();?><div id="sbc-agent-portal"><style>#sbc-agent-portal{--sbc-primary:<?php echo esc_attr($primary);?>;--sbc-secondary:<?php echo esc_attr($secondary);?>;font:14px system-ui;color:#14213d;background:#f4f7fb;min-height:650px;border-radius:18px;overflow:hidden}.sbcp-head{background:linear-gradient(135deg,var(--sbc-primary),var(--sbc-secondary));color:white;padding:18px 22px;display:flex;justify-content:space-between}.sbcp-grid{display:grid;grid-template-columns:320px 1fr 280px;min-height:590px}.sbcp-pane{padding:16px;border-right:1px solid #dce5f1;background:white}.sbcp-main{padding:18px}.sbcp-list button{width:100%;text-align:left;border:1px solid #dce5f1;background:white;border-radius:12px;padding:12px;margin:5px 0}.sbcp-msg{padding:10px 12px;margin:8px;max-width:70%;border-radius:13px;background:white}.sbcp-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))}.sbcp-compose{display:flex;gap:8px;margin-top:14px}.sbcp-compose textarea{flex:1;min-height:70px}@media(max-width:850px){.sbcp-grid{grid-template-columns:1fr}.sbcp-pane.sbcp-details{display:none}}</style><div class="sbcp-head"><strong>Support Desk</strong><span><?php echo esc_html(wp_get_current_user()->display_name); ?></span></div><div class="sbcp-grid"><aside class="sbcp-pane"><input id="sbcp-search" placeholder="Search customers" style="width:100%"><div id="sbcp-list" class="sbcp-list"></div></aside><main class="sbcp-main"><div id="sbcp-chat">Choose a conversation.</div></main><aside class="sbcp-pane sbcp-details"><h3>Customer &amp; transaction</h3><div id="sbcp-details"></div></aside></div></div><script>(function($){const ajax=<?php echo wp_json_encode(admin_url('admin-ajax.php'));?>,nonce=<?php echo wp_json_encode($nonce);?>;let current=0;function call(op,data){return $.post(ajax,Object.assign({action:'sbc_ops_portal',nonce,op},data||{}));}function load(){call('inbox',{search:$('#sbcp-search').val()}).done(r=>{if(!r.success)return;$('#sbcp-list').html(r.data.chats.map(c=>`<button data-id="${c.id}"><b>${esc(c.full_name||c.customer_email)}</b><br><small>${esc(c.service_type)} · ${esc(c.status)}${c.agent_label?' · '+esc(c.agent_label):''}</small></button>`).join(''));});}function esc(v){return $('<div>').text(v||'').html();}function open(id){current=id;call('open',{chat_id:id}).done(r=>{if(!r.success){alert(r.data.message);return;}let c=r.data.chat;$('#sbcp-chat').html(`<h2>${esc(c.full_name)}</h2><div>${c.messages.map(m=>`<div class="sbcp-msg ${m.sender_type}"><small>${esc(m.sender_type)}</small><br>${esc(m.message)}</div>`).join('')}</div><div class="sbcp-compose"><textarea id="sbcp-reply" placeholder="Type a reply"></textarea><button id="sbcp-send">Send</button><button id="sbcp-close">Close</button></div>`);$('#sbcp-details').html(`<p><b>Email</b><br>${esc(c.customer_email)}</p><p><b>Service</b><br>${esc(c.service_type)}</p><p><b>Complaint</b><br>${esc(c.complaint_reason)}</p><pre style="white-space:pre-wrap">${esc(c.transaction_snapshot)}</pre>`);});}$('#sbcp-list').on('click','button',function(){open(this.dataset.id)});$('#sbc-agent-portal').on('click','#sbcp-send',()=>call('reply',{chat_id:current,message:$('#sbcp-reply').val()}).done(()=>open(current)));$('#sbc-agent-portal').on('click','#sbcp-close',()=>call('close',{chat_id:current}).done(()=>{load();open(current)}));$('#sbcp-search').on('input',load);setInterval(()=>{load();if(current)open(current)},3000);load();})(jQuery);</script><?php return ob_get_clean();}

    public function portal_ajax(){check_ajax_referer('sbc_ops_portal','nonce');if(!current_user_can(self::AGENT_CAP))wp_send_json_error(['message'=>'Unauthorized'],403);global $wpdb;$t=$this->tables();$op=sanitize_key($_POST['op']??'');$uid=get_current_user_id();if($op==='inbox'){$search=sanitize_text_field($_POST['search']??'');$like='%'.$wpdb->esc_like($search).'%';$rows=$wpdb->get_results($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE (%s='' OR full_name LIKE %s OR customer_email LIKE %s) ORDER BY updated_at DESC LIMIT 200",$search,$like,$like));$out=[];foreach($rows as $c)if($this->can_access_chat($c,$uid)){$c->agent_label=$c->assigned_agent_id?$this->agent_label($c->assigned_agent_id):'';$out[]=$c;}wp_send_json_success(['chats'=>$out]);}$id=absint($_POST['chat_id']??0);$chat=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id=%d",$id));if(!$chat||!$this->can_access_chat($chat,$uid))wp_send_json_error(['message'=>'Chat not found or unavailable.'],404);if($op==='open'){$this->check_inactivity($id);$chat=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id=%d",$id));if($chat->status!=='closed'){$claim=$this->claim_chat($id,$uid);if(is_wp_error($claim))wp_send_json_error(['message'=>$claim->get_error_message()],409);$wpdb->update($t['chats'],['assignment_heartbeat_at'=>current_time('mysql')],['id'=>$id]);}$chat=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['chats']} WHERE id=%d",$id));$chat->messages=$wpdb->get_results($wpdb->prepare("SELECT * FROM {$t['msgs']} WHERE chat_id=%d ORDER BY id",$id));wp_send_json_success(['chat'=>$chat,'canned'=>$this->visible_canned($uid)]);}if($op==='reply'){if($chat->status==='closed')wp_send_json_error(['message'=>'This chat is closed and cannot receive replies.'],409);$claim=$this->claim_chat($id,$uid);if(is_wp_error($claim))wp_send_json_error(['message'=>$claim->get_error_message()],409);$message=sanitize_textarea_field($_POST['message']??'');if(!$message)wp_send_json_error(['message'=>'Reply required.']);$now=current_time('mysql');$wpdb->insert($t['msgs'],['chat_id'=>$id,'sender_type'=>'agent','message'=>$message,'created_at'=>$now]);$wpdb->update($t['chats'],['status'=>'active','agent_replied_at'=>$chat->agent_replied_at?:$now,'last_human_activity_at'=>$now,'inactivity_warned_at'=>null,'assignment_heartbeat_at'=>$now,'updated_at'=>$now],['id'=>$id]);$this->audit('reply',$id);wp_send_json_success();}if($op==='close'){$now=current_time('mysql');$wpdb->update($t['chats'],['status'=>'closed','closed_at'=>$now,'assigned_agent_id'=>0,'assignment_heartbeat_at'=>null,'updated_at'=>$now],['id'=>$id]);$this->audit('close',$id);wp_send_json_success();}wp_send_json_error(['message'=>'Invalid action.'],400);}
}

function sbc_ops(){ return SBC_Operations::instance(); }
sbc_ops();
