<?php
function gemaccServiceTypeFromHint(string $raw, string $hint=''): string {
    $s = function_exists('mb_strtolower') ? mb_strtolower(trim($raw), 'UTF-8') : strtolower(trim($raw));
    $h = function_exists('mb_strtolower') ? mb_strtolower(trim($hint), 'UTF-8') : strtolower(trim($hint));
    if (in_array($s, ['windscribe','ws','ویندسکرایب'], true) || str_contains($h, 'windscribe') || str_contains($h, 'ویندسکرایب')) return 'windscribe';
    if (in_array($s, ['expressvpn','express','اکسپرس','express vpn'], true) || str_contains($h, 'express') || str_contains($h, 'اکسپرس')) return 'expressvpn';
    if (in_array($s, ['v2ray','v2','vmess','vless','trojan','ویتوری','وی تو ری','وی‌تو‌ری'], true) || str_contains($h, 'v2ray') || str_contains($h, 'vmess') || str_contains($h, 'vless') || str_contains($h, 'trojan') || str_contains($h, 'ویتوری') || str_contains($h, 'وی تو ری') || str_contains($h, 'وی‌تو‌ری')) return 'v2ray';
    return 'openvpn_l2tp';
}
function gemaccServiceLabelFromRow(array $row): string {
    $custom = trim((string)($row['service_name'] ?? $row['category_service_name'] ?? ''));
    $type = gemaccServiceTypeFromHint((string)($row['service_type'] ?? $row['protocol'] ?? $row['category_service_type'] ?? 'openvpn_l2tp'), $custom.' '.(string)($row['name'] ?? $row['category_name'] ?? $row['title'] ?? ''));
    if ($custom !== '') return $custom;
    return $type === 'windscribe' ? 'Windscribe' : ($type === 'expressvpn' ? 'ExpressVPN' : ($type === 'v2ray' ? 'V2Ray' : 'OpenVPN | L2TP'));
}
function gemaccServiceCodePrefix(string $type): string { $type=gemaccServiceTypeFromHint($type); return $type === 'windscribe' ? 'WS_' : ($type === 'expressvpn' ? 'EXP_' : ($type === 'v2ray' ? 'V2_' : 'OVPN_')); }
function gemaccServiceEmojiFromType(string $type): string { $type = gemaccServiceTypeFromHint($type); return $type === 'windscribe' ? '🟦' : ($type === 'expressvpn' ? '🚀' : ($type === 'v2ray' ? '⚡' : '🧊')); }
function gemaccServiceShortLabel(string $type): string { $type = gemaccServiceTypeFromHint($type); return $type === 'windscribe' ? 'Windscribe' : ($type === 'expressvpn' ? 'ExpressVPN' : ($type === 'v2ray' ? 'V2Ray' : 'OpenVPN/L2TP')); }

function gemaccTelegramMessageAttachment(array $msg): array {
    $caption = trim((string)($msg['caption'] ?? $msg['text'] ?? ''));
    if (!empty($msg['photo']) && is_array($msg['photo'])) {
        $last = end($msg['photo']);
        return ['file_id'=>(string)($last['file_id'] ?? ''), 'type'=>'photo', 'name'=>'telegram-photo.jpg', 'mime'=>'image/jpeg', 'text'=>$caption];
    }
    foreach ([
        'document'=>['document','فایل'], 'video'=>['video','ویدیو'], 'animation'=>['animation','تصویر متحرک'],
        'voice'=>['voice','پیام صوتی'], 'audio'=>['audio','فایل صوتی']
    ] as $key=>$meta) {
        if (!empty($msg[$key]) && is_array($msg[$key])) {
            $row=$msg[$key];
            return [
                'file_id'=>(string)($row['file_id'] ?? ''), 'type'=>$meta[0],
                'name'=>(string)($row['file_name'] ?? ($meta[1].($key==='document'?'.bin':''))),
                'mime'=>(string)($row['mime_type'] ?? ''), 'text'=>$caption,
            ];
        }
    }
    return ['file_id'=>'','type'=>'','name'=>'','mime'=>'','text'=>$caption];
}

function gemaccStarsRateToman(): int {
    return max(1, (int)(function_exists('getSetting') ? getSetting('stars_rate_toman','7000') : 7000));
}
function gemaccStarsAmountFromToman(int $toman): int {
    return max(1, (int)ceil(max(0, $toman) / gemaccStarsRateToman()));
}
function gemaccStarsTrafficLabel(array $row): string {
    $type = (string)($row['traffic_type'] ?? $row['category_traffic_type'] ?? 'unlimited');
    if ($type === 'limited') return 'حجمی ' . (int)($row['traffic_limit_gb'] ?? $row['category_traffic_limit_gb'] ?? 0) . ' گیگ';
    return 'نامحدود';
}
function gemaccStarsDurationLabel(array $row): string {
    $months = max(1, (int)($row['months'] ?? $row['category_months'] ?? 1));
    if ($months === 12) return 'یک‌ساله / ۱۲ ماهه';
    if ($months === 1) return 'یک‌ماهه';
    return $months . ' ماهه';
}
function gemaccStarsInvoiceDescription(array $plan, int $finalPrice, int $starsAmount, int $requestedSeats): string {
    $service = gemaccServiceLabelFromRow($plan);
    $duration = gemaccStarsDurationLabel($plan);
    $rate = gemaccStarsRateToman();
    $lines = [
        'خرید امن با استارز GEM ACC',
        'سرویس: ' . $service,
        'مدت: ' . $duration,
        'تعداد کاربر: ' . $requestedSeats,
        'قیمت: ' . number_format($finalPrice) . ' تومان',
        'تبدیل: هر ⭐ = ' . number_format($rate) . ' تومان',
        'پرداخت نهایی: ' . $starsAmount . '⭐',
    ];
    return mb_substr(implode("
", $lines), 0, 255, 'UTF-8');
}
function gemaccStarsCancelPendingOrder(PDO $pdo, int $orderId, int $userId): array {
    if (function_exists('gemaccStarsAutoCancelPendingOrder')) {
        return gemaccStarsAutoCancelPendingOrder($pdo, $orderId, $userId, false, false);
    }
    $pdo->beginTransaction();
    try {
        $st = $pdo->prepare("SELECT * FROM orders WHERE id=? AND user_id=? AND UPPER(COALESCE(payment_method,''))='STARS' FOR UPDATE");
        $st->execute([$orderId, $userId]);
        $order = $st->fetch(PDO::FETCH_ASSOC);
        if (!$order) throw new RuntimeException('سفارش استارز یافت نشد.');
        if (strtolower((string)($order['payment_status'] ?? '')) === 'paid') throw new RuntimeException('این سفارش پرداخت شده و قابل لغو نیست.');
        if (in_array((string)($order['status'] ?? ''), ['CANCELLED','REJECTED','DELIVERED'], true)) {
            $pdo->commit();
            return ['ok'=>true,'message'=>'این سفارش قبلاً بسته شده است.'];
        }
        try {
            $du = $pdo->prepare("SELECT discount_id FROM discount_usages WHERE order_id=?");
            $du->execute([$orderId]);
            $discountIds = array_map('intval', $du->fetchAll(PDO::FETCH_COLUMN));
            foreach ($discountIds as $did) {
                if ($did > 0) $pdo->prepare("UPDATE discount_codes SET used_count=GREATEST(used_count-1,0) WHERE id=?")->execute([$did]);
            }
            $pdo->prepare("DELETE FROM discount_usages WHERE order_id=?")->execute([$orderId]);
        } catch (Throwable $ignored) {}
        $pdo->prepare("UPDATE orders SET payment_status='cancelled', delivery_status='cancelled', status='CANCELLED', updated_at=NOW() WHERE id=?")->execute([$orderId]);
        $pdo->commit();
        return ['ok'=>true,'message'=>'سفارش استارز لغو شد.'];
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        return ['ok'=>false,'error'=>$e->getMessage()];
    }
}
function gemaccStarsNotifyAdminsAfterPayment(PDO $pdo, int $orderId, array $result, int $starsPaid): void {
    try {
        $st = $pdo->prepare("SELECT o.*, u.first_name, u.username FROM orders o LEFT JOIN users u ON u.id=o.user_id WHERE o.id=? LIMIT 1");
        $st->execute([$orderId]);
        $order = $st->fetch(PDO::FETCH_ASSOC) ?: [];
        $userName = trim((string)($order['first_name'] ?? '')) ?: ((string)($order['username'] ?? 'کاربر'));
        $details = [
            'شماره سفارش' => '#' . $orderId,
            'کاربر' => $userName . ' | ' . (int)($order['user_id'] ?? 0),
            'سرویس' => (string)($order['plan_title'] ?? 'سرویس'),
            'مبلغ تومان' => function_exists('money') ? money((int)($order['price'] ?? 0)) : number_format((int)($order['price'] ?? 0)),
            'استارز پرداخت‌شده' => $starsPaid . ' ⭐',
            'روش پرداخت' => 'Telegram Stars / خودکار',
        ];
        if (!empty($result['queued'])) {
            $details['وضعیت'] = 'موجودی کافی نبود؛ سفارش وارد صف تحویل شد';
            gemaccAdminActionNotify('پرداخت استارز موفق؛ سفارش در صف تحویل قرار گرفت', $details, null, [
                'inline_keyboard' => [
                    [['text'=>'🔐 مدیریت اشتراک‌ها','callback_data'=>'ADMIN_CREDENTIALS']],
                    [['text'=>'📋 مشاهده سفارشات/صف','callback_data'=>'ADMIN_ORDERS']],
                ],
            ]);
        } else {
            $details['وضعیت'] = 'بدون نیاز به تأیید ادمین تحویل شد';
            gemaccAdminActionNotify('پرداخت استارز موفق؛ سفارش خودکار تأیید شد', $details, null);
        }
    } catch (Throwable $e) { error_log('[stars admin notify] '.$e->getMessage()); }
}

/**
 * OpenVPN | L2TP Telegram Bot
 * Version 2.0 - Refactored for selling OpenVPN | L2TP credentials
 */

require __DIR__ . '/../config.php';
$__swapwalletLib = dirname(__DIR__, 2) . '/public/mini_app/api/swapwallet_lib.php';
if (is_file($__swapwalletLib)) { require_once $__swapwalletLib; }
$__plisioLib = dirname(__DIR__, 2) . '/public/mini_app/api/plisio_lib.php';
if (is_file($__plisioLib)) { require_once $__plisioLib; }

$__discountEngine = dirname(__DIR__, 2) . '/public/mini_app/api/discount_engine.php';
if (is_file($__discountEngine)) { require_once $__discountEngine; }
$__vpnHelpers = dirname(__DIR__, 2) . '/public/mini_app/api/_vpn_helpers.php';
if (is_file($__vpnHelpers)) { require_once $__vpnHelpers; }


// لود کلاس‌ها
spl_autoload_register(function ($class) {
    $prefix = 'GemAcc\\';
    $base_dir = __DIR__ . '/../GemAcc/';
    
    if (strncmp($prefix, $class, strlen($prefix)) !== 0) {
        return;
    }
    
    $relative_class = substr($class, strlen($prefix));
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
    
    if (file_exists($file)) {
        require $file;
    }
});

use GemAcc\{Credentials, ForceJoin, Support, Tutorials, Orders, Plans, Wallet, DiscountCode, Avatar, StateService, ReferralService};

// گرفتن آپدیت
$__raw = isset($GLOBALS['__update_override_raw']) ? $GLOBALS['__update_override_raw'] : file_get_contents('php://input');
$update = json_decode($__raw, true);
if (!$update) exit('OK');

$pdo = db();
try { if (function_exists('vpnEnsureColumns')) vpnEnsureColumns($pdo); } catch (Throwable $e) { error_log('[vpn_schema_boot] '.$e->getMessage()); }
try { if (function_exists('gemaccStarsAutoCancelExpired')) gemaccStarsAutoCancelExpired($pdo, 10); } catch (Throwable $e) { error_log('[stars_auto_cancel_bot] '.$e->getMessage()); }

// نمونه‌سازی کلاس‌ها
$credentials  = new Credentials($pdo);
$forceJoin    = new ForceJoin($pdo);
$support      = new Support($pdo);
$tutorials    = new Tutorials($pdo);
$orders       = new Orders($pdo);
$plans        = new Plans($pdo);
$wallet       = new Wallet($pdo);
$discountCode = new DiscountCode($pdo);
$avatar       = new Avatar($pdo);

// ════════════════════════════════════════════════════════════════════════════
// STATE MANAGEMENT
// ════════════════════════════════════════════════════════════════════════════

$__gemaccStateService = null;
try {
    $__gemaccStateService = new StateService($pdo, (int)env('BOT_STATE_TTL_SECONDS', 7200));
} catch (Throwable $e) {
    error_log('[StateService] init failed, falling back to locked file state: ' . $e->getMessage());
}
$cacheFile = __DIR__ . '/state-cache.json';
$STATE = [];

function gemaccLegacyStateReadFile(): array {
    global $cacheFile;
    if (!is_file($cacheFile)) return [];
    $fp = @fopen($cacheFile, 'c+');
    if (!$fp) return [];
    @flock($fp, LOCK_SH);
    $raw = stream_get_contents($fp);
    @flock($fp, LOCK_UN);
    @fclose($fp);
    $data = json_decode((string)$raw, true);
    return is_array($data) ? $data : [];
}

function gemaccLegacyStateWriteFile(array $state): void {
    global $cacheFile;
    $fp = @fopen($cacheFile, 'c+');
    if (!$fp) return;
    @flock($fp, LOCK_EX);
    ftruncate($fp, 0);
    rewind($fp);
    fwrite($fp, json_encode($state, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
    fflush($fp);
    @flock($fp, LOCK_UN);
    @fclose($fp);
}

function setUserMode($uid, $mode, $payload = null): void {
    global $__gemaccStateService;
    $uid = (int)$uid;
    $mode = $mode === null ? '' : (string)$mode;
    if ($mode === '') { clearUserMode($uid); return; }
    if ($__gemaccStateService instanceof StateService) {
        $__gemaccStateService->set($uid, $mode, $payload);
        return;
    }
    $state = gemaccLegacyStateReadFile();
    $state[(string)$uid] = ['mode' => $mode, 'payload' => $payload, 't' => time(), 'expires_at' => time() + 7200];
    gemaccLegacyStateWriteFile($state);
}

function getUserMode($uid) {
    global $__gemaccStateService;
    $uid = (int)$uid;
    if ($__gemaccStateService instanceof StateService) return $__gemaccStateService->get($uid);
    $state = gemaccLegacyStateReadFile();
    $row = $state[(string)$uid] ?? null;
    if (is_array($row) && !empty($row['expires_at']) && (int)$row['expires_at'] < time()) {
        unset($state[(string)$uid]);
        gemaccLegacyStateWriteFile($state);
        return null;
    }
    return is_array($row) ? $row : null;
}

function clearUserMode($uid): void {
    global $__gemaccStateService;
    $uid = (int)$uid;
    if ($__gemaccStateService instanceof StateService) { $__gemaccStateService->delete($uid); return; }
    $state = gemaccLegacyStateReadFile();
    unset($state[(string)$uid]);
    gemaccLegacyStateWriteFile($state);
}

/**
 * تشخیص دکمه‌ها و دستورات ناوبری ربات.
 * این تابع عمداً محدود به متن دقیق دکمه‌ها نیست تا اگر ظاهر دکمه تغییر کرد
 * یا ایموجی/فاصله اضافه شد، هنگام انتظار رسید، دکمه منو به‌اشتباه رسید ثبت نشود.
 */
function gemaccNormalizeBotText($text): string {
    $text = trim((string)$text);
    $text = preg_replace('/\s+/u', ' ', $text);
    return $text ?? '';
}



/**
 * فقط متن دقیق دکمه‌های منوی اصلی را تشخیص می‌دهد.
 * برای حالت‌های ورود متن آزاد استفاده می‌شود تا متن‌های آموزشی که داخلشان کلمات
 * «آموزش/پشتیبانی/خرید» دارند، اشتباهی منو حساب نشوند.
 */
function gemaccIsExactMenuCommand($text): bool {
    $t = gemaccNormalizeBotText($text);
    if ($t === '') return false;
    $exact = [
        '/start','/admin','/cancel',
        '🛒 خرید','🛒 خرید فیلترشکن','🟩 🛒 خرید فیلترشکن','🛍️ خرید اشتراک',
        '📦 سرویس‌های من','📡 فیلترشکن من','🟦 📡 فیلترشکن من','📲 سرویس‌های من',
        '👤 حساب کاربری','💰 کیف پول','💎 کیف پول','🟨 💰 کیف پول',
        '🏠 منوی اصلی','🔙 بازگشت','❌ انصراف',
        '🎓 آموزش','📚 آموزش','🟩 📚 آموزش','🟩 ⚙️ 🧪 تست فیلترشکن',
        '❓ سوالات متداول','❔ سوالات متداول','❓ درباره ما',
        '💬 پشتیبانی','💭 پشتیبانی','📱 پشتیبانی','🟥 📱 پشتیبانی',
        '👑 پنل مدیریت'
    ];
    return in_array($t, $exact, true);
}

/**
 * متن دکمه را به یک مقدار استاندارد تبدیل می‌کند تا اگر ایموجی/فاصله/ظاهر دکمه
 * تغییر کرد، switch اصلی ربات از کار نیفتد.
 */
function gemaccCanonicalMenuText($text): string {
    $t = gemaccNormalizeBotText($text);
    if ($t === '') return $t;

    if ($t === '/start' || $t === '/admin' || $t === '/cancel') return $t;
    if (mb_strpos($t, 'خرید فیلترشکن') !== false || mb_strpos($t, 'خرید اشتراک') !== false || $t === '🛒 خرید' || mb_strpos($t, 'خرید سرویس') !== false) return '🛒 خرید فیلترشکن';
    if (mb_strpos($t, 'فیلترشکن من') !== false || mb_strpos($t, 'سرویس‌های من') !== false || mb_strpos($t, 'سرویس های من') !== false) return '📡 فیلترشکن من';
    if (mb_strpos($t, 'حساب کاربری') !== false || mb_strpos($t, 'کیف پول') !== false) return '👤 حساب کاربری';
    if (mb_strpos($t, 'سوالات متداول') !== false) return '❓ سوالات متداول';
    if (mb_strpos($t, 'درباره ما') !== false) return '❓ درباره ما';
    if (mb_strpos($t, 'پشتیبانی') !== false) return '📱 پشتیبانی';
    if (mb_strpos($t, 'پنل مدیریت') !== false) return '👑 پنل مدیریت';
    if (mb_strpos($t, 'آموزش') !== false || mb_strpos($t, 'تست فیلترشکن') !== false) return '🎓 آموزش';
    if (mb_strpos($t, 'منوی اصلی') !== false || mb_strpos($t, 'بازگشت') !== false || mb_strpos($t, 'انصراف') !== false) return '🏠 منوی اصلی';

    return $t;
}

function gemaccSalesEnabled(): bool { return (string)getSetting('sales_enabled','1') === '1'; }
function gemaccSalesClosedMessage(): string { return '⏸ فروش موقتاً متوقف است. لطفاً چند ساعت دیگر دوباره تلاش کنید.'; }

function gemaccIsBotNavigationText($text): bool {
    $t = gemaccNormalizeBotText($text);
    if ($t === '') return false;

    $exact = [
        '/start','/admin','/cancel',
        '🛒 خرید','🛒 خرید فیلترشکن','🟩 🛒 خرید فیلترشکن','🛍️ خرید اشتراک',
        '📦 سرویس‌های من','📡 فیلترشکن من','🟦 📡 فیلترشکن من','📲 سرویس‌های من',
        '👤 حساب کاربری','💰 کیف پول','💎 کیف پول','🟨 💰 کیف پول',
        '🏠 منوی اصلی','🔙 بازگشت','❌ انصراف',
        '🎓 آموزش','📚 آموزش','🟩 📚 آموزش','🟩 ⚙️ 🧪 تست فیلترشکن',
        '❓ سوالات متداول','❔ سوالات متداول','❓ درباره ما','💬 پشتیبانی','💭 پشتیبانی','📱 پشتیبانی','🟥 📱 پشتیبانی',
        '👑 پنل مدیریت'
    ];
    if (in_array($t, $exact, true)) return true;

    // تشخیص مقاوم برای دکمه‌های منو حتی اگر ایموجی یا رنگ عوض شده باشد.
    $keywords = [
        'خرید فیلترشکن','خرید اشتراک','سرویس‌های من','فیلترشکن من',
        'حساب کاربری','کیف پول','منوی اصلی','بازگشت','انصراف',
        'آموزش','سوالات متداول','پشتیبانی','پنل مدیریت','درباره ما','تست فیلترشکن'
    ];
    foreach ($keywords as $kw) {
        if (mb_strpos($t, $kw) !== false) return true;
    }

    return false;
}

// ════════════════════════════════════════════════════════════════════════════
// HELPER FUNCTIONS
// ════════════════════════════════════════════════════════════════════════════

// BASE_PATH ممکن است از public/webhook.php ست شده باشد؛ اگر نبود همینجا ست کن
if (!defined('BASE_PATH')) {
    // src/legacy => src => project root
    define('BASE_PATH', dirname(__DIR__, 2));
}

/**
 * دانلود فایل تلگرام (file_id) و ذخیره داخل mini_app/uploads
 * خروجی: نام فایل (مثلاً receipt_123_1700000000.jpg) یا null در صورت خطا
 */
function saveTelegramFileToUploads(string $fileId, string $prefix, int $userId): ?string {
    // گرفتن file_path از تلگرام
    $gf = tg('getFile', ['file_id' => $fileId]);
    if (!$gf || empty($gf['ok'])) {
        if (function_exists('logError')) {
            logError('getFile failed: ' . json_encode($gf, JSON_UNESCAPED_UNICODE));
        }
        return null;
    }

    $filePath = $gf['result']['file_path'] ?? null;
    if (!$filePath) return null;

    $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION) ?: 'jpg');
    if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
        // بعضی وقت‌ها تلگرام فرمت عجیب می‌دهد؛ پیش‌فرض jpg
        $ext = 'jpg';
    }

    $uploadDir = BASE_PATH . '/public/mini_app/uploads/';
    if (!is_dir($uploadDir)) {
        @mkdir($uploadDir, 0755, true);
    }

    $fileName = "{$prefix}_{$userId}_" . time() . ".{$ext}";
    $destPath = $uploadDir . $fileName;

    $url = 'https://api.telegram.org/file/bot' . BOT_TOKEN . '/' . $filePath;

    // دانلود با CURL (allow_url_fopen ممکن است خاموش باشد)
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_TIMEOUT        => 20,
    ]);
    $bin = curl_exec($ch);
    $err = curl_error($ch);
    $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);

    if ($bin === false || $code !== 200) {
        if (function_exists('logError')) {
            logError("download telegram file failed (code={$code}): {$err}");
        }
        return null;
    }

    if (@file_put_contents($destPath, $bin) === false) {
        if (function_exists('logError')) {
            logError('write upload file failed: ' . $destPath);
        }
        return null;
    }

    return $fileName;
}

/**
 * ارسال امن پیام/عکس به ادمین (برای جلوگیری از خطای parse_mode)
 */
function tgSendSafe(string $method, array $params) {
    $res = tg($method, $params);
    if ($res && !empty($res['ok'])) return $res;

    // اگر parse_mode باعث خطا شده، یکبار بدون parse_mode تلاش کن
    if (isset($params['parse_mode'])) {
        unset($params['parse_mode']);
        $res2 = tg($method, $params);
        if ($res2 && !empty($res2['ok'])) return $res2;
    }

    if (function_exists('logError')) {
        logError('TG send failed: ' . $method . ' | ' . json_encode($res, JSON_UNESCAPED_UNICODE));
    }

    return $res;
}


/**
 * پاسخ دادن به callback_query فقط یک بار (برای جلوگیری از خطای TG_HTTP_400)
 */
if (!function_exists('answerCbOnce')) {
    function answerCbOnce(array $cb, ?string $text = null, bool $showAlert = false): void {
        if (!isset($GLOBALS['__cb_answered'])) {
            $GLOBALS['__cb_answered'] = false;
        }
        if ($GLOBALS['__cb_answered']) return;

        $params = ['callback_query_id' => $cb['id']];
        if ($text !== null) {
            $params['text'] = $text;
            $params['show_alert'] = $showAlert;
        }
        tg('answerCallbackQuery', $params);
        $GLOBALS['__cb_answered'] = true;
    }
}

/**
 * پاسخ دادن به callback_query با پارامترهای آماده، فقط یک بار
 * (برای مواقعی که می‌خواهیم همان آرایه‌ی قبلی را بدون تغییر استفاده کنیم)
 */
if (!function_exists('answerCbOnceParams')) {
    function answerCbOnceParams(array $params): void {
        if (!isset($GLOBALS['__cb_answered'])) {
            $GLOBALS['__cb_answered'] = false;
        }
        if ($GLOBALS['__cb_answered']) return;
        tg('answerCallbackQuery', $params);
        $GLOBALS['__cb_answered'] = true;
    }
}


/**
 * v27.41: Some broadcast messages are sent as photo+caption. Telegram cannot
 * edit a photo/caption message with editMessageText. For broadcast inline
 * callbacks like BUY and SUPPORT, first try to edit when the source message is
 * a normal text message; if Telegram rejects it, send a fresh message with the
 * correct keyboard so the button always works.
 */
if (!function_exists('gemaccEditTextOrSendFromCallback')) {
    function gemaccEditTextOrSendFromCallback(int $chatId, ?int $msgId, array $payload) {
        $base = $payload;
        $base['chat_id'] = $chatId;

        if ($msgId) {
            $edit = $base;
            $edit['message_id'] = $msgId;
            $res = tgSendSafe('editMessageText', $edit);
            if (is_array($res) && !empty($res['ok'])) {
                return $res;
            }
        }

        unset($base['message_id']);
        return tgSendSafe('sendMessage', $base);
    }
}

function ensureUser(array $u): void {
    global $pdo;
    
    // بررسی آیا کاربر جدید است
    $checkStmt = $pdo->prepare("SELECT id, balance FROM users WHERE id = ?");
    $checkStmt->execute([$u['id']]);
    $existingUser = $checkStmt->fetch(PDO::FETCH_ASSOC);
    $isNewUser = !$existingUser;
    
    $stmt = $pdo->prepare("
        INSERT INTO users (id, username, first_name, last_name, is_admin, balance)
        VALUES (:id, :un, :fn, :ln, :is_admin, :balance)
        ON DUPLICATE KEY UPDATE 
            username = VALUES(username), 
            first_name = VALUES(first_name), 
            last_name = VALUES(last_name), 
            is_admin = VALUES(is_admin)
    ");
    $stmt->execute([
        ':id'       => $u['id'],
        ':un'       => $u['username'] ?? null,
        ':fn'       => $u['first_name'] ?? null,
        ':ln'       => $u['last_name'] ?? null,
        ':is_admin' => isAdmin($u['id']) ? 1 : 0,
        ':balance'  => $isNewUser ? 15000 : 0,
    ]);
    
    // اگر کاربر جدید است، پیام هدیه ارسال کن
    if ($isNewUser) {
        // ثبت تراکنش هدیه
        $stmt = $pdo->prepare("
            INSERT INTO wallet_transactions (user_id, amount, type, status, description)
            VALUES (?, 15000, 'ADMIN', 'COMPLETED', 'هدیه ثبت‌نام')
        ");
        $stmt->execute([$u['id']]);
        
        $firstName = $u['first_name'] ?? 'کاربر';
        
        $welcomeMsg = "🎉 **به فروشگاه GEM ACC خوش آمدید!** 🎉\n\n";
        $welcomeMsg .= "سلام {$firstName} عزیز 👋\n\n";
        $welcomeMsg .= "━━━━━━━━━━━━━━━━━━━━\n";
        $welcomeMsg .= "🎁 **هدیه ویژه ثبت‌نام**\n";
        $welcomeMsg .= "💰 مبلغ **15,000 تومان** به کیف پول شما اضافه شد.\n";
        $welcomeMsg .= "━━━━━━━━━━━━━━━━━━━━\n\n";
        $welcomeMsg .= "🛒 در **GEM ACC** می‌توانید سرویس‌ها و اکانت‌های دیجیتال معتبر تهیه کنید:\n";
        $welcomeMsg .= "🌐 OpenVPN | L2TP\n";
        $welcomeMsg .= "🌀 Windscribe Original\n";
        $welcomeMsg .= "💎 سرویس‌ها و محصولات جدید که به‌زودی اضافه می‌شوند\n\n";
        $welcomeMsg .= "✨ با موجودی هدیه می‌توانید:\n";
        $welcomeMsg .= "• بخشی از هزینه خرید خود را پرداخت کنید\n";
        $welcomeMsg .= "• سرویس مناسب خود را انتخاب کنید\n";
        $welcomeMsg .= "• سفارش‌ها و اکانت‌هایتان را از بخش «سرویس‌های من» مدیریت کنید\n\n";
        $welcomeMsg .= "🚀 برای شروع از منوی زیر استفاده کنید:\n";
        $welcomeMsg .= "🛍 خرید سرویس | 💎 کیف پول | 🎓 آموزش | ❓ سوالات متداول";
        
        tg('sendMessage', [
            'chat_id'    => $u['id'],
            'text'       => $welcomeMsg,
            'parse_mode' => 'Markdown',
            'reply_markup' => json_encode(gemaccReplyKeyboard(isAdmin($u['id'])), JSON_UNESCAPED_UNICODE),
        ]);
        
        // لاگ برای ادمین
        logError("New user joined with 15K bonus: {$u['id']} - " . ($u['first_name'] ?? 'Unknown'));
    }
}

function checkMembership($channelUsername, $userId): bool {
    // اطمینان از فرمت صحیح username
    $channelUsername = '@' . ltrim(trim($channelUsername), '@');
    
    $result = tg('getChatMember', [
        'chat_id' => $channelUsername,
        'user_id' => $userId,
    ]);
    
    // اگر ربات به کانال دسترسی ندارد، عبور بده
    if (!$result || !isset($result['ok']) || !$result['ok']) {
        error_log("Force join check failed for {$channelUsername}: " . json_encode($result));
        return true;
    }
    
    if (!isset($result['result']['status'])) {
        return true;
    }
    
    $status = $result['result']['status'];
    // left و kicked یعنی عضو نیست
    return in_array($status, ['member', 'administrator', 'creator', 'restricted']);
}


function gemaccReplyKeyboard(bool $isAdmin = false): array {
    $rows = [
        [['text' => '🛒 خرید فیلترشکن'], ['text' => '📡 فیلترشکن من']],
        [['text' => '👤 حساب کاربری'], ['text' => '📚 آموزش']],
        [['text' => '❓ سوالات متداول'], ['text' => '📱 پشتیبانی']],
        [['text' => '❓ درباره ما']],
    ];
    if ($isAdmin) {
        $rows[] = [['text' => '👑 پنل مدیریت']];
    }
    return [
        'keyboard' => $rows,
        // Keep the reply keyboard compact and collapsible.
        // Do not set is_persistent=true; Telegram clients can then hide/show it from the Menu button beside the input box.
        'resize_keyboard' => true,
        'one_time_keyboard' => false,
        'input_field_placeholder' => 'برای نمایش/مخفی کردن منو از دکمه Menu استفاده کنید...',
    ];
}

function sendMainMenu($chatId, $text = null): void {
    global $wallet, $orders;
    
    $balance = $wallet->getBalance($chatId);
    $servicesCount = $orders->getUserDeliveriesCount($chatId);
    
    if (!$text) {
        // دریافت پیام خوش‌آمد از تنظیمات
        $welcomeMsg = getSetting('welcome_message', '');
        
        $text = "🌐 **" . SHOP_NAME . "**\n\n";
        if ($welcomeMsg) {
            $text .= $welcomeMsg . "\n\n";
        } else {
            $text .= "به فروشگاه GEM ACC خوش آمدید؛ مرکز خرید سرویس‌های دیجیتال، VPN و اکانت‌های اختصاصی.\n\n";
        }
        $text .= "💰 موجودی: " . money($balance) . "\n";
        $text .= "📦 سرویس‌های فعال: {$servicesCount} عدد\n\n";
        $text .= "📲 از منوی زیر گزینه مورد نظر را انتخاب کنید:";
    }
    
    $keyboard = gemaccReplyKeyboard(isAdmin($chatId));

    tg('sendMessage', [
        'chat_id'      => $chatId,
        'text'         => $text,
        'parse_mode'   => 'Markdown',
        'reply_markup' => json_encode($keyboard, JSON_UNESCAPED_UNICODE),
    ]);
}



function gemaccMd(string $text): string {
    // Telegram Markdown v1: escape chars that frequently break parse_mode in usernames/names.
    return str_replace(['\\','_','*','`','['], ['\\\\','\\_','\\*','\\`','\\['], $text);
}

function gemaccUserStars(int $successfulPurchases): string {
    $stars = max(1, min(5, 1 + intdiv($successfulPurchases, 3)));
    return str_repeat('⭐', $stars);
}

function gemaccAccountText(int $chatId): string {
    global $pdo, $wallet;
    $st=$pdo->prepare("SELECT id,username,first_name,last_name,balance FROM users WHERE id=?");
    $st->execute([$chatId]);
    $u=$st->fetch(PDO::FETCH_ASSOC) ?: ['id'=>$chatId,'balance'=>0];
    $cntSt=$pdo->prepare("SELECT COUNT(*) FROM orders WHERE user_id=? AND status='DELIVERED'");
    $cntSt->execute([$chatId]);
    $success=(int)$cntSt->fetchColumn();
    $name=trim(($u['first_name'] ?? '').' '.($u['last_name'] ?? '')) ?: 'کاربر';
    $username=!empty($u['username']) ? '@'.$u['username'] : 'ثبت نشده';
    $safeName=function_exists('gemaccMd') ? gemaccMd($name) : $name;
    $safeUsername=function_exists('gemaccMd') ? gemaccMd($username) : $username;
    $txt="👤 **حساب کاربری شما**\n\n";
    $txt.="نام کاربری: {$safeName}\n";
    $txt.="آیدی عددی: `{$chatId}`\n";
    $txt.="آیدی تلگرام: {$safeUsername}\n";
    $txt.="موجودی: ".money((int)($u['balance'] ?? $wallet->getBalance($chatId)))."\n";
    $txt.="تعداد خریدهای موفق: {$success}\n";
    $txt.="امتیاز کاربر: ".gemaccUserStars($success)."\n\n";
    $txt.="از این بخش می‌توانید موجودی، تراکنش‌ها و کدهای تخفیف خود را مدیریت کنید.";
    return $txt;
}

function gemaccWalletAmountKeyboard(string $prefix, string $back='ADD_BALANCE'): array {
    return ['inline_keyboard'=>[
        [['text'=>'۵۰۰ هزار تومان','callback_data'=>$prefix.'_500000'], ['text'=>'۱ میلیون تومان','callback_data'=>$prefix.'_1000000']],
        [['text'=>'۲ میلیون تومان','callback_data'=>$prefix.'_2000000'], ['text'=>'۳ میلیون تومان','callback_data'=>$prefix.'_3000000']],
        [['text'=>'✍️ مبلغ دلخواه','callback_data'=>$prefix.'_CUSTOM']],
        [['text'=>'◀️ بازگشت','callback_data'=>$back]],
    ]];
}

/**
 * v27.53 (bug #4): create a PENDING wallet top-up transaction and send a
 * Telegram Stars invoice for it, directly inside the bot chat.
 * $cb may be null when triggered from a plain text message (custom amount).
 * The invoice is created FIRST-then-committed: if Telegram refuses, the PENDING
 * row is rolled back so nothing orphan remains.
 */
function gemaccBotStartStarsWalletTopup(int $chatId, int $amount, int $msgId = 0, $cb = null): void {
    global $pdo;
    $reply = function(string $text, bool $alert=false) use ($cb, $chatId) {
        if ($cb !== null && function_exists('answerCbOnce')) { answerCbOnce($cb, $text, $alert); }
        else { tg('sendMessage', ['chat_id'=>$chatId, 'text'=>$text]); }
    };
    if (getSetting('stars_enabled','0') !== '1') { $reply('⭐ پرداخت با استارز فعلاً غیرفعال است.', true); return; }
    if ($amount < 10000) { $reply('حداقل مبلغ شارژ ۱۰,۰۰۰ تومان است.', true); return; }
    if ($amount > 500000000) { $reply('مبلغ شارژ بیش از حد مجاز است.', true); return; }

    $rate = max(1,(int)getSetting('stars_rate_toman','7000'));
    $starsAmount = max(1,(int)ceil($amount / $rate));

    try {
        $pdo->beginTransaction();
        $meta = json_encode(['amount'=>$amount,'stars'=>$starsAmount,'payment'=>'stars','kind'=>'wallet_topup','source'=>'bot'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
        $pdo->prepare("INSERT INTO wallet_transactions(user_id,amount,type,status,payment_method,currency,description,receipt_text) VALUES(?,?,'DEPOSIT','PENDING','stars','XTR',?,?)")
            ->execute([$chatId, $amount, 'شارژ کیف پول با استارز تلگرام', $meta]);
        $txId = (int)$pdo->lastInsertId();

        $invResp = tg('sendInvoice', [
            'chat_id'     => $chatId,
            'title'       => mb_substr('شارژ کیف پول',0,32),
            'description' => mb_substr('افزایش موجودی به مبلغ '.number_format($amount).' تومان',0,255),
            'payload'     => 'STARS_WALLET_'.$txId,
            'currency'    => 'XTR',
            'prices'      => [['label'=>'شارژ کیف پول','amount'=>$starsAmount]],
        ]);
        $okInv = is_array($invResp) ? (bool)($invResp['ok'] ?? false) : false;
        if (!$okInv) {
            $pdo->rollBack();
            $errd = is_array($invResp) ? ($invResp['description'] ?? '') : '';
            $reply('❌ ساخت فاکتور استارز ناموفق بود'.($errd?": {$errd}":'.'), true);
            return;
        }
        $pdo->commit();
        if (function_exists('clearUserMode')) clearUserMode($chatId);
        $reply("⭐ فاکتور شارژ ({$starsAmount}⭐) ارسال شد. در همین چت پرداخت را تأیید کنید.");
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        error_log('[bot stars wallet topup] '.$e->getMessage());
        $reply('❌ خطا در ساخت فاکتور استارز. بعداً تلاش کنید.', true);
    }
}




function gemaccBotPaymentToleranceToman(array $plan): int {
    $traffic = strtolower((string)($plan['traffic_type'] ?? $plan['category_traffic_type'] ?? 'unlimited'));
    $key = $traffic === 'limited' ? 'volume_payment_tolerance_toman' : 'default_payment_tolerance_toman';
    $fallback = $traffic === 'limited' ? 30000 : 50000;
    $value = (int)getSetting($key, $fallback);
    return max(0, min(5000000, $value));
}

function gemaccCardPayableAmount(int $amount, int $userId = 0, string $salt = '', int $tolerance = 50000): int {
    // Card-to-card anti-fake amount: always ABOVE the invoice amount.
    // Stored/order price is in toman, while the user-facing exact amount is shown in rial.
    if ($amount <= 0) return 0;
    $maxAdd = max(0, min(5000000, $tolerance));
    if ($maxAdd <= 0) return $amount;
    $seed = sprintf('%u', crc32($userId . '|' . $salt . '|' . date('YmdHi')));
    $add = 1 + ((int)$seed % max(1, $maxAdd));
    return $amount + $add;
}

function gemaccCardPayNotice(int $payable, int $original): string {
    $rial = $payable * 10;
    $txt = "💰 مبلغ دقیق قابل واریز: `" . number_format($payable) . " تومان`\n";
    $txt .= "🔢 مبلغ قابل کپی به ریال: `" . $rial . "`\n";
    if ($payable !== $original) {
        $txt .= "ℹ️ برای جلوگیری از فیش‌های تکراری/فیک، مبلغ نهایی کمی بیشتر و کاملاً یکتا ساخته شده است.\n";
    }
    $txt .= "⚠️ لطفاً دقیقاً همین مبلغ را کپی و واریز کنید. برای کیف پول و ارز دیجیتال تلورانس اعمال نمی‌شود.\n";
    return $txt;
}

function gemaccAvailableDiscountCodes(int $userId, int $planId = 0, int $limit = 5): array {
    global $pdo;
    try {
        $sql = "SELECT dc.code, dc.type, dc.value, dc.min_purchase, dc.max_discount, dc.payment_scope,
                       COALESCE(udc.title,'کد تخفیف جشنواره‌ای') AS title,
                       COALESCE(NULLIF(udc.message,''), dc.broadcast_message, '') AS message
                FROM discount_codes dc
                LEFT JOIN user_discount_codes udc ON udc.code=dc.code AND udc.user_id=? AND udc.is_active=1
                WHERE dc.is_active=1
                  AND (dc.valid_from IS NULL OR dc.valid_from <= NOW())
                  AND (dc.valid_until IS NULL OR dc.valid_until >= NOW())
                  AND (dc.max_uses IS NULL OR dc.used_count < dc.max_uses)
                  AND (SELECT COUNT(*) FROM discount_usages du WHERE du.discount_id=dc.id AND du.user_id=?) < GREATEST(1, dc.max_uses_per_user)
                ORDER BY udc.id IS NULL ASC, dc.created_at DESC
                LIMIT " . max(1, min(10, $limit));
        $st=$pdo->prepare($sql); $st->execute([$userId,$userId]);
        return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
    } catch (Throwable $e) { return []; }
}


function gemaccDiscountValueLabel(array $c): string {
    return strtoupper((string)($c['type'] ?? '')) === 'PERCENT' ? ((int)($c['value'] ?? 0) . '٪') : money((int)($c['value'] ?? 0));
}
function gemaccDiscountScopeLabel(array $c): string {
    $s = strtolower((string)($c['payment_scope'] ?? 'all'));
    if ($s === 'card') return 'کارت به کارت';
    if ($s === 'crypto') return 'ارز دیجیتال';
    return 'کارت به کارت و ارز دیجیتال';
}
function gemaccDiscountButtonSummary(array $codes): string {
    if (!$codes) return 'کد تخفیف';
    $labels = [];
    foreach (array_slice($codes, 0, 2) as $c) $labels[] = gemaccDiscountValueLabel($c);
    return 'کد تخفیف (' . implode(' یا ', $labels) . ')';
}

function gemaccDiscountHelpText(int $userId, int $planId = 0): string {
    $codes = gemaccAvailableDiscountCodes($userId, $planId, 6);
    if (!$codes) return '';
    $txt = "\n🎫 کدهای تخفیف قابل استفاده شما:\n";
    foreach ($codes as $c) {
        $label = strtoupper((string)$c['code']);
        $value = gemaccDiscountValueLabel($c);
        $scope = gemaccDiscountScopeLabel($c);
        $msg = trim((string)($c['message'] ?? $c['title'] ?? ''));
        $txt .= "• `{$label}` ({$value}) | {$scope}\n";
        if ($msg !== '') $txt .= "  └ {$msg}\n";
    }
    $txt .= "برای کپی، روی کد نگه دارید یا دکمه تخفیف را بزنید.\n";
    return $txt;
}

function gemaccCryptoEnabledAssets(): array {
    $raw = strtoupper(trim((string)getSetting('crypto_enabled_assets','USDT,TRX')));
    $raw = str_replace([' ', '،', ';', '|'], [',', ',', ',', ','], $raw);
    $parts = array_values(array_unique(array_filter(array_map('trim', explode(',', $raw)))));
    $out=[];
    foreach($parts as $p){
        if(in_array($p,['USDT','TETHER','USDT_TRX','USDT_BSC'],true) && !in_array('USDT',$out,true)) $out[]='USDT';
        if(in_array($p,['TRX','TRON'],true) && !in_array('TRX',$out,true)) $out[]='TRX';
    }
    return $out ?: ['USDT','TRX'];
}
function gemaccCryptoNormalizeAsset(string $asset): string {
    $a=strtoupper(trim($asset));
    if(in_array($a,['USDT','TETHER','USDT_TRX','USDT_BSC'],true)) return 'USDT';
    if(in_array($a,['TRX','TRON'],true)) return 'TRX';
    return '';
}
function gemaccCryptoAssetLabel(string $asset): string { return gemaccCryptoNormalizeAsset($asset)==='TRX' ? 'ترون TRX' : 'تتر USDT'; }
function gemaccManualCryptoConfig(): array {
    return [
        'enabled_assets' => gemaccCryptoEnabledAssets(),
        'usdt_address' => trim((string)getSetting('manual_crypto_usdt_address','')),
        'trx_address' => trim((string)getSetting('manual_crypto_trx_address','')),
        'usdt_network' => trim((string)getSetting('manual_crypto_usdt_network','TRON')) ?: 'TRON',
        'trx_network' => trim((string)getSetting('manual_crypto_trx_network','TRON')) ?: 'TRON',
        'help' => trim((string)getSetting('manual_crypto_receipt_help','بعد از واریز، عکس رسید یا هش تراکنش را ارسال کنید.')),
    ];
}
function gemaccCryptoWalletForAsset(string $asset): array {
    $cfg=gemaccManualCryptoConfig(); $asset=gemaccCryptoNormalizeAsset($asset) ?: 'USDT';
    if(!in_array($asset,$cfg['enabled_assets'],true)) throw new RuntimeException('این ارز در تنظیمات فعال نیست.');
    if($asset==='TRX') return ['asset'=>'TRX','label'=>'ترون TRX','address'=>$cfg['trx_address'],'network'=>$cfg['trx_network']];
    return ['asset'=>'USDT','label'=>'تتر USDT','address'=>$cfg['usdt_address'],'network'=>$cfg['usdt_network']];
}

function gemaccCryptoHttpJson(string $url, int $timeout=6): ?array {
    $ch = curl_init($url);
    curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>$timeout,CURLOPT_CONNECTTIMEOUT=>4,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_HTTPHEADER=>['Accept: application/json']]);
    $body = curl_exec($ch); $code=(int)curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
    if($code<200 || $code>=300 || !$body) return null;
    $j=json_decode($body,true); return is_array($j)?$j:null;
}
function gemaccCryptoRate(string $symbol): ?float {
    $j=gemaccCryptoHttpJson('https://api.nobitex.ir/v3/orderbook/'.rawurlencode($symbol)); if(!$j) return null;
    foreach(['lastTradePrice','last','lastPrice'] as $k){ if(isset($j[$k]) && is_numeric($j[$k]) && (float)$j[$k]>0) return (float)$j[$k]; }
    if(isset($j['asks'][0][0]) && is_numeric($j['asks'][0][0]) && (float)$j['asks'][0][0]>0) return (float)$j['asks'][0][0];
    return null;
}
function gemaccCryptoQuoteText(int $amount, string $asset=''): string {
    $asset = gemaccCryptoNormalizeAsset($asset);
    $usdt=(float)getSetting('manual_crypto_usdt_irt_rate','0'); $trx=(float)getSetting('manual_crypto_trx_irt_rate','0'); $live=false;
    if(getSetting('manual_crypto_live_rates_enabled','0') === '1'){
        $u=gemaccCryptoRate('USDTIRT'); $t=gemaccCryptoRate('TRXIRT');
        if($u && $u>0){$usdt=$u;$live=true;} if($t && $t>0){$trx=$t;$live=true;}
    }
    $fmt=function($v){ return rtrim(rtrim(number_format((float)$v,6,'.',''),'0'),'.'); };
    $txt='';
    if(($asset==='' || $asset==='USDT') && $usdt>0){ $txt .= "💵 مقدار دقیق قابل انتقال: `".$fmt($amount/$usdt)."` USDT
"; $txt .= "📌 نرخ تتر: `".number_format($usdt)."` تومان
"; }
    if(($asset==='' || $asset==='TRX') && $trx>0){ $txt .= "🔴 مقدار دقیق قابل انتقال: `".$fmt($amount/$trx)."` TRX
"; $txt .= "📌 نرخ ترون: `".number_format($trx)."` تومان
"; }
    if($txt!=='') $txt .= "📈 مبنا: ".($live?'نرخ لحظه‌ای فعال':'نرخ دستی تنظیم‌شده توسط مدیر')."

";
    return $txt;
}
function gemaccServiceButtonLabel($date, int $count): string {
    $ts = is_numeric($date) ? (int)$date : strtotime((string)$date); if(!$ts) $ts=time();
    $ampm = date('A',$ts)==='AM' ? 'ق.ظ' : 'ب.ظ';
    return jdate('d/m',$ts).'  '.date('g:i',$ts).$ampm.' | '.$count.' کاربر';
}

function gemaccManualCryptoText(int $amount, ?array $plan=null, string $asset=''): string {
    $cfg = gemaccManualCryptoConfig();
    $txt = "🪙 **پرداخت دستی ارز دیجیتال**\n\n";
    if ($plan) $txt .= "📦 پلن: " . ($plan['title'] ?? 'سرویس') . "\n";
    $txt .= "💰 مبلغ ریالی: " . money($amount) . "\n";
    $asset = gemaccCryptoNormalizeAsset($asset);
    $txt .= gemaccCryptoQuoteText($amount, $asset);
    if (($asset === '' || $asset === 'USDT') && in_array('USDT', $cfg['enabled_assets'], true) && $cfg['usdt_address'] !== '') $txt .= "💵 آدرس تتر USDT ({$cfg['usdt_network']}):\n`{$cfg['usdt_address']}`\n\n";
    if (($asset === '' || $asset === 'TRX') && in_array('TRX', $cfg['enabled_assets'], true) && $cfg['trx_address'] !== '') $txt .= "🔴 آدرس ترون TRX ({$cfg['trx_network']}):\n`{$cfg['trx_address']}`\n\n";
    if ($cfg['usdt_address'] === '' && $cfg['trx_address'] === '') $txt .= "⚠️ آدرس ولت هنوز توسط مدیر تنظیم نشده است.\n\n";
    $txt .= "📌 {$cfg['help']}\n\n";
    $txt .= "بعد از پرداخت، عکس رسید یا هش/TxID تراکنش را همینجا ارسال کنید.";
    return $txt;
}

function gemaccCryptoChoiceText(array $plan, int $amount): string {
    $txt = "₿ **پرداخت ارز دیجیتال**

";
    $txt .= "📦 پلن: " . ($plan['title'] ?? 'سرویس') . "
";
    $txt .= "💰 مبلغ سفارش: " . money($amount) . "

";
    $txt .= "اول ارز پرداخت را انتخاب کنید: **تتر USDT** یا **ترون TRX**.
";
    $txt .= "بعد مشخص کنید پرداخت فوری با درگاه می‌خواهید یا پرداخت دستی با ارسال رسید.

";
    $txt .= "⚡ درگاه: بعد از پرداخت موفق، سفارش خودکار تأیید و ارسال می‌شود.
";
    $txt .= "🧾 دستی: مقدار دقیق و آدرس ولت نمایش داده می‌شود و رسید توسط مدیر بررسی می‌شود.

";
    $txt .= gemaccCryptoQuoteText($amount);
    return $txt;
}
function gemaccSendGatewayLinkToUser(int $userId, int $orderId, string $url, int $amount): void {
    $txt = "⚡ **پرداخت فوری ارز دیجیتال**\n\n";
    $txt .= "سفارش شما در مرحله پرداخت درگاه است.\n";
    $txt .= "💰 مبلغ سفارش: " . money($amount) . "\n\n";
    $txt .= "اگر می‌خواهید این مبلغ را در درگاه پرداخت کنید، روی لینک زیر بزنید. بعد از پرداخت موفق، سفارش خودکار تأیید و ارسال می‌شود.";
    tg('sendMessage', ['chat_id'=>$userId, 'text'=>$txt, 'parse_mode'=>'Markdown', 'reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'⚡ ورود به درگاه پرداخت', 'url'=>$url]]]], JSON_UNESCAPED_UNICODE)]);
}

function gemaccUserIsBlocked(int $userId): bool {
    global $pdo; try { $st=$pdo->prepare("SELECT is_blocked FROM users WHERE id=?"); $st->execute([$userId]); return (int)$st->fetchColumn()===1; } catch(Throwable $e){ return false; }
}


function gemaccBotCurrentOrderState(int $chatId, int $planId, array $plan): array {
    global $pdo;
    $mode = getUserMode($chatId);
    $state = ['discount_code'=>'','discount_amount'=>0,'base_price'=>(int)($plan['price'] ?? 0),'final_price'=>(int)($plan['price'] ?? 0),'wallet_credit_used'=>0];
    if ($mode && in_array(($mode['mode'] ?? ''), ['BOT_ORDER_DISCOUNT_APPLIED','BOT_ORDER_WALLET_APPLIED','BOT_CRYPTO_CHOICE'], true) && (int)($mode['payload']['plan_id'] ?? 0)===$planId) {
        $state['discount_code'] = (string)($mode['payload']['discount_code'] ?? '');
        $state['discount_amount'] = (int)($mode['payload']['discount_amount'] ?? 0);
        $state['base_price'] = (int)($mode['payload']['base_price'] ?? $mode['payload']['original_final_price'] ?? $mode['payload']['final_price'] ?? $plan['price'] ?? 0);
        $state['wallet_credit_used'] = (int)($mode['payload']['wallet_credit_used'] ?? 0);
        $state['final_price'] = (int)($mode['payload']['final_price'] ?? max(0,$state['base_price']-$state['wallet_credit_used']));
    }
    return $state;
}
function gemaccBotDeductWalletCredit(PDO $pdo, int $userId, int $amount, string $description): int {
    $amount = max(0, $amount);
    if ($amount <= 0) return 0;
    $st=$pdo->prepare("SELECT balance FROM users WHERE id=? FOR UPDATE"); $st->execute([$userId]);
    $balance=max(0,(int)$st->fetchColumn());
    $use=min($balance,$amount);
    if($use>0){
        $pdo->prepare("UPDATE users SET balance=balance-? WHERE id=? AND balance>=?")->execute([$use,$userId,$use]);
        $pdo->prepare("INSERT INTO wallet_transactions (user_id, amount, type, status, description) VALUES (?, ?, 'PURCHASE', 'COMPLETED', ?)")->execute([$userId,$use,$description]);
    }
    return $use;
}

function gemaccShowBotPaymentOptions(int $chatId, int $msgId, array $plan, int $price, ?string $discountCode=null, int $discountAmount=0): void {
    global $wallet;
    $balance=$wallet->getBalance($chatId);
    $modeNow=getUserMode($chatId);
    $walletAlready = $modeNow && ($modeNow['mode'] ?? '')==='BOT_ORDER_WALLET_APPLIED' && (int)($modeNow['payload']['plan_id'] ?? 0)===(int)($plan['id'] ?? 0);
    $txt="📦 **جزئیات سفارش**\n\n";
    $txt.="🏷 پلن: {$plan['title']}\n";
    if (!empty($plan['category_name'])) $txt.="📅 دسته: {$plan['category_name']}\n";
    if ($discountAmount > 0) { $txt.="💵 قیمت اصلی: ".money((int)$plan['price'])."\n🎫 تخفیف: ".money($discountAmount).($discountCode?" (`{$discountCode}`)":"")."\n"; }
    $txt.="💳 مبلغ نهایی: ".money($price)."\n";
    if($walletAlready && (int)($modeNow['payload']['wallet_credit_used'] ?? 0)>0) $txt.="💎 کسر از کیف پول: ".money((int)$modeNow['payload']['wallet_credit_used'])."\n";
    $txt.="💰 موجودی شما: ".money($balance)."\n";
    $txt .= gemaccDiscountHelpText($chatId, (int)($plan['id'] ?? 0));
    $txt .= "\nروش پرداخت را انتخاب کنید:";
    $buttons=[];
    $codes = gemaccAvailableDiscountCodes($chatId, (int)($plan['id'] ?? 0), 5);
    if($balance >= $price) $buttons[]=[['text'=>'💳 پرداخت کامل از کیف پول','callback_data'=>"PAY_WALLET_{$plan['id']}"]];
    elseif($balance > 0 && !$walletAlready) $buttons[]=[['text'=>'💎 استفاده از موجودی کیف پول: '.money(min($balance, $price)),'callback_data'=>"PAY_USE_WALLET_{$plan['id']}"]];
    $buttons[]=[['text'=>'🎫 '.gemaccDiscountButtonSummary($codes),'callback_data'=>"BOT_DISCOUNT_{$plan['id']}"]];
    foreach($codes as $c){
        $codeLabel = strtoupper((string)$c['code']);
        $buttons[]=[['text'=>'🎟 استفاده از '.$codeLabel.' - '.gemaccDiscountValueLabel($c),'callback_data'=>"BOT_USE_DISCOUNT_{$plan['id']}_".$codeLabel]];
    }
    $buttons[]=[['text'=>'🏧 کارت به کارت','callback_data'=>"PAY_CARD_{$plan['id']}"]];
    $buttons[]=[['text'=>'🪙 پرداخت ارز دیجیتال','callback_data'=>"PAY_CRYPTO_{$plan['id']}"]];
    // v27.53: Telegram Stars button in the BOT itself (bug #1). Only when enabled.
    if (getSetting('stars_enabled','0') === '1') {
        $rate = max(1,(int)getSetting('stars_rate_toman','7000'));
        $starsAmt = max(1,(int)ceil($price / $rate));
        $buttons[]=[['text'=>"⭐ پرداخت با استارز تلگرام ({$starsAmt}⭐)",'callback_data'=>"PAY_STARS_{$plan['id']}"]];
    }
    $buttons[]=[['text'=>'◀️ بازگشت','callback_data'=>'BUY']];
    $method = $msgId ? 'editMessageText' : 'sendMessage';
    $payload=['chat_id'=>$chatId,'text'=>$txt,'parse_mode'=>'Markdown','reply_markup'=>json_encode(['inline_keyboard'=>$buttons],JSON_UNESCAPED_UNICODE)];
    if($msgId) $payload['message_id']=$msgId;
    tg($method,$payload);
}

// notifyAdmins is already defined in config.php

function buildDeliveryMessage(array $credential): string {
    $serviceType = gemaccServiceTypeFromHint((string)($credential['service_type'] ?? $credential['protocol'] ?? 'openvpn_l2tp'));
    if (isset($credential[0]) && is_array($credential[0])) {
        $first = $credential[0];
        $serviceType = gemaccServiceTypeFromHint((string)($first['service_type'] ?? $first['protocol'] ?? 'openvpn_l2tp'));
        if (in_array($serviceType, ['windscribe','expressvpn','v2ray'], true)) return buildDeliveryMessage($first);
        $txt = "✅ <b>سرویس‌های OpenVPN | L2TP شما فعال شد!</b>\n\n";
        foreach ($credential as $i => $c) {
            $u=$c['email'] ?? $c['username'] ?? '';
            $p=$c['password'] ?? '';
            $txt .= ($i+1) . ". 👤 <b>یوزرنیم:</b> " . tgCode($u) . "\n";
            $txt .= "   🔐 <b>پسورد:</b> " . tgCode($p) . "\n\n";
        }
        $txt .= "🌐 <b>سرور L2TP:</b> " . tgCode(L2TP_SERVER) . "\n📥 <b>کانفیگ OpenVPN:</b> " . tgHtmlEscape(OPENVPN_CONFIG_URL) . "\n\nبرای جزئیات از بخش فیلترشکن من استفاده کنید.";
        return $txt;
    }
    $username = $credential['email'] ?? $credential['username'] ?? '';
    $password = $credential['password'] ?? '';
    if ($serviceType === 'v2ray') {
        $configText = trim((string)($credential['config_text'] ?? ''));
        $duration = (string)($credential['duration'] ?? '1M');
        $trafficType = (string)($credential['traffic_type'] ?? 'unlimited');
        $trafficLabel = $trafficType === 'limited' ? ('حجمی '.(int)($credential['traffic_limit_gb'] ?? 0).' گیگ') : 'نامحدود';
        $text  = "✅ <b>کانفیگ V2Ray شما با موفقیت تحویل شد.</b>

";
        $text .= "⏳ <b>مدت اعتبار:</b> " . tgHtmlEscape(Plans::getDurationLabel($duration)) . "
";
        $text .= "📦 <b>نوع سرویس:</b> " . tgHtmlEscape($trafficLabel) . "

";
        $text .= "🔗 <b>متن کانفیگ:</b>
" . tgCode($configText !== '' ? $configText : 'کانفیگ ثبت نشده') . "

";
        $text .= "☝🏻 برای کپی کردن لینک بالا فقط کافیست آدرس لینک را یک بار لمس کنید!

";
        $text .= !empty($credential['qr_code_file_id']) || !empty($credential['qr_code_path']) ? "📷 QR Code در بالای همین پیام قرار دارد." : "ℹ️ برای این کانفیگ QR Code ثبت نشده است.";
        return $text;
    }
    if ($serviceType === 'windscribe') {
        return function_exists('gemaccWindscribeNoticeText') ? gemaccWindscribeNoticeText((string)$username, (string)$password) : ("🔹 Username:
" . tgCode($username) . "
🔹 Password:
" . tgCode($password));
    }
    if ($serviceType === 'expressvpn') {
        $seats = max(1, (int)($credential['seats_allocated'] ?? $credential['requested_seats'] ?? 1));
        $duration = (string)($credential['duration'] ?? '1M');
        $months = preg_match('/^(\d+)M$/', $duration, $m) ? (int)$m[1] : ($duration === '1Y' ? 12 : 1);
        $text  = "✅ <b>اشتراک ExpressVPN شما با موفقیت فعال شد</b>\n\n";
        $text .= "📦 <b>پلن:</b> " . tgHtmlEscape(Plans::getDurationLabel($duration)) . " | " . tgHtmlEscape($seats . ' کاربر') . "\n";
        $text .= "⏳ <b>اعتبار تقریبی:</b> " . tgHtmlEscape(($months * 30) . ' روز') . "\n\n";
        $text .= "━━━━━━━━━━━━━━━\n\n";
        $text .= "👤 <b>Username</b>\n" . tgCode($username) . "\n\n";
        $text .= "🔑 <b>Password</b>\n" . tgCode($password) . "\n\n";
        $text .= "📋 با لمس روی کادرها قابل کپی هستند.\n\n";
        $text .= "💡 برای آموزش نصب و اتصال ExpressVPN دکمه آموزش را بزنید.";
        return $text;
    }
    $text  = "✅ <b>سرویس OpenVPN | L2TP شما فعال شد!</b>\n\n";
    $text .= "━━━━━━━━━━━━━━━━━━━━\n";
    $text .= "👤 <b>یوزرنیم:</b>\n" . tgCode($username) . "\n\n";
    $text .= "🔐 <b>پسورد:</b>\n" . tgCode($password) . "\n\n";
    $text .= "🌐 <b>سرور L2TP:</b> " . tgCode(L2TP_SERVER) . "\n";
    $text .= "📥 <b>کانفیگ OpenVPN:</b> " . tgHtmlEscape(OPENVPN_CONFIG_URL) . "\n";
    $text .= "━━━━━━━━━━━━━━━━━━━━\n\n";
    $text .= "💡 برای آموزش کامل دکمه زیر را بزنید:";
    return $text;
}

function gemaccResolveV2RayPhoto(array $row): string {
    $photo = trim((string)($row['qr_code_file_id'] ?? ''));
    if ($photo !== '') return $photo;
    $path = trim((string)($row['qr_code_path'] ?? ''));
    if ($path === '') return '';
    $abs = dirname(__DIR__, 2) . '/' . ltrim($path, '/');
    return is_file($abs) ? $abs : '';
}

function gemaccSendV2RayCard(int $chatId, ?int $replaceMessageId, string $caption, array $replyMarkup, string $photo = ''): void {
    if ($photo !== '') {
        if ($replaceMessageId) {
            try { tg('deleteMessage', ['chat_id'=>$chatId, 'message_id'=>$replaceMessageId]); } catch (Throwable $e) {}
        }
        $res = tg('sendPhoto', [
            'chat_id' => $chatId,
            'photo' => $photo,
            'caption' => $caption,
            'parse_mode' => 'HTML',
            'reply_markup' => json_encode($replyMarkup, JSON_UNESCAPED_UNICODE),
        ]);
        if (is_array($res) && !empty($res['ok'])) return;
    }
    if ($replaceMessageId) {
        tgSendSafe('editMessageText', [
            'chat_id' => $chatId,
            'message_id' => $replaceMessageId,
            'text' => $caption,
            'parse_mode' => 'HTML',
            'reply_markup' => json_encode($replyMarkup, JSON_UNESCAPED_UNICODE),
        ]);
        return;
    }
    tg('sendMessage', [
        'chat_id' => $chatId,
        'text' => $caption,
        'parse_mode' => 'HTML',
        'reply_markup' => json_encode($replyMarkup, JSON_UNESCAPED_UNICODE),
    ]);
}

function kbDelivery(string $serviceType='openvpn_l2tp'): array {
    $serviceType = gemaccServiceTypeFromHint($serviceType);
    $rows = [];
    if ($serviceType === 'openvpn_l2tp') {
        $rows[] = [['text' => '📥 دانلود کانفیگ OpenVPN', 'url' => OPENVPN_CONFIG_URL]];
    }
    $rows[] = [['text' => '📚 آموزش ' . gemaccServiceShortLabel($serviceType), 'callback_data' => 'TUTORIAL_SERVICE_' . $serviceType]];
    return ['inline_keyboard' => $rows];
}

function kbMainMenu(): array {
    return [
        'inline_keyboard' => [
            [['text' => '◀️ منوی اصلی', 'callback_data' => 'BACK_MAIN']],
        ],
    ];
}


function gemaccServiceDetailText(array $d): string {
    $username = $d['email'] ?? $d['username'] ?? '';
    $password = $d['password'] ?? '';
    $durationLabel = isset($d['duration']) ? Plans::getDurationLabel($d['duration']) : 'OpenVPN | L2TP';
    $delivered = !empty($d['delivered_at']) ? jdate('d F H:i', strtotime($d['delivered_at'])) : '-';
    $trafficLabel = (($d['traffic_type'] ?? 'unlimited') === 'limited') ? 'حجمی' : 'نامحدود';
    $daysLeft = '-';
    if (!empty($d['expires_at'])) $daysLeft = max(0, (int)ceil((strtotime($d['expires_at'])-time())/86400)) . ' روز';
    $serviceType = gemaccServiceTypeFromHint((string)($d['service_type'] ?? $d['protocol'] ?? $d['order_service_type'] ?? 'openvpn_l2tp'));
    if ($serviceType === 'v2ray') {
        $configText = trim((string)($d['config_text'] ?? ''));
        $txt = "⚡ <b>جزئیات کانفیگ V2Ray</b>

";
        $txt .= "⏳ <b>مدت اعتبار:</b> " . tgHtmlEscape($durationLabel) . "
";
        $txt .= "📦 <b>نوع سرویس:</b> " . tgHtmlEscape($trafficLabel) . "
";
        $txt .= "📅 <b>تاریخ تحویل:</b> " . tgHtmlEscape($delivered) . "
";
        $txt .= "⏱ <b>باقی‌مانده:</b> " . tgHtmlEscape($daysLeft) . "

";
        $txt .= "🔗 <b>متن کانفیگ:</b>
" . tgCode($configText !== '' ? $configText : 'کانفیگ ثبت نشده') . "

";
        $txt .= "☝🏻 برای کپی کردن لینک بالا فقط کافیست آدرس لینک را یک بار لمس کنید!

";
        $txt .= !empty($d['qr_code_file_id']) || !empty($d['qr_code_path']) ? "📷 QR Code در بالای همین پیام قرار دارد." : "ℹ️ برای این کانفیگ QR Code ثبت نشده است.";
        return $txt;
    }
    if ($serviceType === 'windscribe') {
        $seats=max(1,(int)($d['seats_allocated'] ?? 0),(int)($d['requested_seats'] ?? 0));
        $months = preg_match('/^(\d+)M$/', (string)($d['duration'] ?? '1M'), $mm) ? (int)$mm[1] : 1;
        $txt = "🟦 <b>جزئیات سرویس Windscribe</b>\n\n";
        $txt .= "📦 <b>پلن:</b> " . tgHtmlEscape($durationLabel) . " | " . tgHtmlEscape($seats . ' کاربر') . "\n";
        $txt .= "⏳ <b>اعتبار:</b> " . tgHtmlEscape(($months*30) . ' روز') . "\n\n";
        $txt .= "👤 <b>Username</b>\n" . tgCode($username) . "\n\n";
        $txt .= "🔑 <b>Password</b>\n" . tgCode($password) . "\n\n";
        $txt .= "⚠️ <b>مهم:</b> Logout نکنید و فقط برای تعداد کاربر خریداری‌شده استفاده کنید. استفاده اضافه یا ورود مجدد غیرعادی ممکن است باعث Ban دائمی شود.";
        return $txt;
    }
    if ($serviceType === 'expressvpn') {
        $seats=max(1,(int)($d['seats_allocated'] ?? 0),(int)($d['requested_seats'] ?? 0));
        $txt = "🚀 <b>جزئیات سرویس ExpressVPN</b>\n\n";
        $txt .= "📦 <b>پلن:</b> " . tgHtmlEscape($durationLabel) . " | " . tgHtmlEscape($seats . ' کاربر') . "\n";
        $txt .= "👤 <b>Username</b>\n" . tgCode($username) . "\n\n";
        $txt .= "🔑 <b>Password</b>\n" . tgCode($password) . "\n\n";
        $txt .= "📅 <b>تاریخ خرید:</b> " . tgHtmlEscape($delivered) . "\n";
        $txt .= "⏱ <b>باقی‌مانده:</b> " . tgHtmlEscape($daysLeft) . "\n\n";
        $txt .= "💡 برای آموزش نصب و اتصال ExpressVPN از دکمه آموزش استفاده کنید.";
        return $txt;
    }
    $txt  = "🧊 <b>جزئیات سرویس OpenVPN | L2TP</b>\n\n";
    $txt .= "👤 <b>یوزرنیم:</b>\n" . tgCode($username) . "\n\n";
    $txt .= "🔐 <b>پسورد:</b>\n" . tgCode($password) . "\n\n";
    $txt .= "📅 <b>تاریخ خرید:</b> " . tgHtmlEscape($delivered) . "\n";
    $txt .= "📦 <b>نوع سرویس:</b> OpenVPN | L2TP - " . tgHtmlEscape($trafficLabel) . "\n";
    $txt .= "⏳ <b>مدت اعتبار:</b> " . tgHtmlEscape($durationLabel) . "\n";
    $txt .= "📦 <b>وضعیت:</b> تحویل‌شده و آماده استفاده\n";
    $txt .= "⏱ <b>باقی‌مانده:</b> " . tgHtmlEscape($daysLeft) . "\n\n";
    $txt .= "🌐 <b>سرور L2TP:</b> " . tgCode(L2TP_SERVER) . "\n";
    $txt .= "📥 <b>دانلود کانفیگ OpenVPN:</b> " . tgHtmlEscape(OPENVPN_CONFIG_URL);
    return $txt;
}
function gemaccShowMyServices($chatId, $msgId=null, int $page=1): void {
    global $pdo;
    $limit=10; $offset=($page-1)*$limit;
    $st=$pdo->prepare("SELECT o.id,o.requested_seats,o.delivered_at,o.created_at,o.service_type,COALESCE(MAX(NULLIF(d.service_type,'')),MAX(NULLIF(c.protocol,'')),o.service_type,'openvpn_l2tp') resolved_service_type,COUNT(d.id) cnt,COALESCE(SUM(NULLIF(d.seats_allocated,0)),0) total_seats,MIN(d.id) first_delivery_id FROM orders o JOIN deliveries d ON d.order_id=o.id LEFT JOIN credentials c ON c.id=d.credential_id WHERE o.user_id=? AND o.status='DELIVERED' AND d.status='delivered' AND ((COALESCE(NULLIF(d.username,''),NULLIF(c.username,''),NULLIF(c.email,'')) IS NOT NULL AND COALESCE(NULLIF(d.password,''),NULLIF(c.password,'')) IS NOT NULL) OR COALESCE(NULLIF(d.config_text,''),NULLIF(c.config_text,'')) IS NOT NULL) GROUP BY o.id,o.requested_seats,o.delivered_at,o.created_at,o.service_type ORDER BY COALESCE(o.delivered_at,o.created_at) DESC LIMIT ? OFFSET ?");
    $st->bindValue(1,$chatId,PDO::PARAM_INT); $st->bindValue(2,$limit,PDO::PARAM_INT); $st->bindValue(3,$offset,PDO::PARAM_INT); $st->execute();
    $ordersRows=$st->fetchAll(PDO::FETCH_ASSOC);
    $countSt=$pdo->prepare("SELECT COUNT(DISTINCT o.id) FROM orders o JOIN deliveries d ON d.order_id=o.id LEFT JOIN credentials c ON c.id=d.credential_id WHERE o.user_id=? AND o.status='DELIVERED' AND d.status='delivered' AND ((COALESCE(NULLIF(d.username,''),NULLIF(c.username,''),NULLIF(c.email,'')) IS NOT NULL AND COALESCE(NULLIF(d.password,''),NULLIF(c.password,'')) IS NOT NULL) OR COALESCE(NULLIF(d.config_text,''),NULLIF(c.config_text,'')) IS NOT NULL)"); $countSt->execute([$chatId]); $total=(int)$countSt->fetchColumn();
    if(!$ordersRows){
        $payload=['chat_id'=>$chatId,'text'=>'📦 شما هنوز سرویسی ندارید.','reply_markup'=>json_encode(kbMainMenu(),JSON_UNESCAPED_UNICODE)];
        if($msgId) $payload['message_id']=$msgId;
        tgSendSafe($msgId?'editMessageText':'sendMessage',$payload); return;
    }
    $buttons=[];
    foreach($ordersRows as $o){
        $svcType = gemaccServiceTypeFromHint((string)($o['resolved_service_type'] ?? ($o['service_type'] ?? 'openvpn_l2tp')));
        $displaySeats = max(1, (int)($o['requested_seats'] ?? 1), (int)($o['total_seats'] ?? 0), (int)($o['cnt'] ?? 0));
        $ts=strtotime($o['delivered_at'] ?: $o['created_at']); $date=gemaccServiceButtonLabel($ts, $displaySeats);
        $svc = gemaccServiceEmojiFromType($svcType) . ' ' . gemaccServiceShortLabel($svcType);
        // Windscribe is one shared account with N allowed users. Open it directly.
        // OpenVPN/L2TP can contain several dedicated accounts, so keep the account picker only there.
        if(in_array($svcType, ['windscribe','expressvpn','v2ray'], true) || (int)$o['cnt']===1){
            $buttons[]=[['text'=>"{$svc} | {$date}",'callback_data'=>'MY_SERVICE_DETAIL_'.$o['first_delivery_id']]];
        } else {
            $buttons[]=[['text'=>"{$svc} | {$date}",'callback_data'=>'MY_ORDER_SERVICES_'.$o['id']]];
        }
    }
    $nav=[]; if($page>1)$nav[]=['text'=>'⏪ قبلی','callback_data'=>'MY_SERVICES_PAGE_'.($page-1)]; if($page*$limit<$total)$nav[]=['text'=>'بعدی ⏩','callback_data'=>'MY_SERVICES_PAGE_'.($page+1)]; if($nav)$buttons[]=$nav;
    $buttons[]=[['text'=>'◀️ بازگشت','callback_data'=>'BACK_MAIN']];
    $payload=['chat_id'=>$chatId,'text'=>"📦 *سرویس‌های من*\n\nروی هر دکمه بزنید تا جزئیات سرویس نمایش داده شود.",'parse_mode'=>'Markdown','reply_markup'=>json_encode(['inline_keyboard'=>$buttons],JSON_UNESCAPED_UNICODE)];
    if($msgId){$payload['message_id']=$msgId; tgSendSafe('editMessageText',$payload);} else tgSendSafe('sendMessage',$payload);
}
function gemaccShowOrderServices($chatId,$msgId,int $orderId): void {
    global $pdo;
    $st=$pdo->prepare("SELECT d.id,COALESCE(NULLIF(d.username,''),NULLIF(c.username,''),NULLIF(c.email,'')) email,COALESCE(NULLIF(d.password,''),NULLIF(c.password,'')) password,COALESCE(NULLIF(d.config_text,''),NULLIF(c.config_text,'')) config_text,d.delivered_at,COALESCE(d.service_type,c.protocol,o.service_type,'openvpn_l2tp') service_type FROM deliveries d LEFT JOIN credentials c ON c.id=d.credential_id JOIN orders o ON o.id=d.order_id WHERE d.user_id=? AND d.order_id=? AND d.status='delivered' ORDER BY d.id ASC");
    $st->execute([$chatId,$orderId]); $rows=$st->fetchAll(PDO::FETCH_ASSOC);
    $buttons=[];
    foreach($rows as $r){
        $type=gemaccServiceTypeFromHint((string)($r['service_type'] ?? 'openvpn_l2tp'));
        $svc=gemaccServiceEmojiFromType($type).' '.gemaccServiceShortLabel($type);
        $label=$type==='v2ray'?($svc.' | کانفیگ #'.(int)$r['id']):($svc.' | '.$r['email'].'|'.$r['password']);
        $buttons[]=[['text'=>$label,'callback_data'=>'MY_SERVICE_DETAIL_'.$r['id']]];
    }
    $buttons[]=[['text'=>'◀️ بازگشت به فیلترشکن من','callback_data'=>'MY_SERVICES_PAGE_1']];
    tgSendSafe('editMessageText',['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>'📦 سرویس‌های این سفارش را انتخاب کنید:','reply_markup'=>json_encode(['inline_keyboard'=>$buttons],JSON_UNESCAPED_UNICODE)]);
}
function gemaccShowServiceDetail($chatId,$msgId,int $deliveryId): void {
    global $pdo;
    $st=$pdo->prepare("SELECT d.*,COALESCE(NULLIF(d.username,''),NULLIF(c.username,''),NULLIF(c.email,'')) email,COALESCE(NULLIF(d.password,''),NULLIF(c.password,'')) password,COALESCE(NULLIF(d.config_text,''),NULLIF(c.config_text,'')) config_text,COALESCE(NULLIF(d.qr_code_file_id,''),NULLIF(c.qr_code_file_id,'')) qr_code_file_id,COALESCE(NULLIF(d.qr_code_path,''),NULLIF(c.qr_code_path,'')) qr_code_path,COALESCE(c.duration,o.duration) duration,COALESCE(c.traffic_type,o.traffic_type,'unlimited') traffic_type,COALESCE(c.traffic_limit_gb,o.traffic_limit_gb) traffic_limit_gb,c.first_connected_at,c.expires_at,o.requested_seats,o.service_type AS order_service_type,d.service_type,c.protocol,o.created_at order_date FROM deliveries d LEFT JOIN credentials c ON c.id=d.credential_id JOIN orders o ON o.id=d.order_id WHERE d.user_id=? AND d.id=? AND d.status='delivered' LIMIT 1");
    $st->execute([$chatId,$deliveryId]); $d=$st->fetch(PDO::FETCH_ASSOC);
    if(!$d){ tg('answerCallbackQuery',['callback_query_id'=>$GLOBALS['cb']['id']??'', 'text'=>'سرویس یافت نشد','show_alert'=>true]); return; }
    $svcType = gemaccServiceTypeFromHint((string)($d['service_type'] ?: $d['order_service_type'] ?: ($d['protocol'] ?? 'openvpn_l2tp')));
    $rows = [];
    if ($svcType === 'openvpn_l2tp') $rows[] = [['text'=>'📥 دانلود کانفیگ OpenVPN','url'=>OPENVPN_CONFIG_URL]];
    $rows[] = [['text'=>'📚 آموزش '.gemaccServiceShortLabel($svcType),'callback_data'=>'TUTORIAL_SERVICE_'.$svcType]];
    $backTarget = in_array($svcType, ['windscribe','expressvpn','v2ray'], true) ? 'MY_SERVICES_PAGE_1' : (((int)$d['requested_seats']>1) ? 'MY_ORDER_SERVICES_'.$d['order_id'] : 'MY_SERVICES_PAGE_1');
    $rows[] = [['text'=>'◀️ بازگشت','callback_data'=>$backTarget]];
    if($svcType==='v2ray'){
        gemaccSendV2RayCard((int)$chatId, $msgId ? (int)$msgId : null, gemaccServiceDetailText($d), ['inline_keyboard'=>$rows], gemaccResolveV2RayPhoto($d));
    } else {
        tgSendSafe('editMessageText',['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>gemaccServiceDetailText($d),'parse_mode'=>'HTML','reply_markup'=>json_encode(['inline_keyboard'=>$rows],JSON_UNESCAPED_UNICODE)]);
    }
}



function gemaccUserHasDeliveredService(PDO $pdo, int $userId, string $serviceType): bool {
    $serviceType = gemaccServiceTypeFromHint($serviceType);
    try {
        $st = $pdo->prepare("SELECT COUNT(*) FROM deliveries d JOIN orders o ON o.id=d.order_id WHERE d.user_id=? AND d.status='delivered' AND o.status='DELIVERED' AND LOWER(COALESCE(d.service_type,o.service_type,'openvpn_l2tp'))=?");
        $st->execute([$userId, $serviceType]);
        return (int)$st->fetchColumn() > 0;
    } catch (Throwable $e) { return false; }
}

function gemaccEnsureFaqTable(): void {
    global $pdo;
    try {
        $pdo->exec("CREATE TABLE IF NOT EXISTS faq_items (id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, question VARCHAR(255) NOT NULL, answer MEDIUMTEXT NOT NULL, is_active TINYINT(1) NOT NULL DEFAULT 1, display_order INT NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_faq_active_order (is_active, display_order, id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
    } catch (Throwable $e) { error_log('FAQ table error: '.$e->getMessage()); }
}
function gemaccFaqList(bool $admin=false): array {
    global $pdo; gemaccEnsureFaqTable();
    $where = $admin ? '1=1' : 'is_active=1';
    $st=$pdo->query("SELECT * FROM faq_items WHERE {$where} ORDER BY display_order ASC, id ASC LIMIT 80");
    return $st ? $st->fetchAll(PDO::FETCH_ASSOC) : [];
}
function gemaccShowFaqList($chatId, $msgId=null, bool $admin=false): void {
    $items=gemaccFaqList($admin);
    $txt=$admin ? "❓ *مدیریت سوالات متداول*\n\nسوال و جواب‌های قابل نمایش برای کاربران را اضافه، ویرایش یا حذف کنید." : "❓ *سوالات متداول*\n\nیکی از سوال‌ها را انتخاب کنید:";
    $rows=[];
    if ($items) foreach($items as $it){
        $prefix = $admin ? ((int)$it['is_active'] ? '✅ ' : '🚫 ') : '▫️ ';
        $rows[]=[['text'=>$prefix . mb_substr($it['question'],0,55,'UTF-8'), 'callback_data'=>($admin?'ADMIN_FAQ_VIEW_':'FAQ_VIEW_').$it['id']]];
    } else {
        $txt .= "\n\nهنوز سوالی ثبت نشده است.";
    }
    if ($admin) $rows[]=[['text'=>'➕ افزودن سوال جدید','callback_data'=>'ADMIN_FAQ_ADD']];
    $rows[]=[['text'=>$admin?'◀️ بازگشت به پنل':'◀️ بازگشت','callback_data'=>$admin?'ADMIN':'BACK_MAIN']];
    $payload=['chat_id'=>$chatId,'text'=>$txt,'parse_mode'=>'Markdown','reply_markup'=>json_encode(['inline_keyboard'=>$rows],JSON_UNESCAPED_UNICODE)];
    if($msgId){$payload['message_id']=$msgId; tgSendSafe('editMessageText',$payload);} else tgSendSafe('sendMessage',$payload);
}
function gemaccShowFaqItem($chatId, $msgId, int $faqId, bool $admin=false): void {
    global $pdo; gemaccEnsureFaqTable();
    $st=$pdo->prepare("SELECT * FROM faq_items WHERE id=?" . ($admin ? '' : ' AND is_active=1'));
    $st->execute([$faqId]); $it=$st->fetch(PDO::FETCH_ASSOC);
    if(!$it){ tg('answerCallbackQuery',['callback_query_id'=>$GLOBALS['cb']['id']??'', 'text'=>'سوال یافت نشد','show_alert'=>true]); return; }
    $txt="❓ *".str_replace(['*','_','`'], '', $it['question'])."*\n\n".$it['answer'];
    $rows=[];
    if($admin){
        $rows[]=[['text'=>'✏️ ویرایش','callback_data'=>'ADMIN_FAQ_EDIT_'.$faqId],['text'=>((int)$it['is_active']?'🚫 غیرفعال':'✅ فعال'),'callback_data'=>'ADMIN_FAQ_TOGGLE_'.$faqId]];
        $rows[]=[['text'=>'🗑 حذف','callback_data'=>'ADMIN_FAQ_DELETE_'.$faqId]];
        $rows[]=[['text'=>'◀️ لیست سوالات','callback_data'=>'ADMIN_FAQ']];
    } else {
        $rows[]=[['text'=>'◀️ سوالات متداول','callback_data'=>'FAQ']];
    }
    tgSendSafe('editMessageText',['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>$txt,'parse_mode'=>'Markdown','reply_markup'=>json_encode(['inline_keyboard'=>$rows],JSON_UNESCAPED_UNICODE)]);
}

function kbAdminPanel(): array {
    return ['inline_keyboard' => [
        [['text'=>'🛒 فروش و سفارش‌ها','callback_data'=>'ADMIN_ORDERS'], ['text'=>'💳 پرداخت و واریزها','callback_data'=>'ADMIN_DEPOSITS']],
        [['text'=>'📦 موجودی و اشتراک‌ها','callback_data'=>'ADMIN_CREDENTIALS'], ['text'=>'➕ افزودن اشتراک','callback_data'=>'ADMIN_ADD_CREDENTIAL']],
        [['text'=>'💰 پلن‌ها و دسته‌بندی','callback_data'=>'ADMIN_PLANS'], ['text'=>'✅ تحویل‌های فروخته‌شده','callback_data'=>'ADMIN_SOLD_CREDENTIALS']],
        [['text'=>'💬 پشتیبانی و چت کاربران','callback_data'=>'ADMIN_CHATS']],
        [['text'=>'👥 کاربران','callback_data'=>'ADMIN_USERS'], ['text'=>'🤝 فروشندگان','callback_data'=>'ADMIN_RESELLERS']],
        [['text'=>'🎓 آموزش سرویس‌ها','callback_data'=>'ADMIN_TUTORIALS'], ['text'=>'🎫 تخفیف‌ها','callback_data'=>'ADMIN_DISCOUNTS']],
        [['text'=>'🎁 بونوس/کش‌بک','callback_data'=>'ADMIN_BONUS_SETTINGS'], ['text'=>'📣 پیام همگانی','callback_data'=>'ADMIN_BROADCAST']],
        [['text'=>'📊 گزارش فروش','callback_data'=>'ADMIN_STATS'], ['text'=>'⚙️ تنظیمات ربات','callback_data'=>'ADMIN_SETTINGS']],
        [['text'=>'📢 کانال‌های اجباری','callback_data'=>'ADMIN_CHANNELS'], ['text'=>'🧹 پاکسازی کش','callback_data'=>'ADMIN_CLEAR_CACHE']],
        [['text'=>'🏠 منوی کاربر','callback_data'=>'BACK_MAIN']],
    ]];
}


function gemaccCreateWalletCryptoInvoice(int $userId, int $amount): array {
    global $pdo;
    $minAmount = (int)getSetting('min_wallet_topup_amount', '10000');
    if ($amount < $minAmount) {
        return ['ok' => false, 'error' => 'حداقل مبلغ واریز ' . number_format($minAmount) . ' تومان است.'];
    }

    try {
        if (function_exists('plisio_enabled') && plisio_enabled()) {
            $pdo->beginTransaction();
            $txStmt = $pdo->prepare("INSERT INTO wallet_transactions(user_id, amount, type, status, payment_method, currency, description, receipt_text) VALUES(?, ?, 'DEPOSIT', 'PENDING', 'crypto', 'IRT', 'شارژ کیف پول با Plisio از ربات', 'Plisio')");
            $txStmt->execute([$userId, $amount]);
            $txId = (int)$pdo->lastInsertId();
            $raw = json_encode(['source'=>'bot_plisio_wallet','amount'=>$amount], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
            $cp = $pdo->prepare("INSERT INTO crypto_payments(user_id, wallet_transaction_id, amount, currency, provider, payment_type, status, raw_request) VALUES(?,?,?,?,?,?,?,?)");
            $cp->execute([$userId, $txId, $amount, 'IRT', 'Plisio', 'wallet_topup', 'PENDING', $raw]);
            $cryptoId = (int)$pdo->lastInsertId();
            $pdo->prepare("UPDATE wallet_transactions SET crypto_payment_id=? WHERE id=?")->execute([$cryptoId, $txId]);
            $invoice = plisio_create_invoice('WALLET-'.$txId, 'GEM ACC wallet topup #'.$txId, $amount, 'wallet_topup');
            $pdo->prepare("UPDATE crypto_payments SET provider_invoice_id=?, provider_order_id=?, payment_url=?, crypto_currency=?, raw_response=? WHERE id=?")->execute([$invoice['txn_id'], $invoice['txn_id'], $invoice['invoice_url'], $invoice['currency'], json_encode($invoice['raw'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES), $cryptoId]);
            $pdo->commit();
            return ['ok'=>true,'provider'=>'Plisio','transaction_id'=>$txId,'crypto_payment_id'=>$cryptoId,'invoice_id'=>$invoice['txn_id'],'provider_order_id'=>'WALLET-'.$txId,'payment_url'=>$invoice['invoice_url'],'wallet_address'=>'','amount'=>$invoice['crypto_amount'],'amount_unit'=>$invoice['currency'],'network'=>'Plisio','token'=>$invoice['currency'],'selected_asset'=>$asset,'invoice'=>$invoice['raw']];
        }

        if (!function_exists('sw_create_invoice')) {
            return ['ok' => false, 'error' => 'درگاه کریپتو فعال نیست؛ Plisio را فعال کنید یا پرداخت دستی ارز را استفاده کنید.'];
        }
        $pdo->beginTransaction();
        $txStmt = $pdo->prepare("INSERT INTO wallet_transactions(user_id, amount, type, status, payment_method, currency, description, receipt_text) VALUES(?, ?, 'DEPOSIT', 'PENDING', 'crypto', ?, 'شارژ کیف پول با ارز دیجیتال از ربات', 'SwapWallet')");
        $txStmt->execute([$userId, $amount, sw_amount_unit()]);
        $txId = (int)$pdo->lastInsertId();
        $tx = ['id' => $txId, 'user_id' => $userId, 'amount' => $amount];

        $payload = sw_make_wallet_invoice_payload($tx, $amount, $userId);
        $resp = sw_create_invoice($payload);
        $json = $resp['json'];
        $result = sw_extract_result($json);
        $providerInvoiceId = sw_extract_invoice_id($json);
        $paymentUrl = sw_extract_payment_url($json);
        $walletAddress = sw_extract_wallet_address($json);
        $cryptoAmount = sw_extract_crypto_amount($json);

        $cryptoStmt = $pdo->prepare("INSERT INTO crypto_payments(user_id, wallet_transaction_id, amount, currency, crypto_currency, network, wallet_address, provider, provider_invoice_id, provider_order_id, payment_url, payment_type, status, raw_request, raw_response) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
        $cryptoStmt->execute([$userId, $txId, $amount, sw_amount_unit(), $cryptoAmount['unit'] ?: sw_default_token(), sw_default_network(), $walletAddress, 'SwapWallet', $providerInvoiceId, $payload['orderId'], $paymentUrl, 'wallet_topup', 'PENDING', json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $resp['body']]);
        $cryptoId = (int)$pdo->lastInsertId();
        $pdo->prepare("UPDATE wallet_transactions SET crypto_payment_id=?, receipt_file_id=?, receipt_text=? WHERE id=?")->execute([$cryptoId, $paymentUrl, $resp['body'], $txId]);
        $pdo->commit();

        return [
            'ok' => true,
            'transaction_id' => $txId,
            'crypto_payment_id' => $cryptoId,
            'invoice_id' => $providerInvoiceId,
            'provider_order_id' => $payload['orderId'],
            'payment_url' => $paymentUrl,
            'wallet_address' => $walletAddress,
            'amount' => $cryptoAmount['number'],
            'amount_unit' => $cryptoAmount['unit'],
            'network' => sw_default_network(),
            'token' => sw_default_token(),
            'invoice' => $result,
        ];
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        if (function_exists('logError')) logError('[wallet crypto bot] ' . $e->getMessage());
        return ['ok' => false, 'error' => 'خطای پرداخت کریپتو: ' . $e->getMessage()];
    }
}


function gemaccCreateOrderCryptoInvoice(int $userId, array $plan, int $price, string $discountCode = '', int $discountAmount = 0, string $asset = '', int $walletCreditUsed = 0): array {
    global $pdo;
    if ($price <= 0) return ['ok' => false, 'error' => 'مبلغ سفارش نامعتبر است.'];
    $asset = gemaccCryptoNormalizeAsset($asset) ?: 'USDT';
    if (!in_array($asset, gemaccCryptoEnabledAssets(), true)) return ['ok'=>false,'error'=>'این ارز در تنظیمات فعال نیست.'];

    try {
        $pdo->beginTransaction();
        $actualWalletUsed = gemaccBotDeductWalletCredit($pdo, $userId, $walletCreditUsed, 'کسر موجودی کیف پول برای درگاه ارز دیجیتال ربات');
        if($actualWalletUsed>0){ $price=max(0,$price); }
        $months = max(1, (int)($plan['months'] ?? 1));
        $requestedSeats = max(1, (int)($plan['seats'] ?? $plan['user_count'] ?? 1));
        $duration = (string)($plan['duration'] ?? '1M');
        $meta = json_encode(['source'=>'bot','asset'=>$asset,'discount_code'=>$discountCode,'discount_amount'=>$discountAmount,'wallet_credit_used'=>$actualWalletUsed,'original_price'=>(int)($plan['price'] ?? $price),'final_price'=>$price], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
        $serviceType = gemaccServiceTypeFromHint((string)($plan['protocol'] ?? ($plan['service_type'] ?? 'openvpn_l2tp')), (string)($plan['title'] ?? ''));
        $stmt = $pdo->prepare("INSERT INTO orders(user_id, plan_id, category_id, plan_code, plan_title, service_type, duration, months, requested_seats, required_accounts, delivered_accounts, price, purchase_price, payment_method, payment_status, delivery_status, status, receipt_text) VALUES(?,?,?,?,?,?,?,?,?,?,0,?,?,'CRYPTO_PLISIO','pending','pending','WAITING_PAYMENT',?)");
        $stmt->execute([$userId, (int)($plan['id'] ?? 0), $plan['category_id'] ?? null, $plan['code'] ?? ('PLAN-' . (int)($plan['id'] ?? 0)), $plan['title'] ?? 'OpenVPN | L2TP', $serviceType, $duration, $months, $requestedSeats, $requestedSeats, $price, (int)($plan['purchase_price'] ?? 0), $meta]);
        $orderId = (int)$pdo->lastInsertId();

        if ($discountCode && function_exists('gemacc_discount_preview') && function_exists('gemacc_discount_apply')) {
            $d = gemacc_discount_preview($pdo, $userId, $discountCode, (int)($plan['price'] ?? $price), (int)($plan['id'] ?? 0), 'crypto');
            if (!empty($d['valid'])) gemacc_discount_apply($pdo, $d, $userId, $orderId);
        }

        $order = ['id'=>$orderId, 'user_id'=>$userId, 'duration'=>$duration, 'requested_seats'=>$requestedSeats, 'required_accounts'=>$requestedSeats, 'service_type'=>$serviceType, 'plan_id'=>(int)($plan['id'] ?? 0), 'category_id'=>$plan['category_id'] ?? null];
        if (function_exists('plisio_enabled') && plisio_enabled()) {
            $invoice = plisio_create_invoice('ORD-'.$orderId, 'GEM ACC order #'.$orderId, $price, 'order', $asset);
            $cryptoStmt = $pdo->prepare("INSERT INTO crypto_payments(order_id, user_id, amount, currency, crypto_currency, provider, provider_invoice_id, provider_order_id, payment_url, payment_type, status, raw_request, raw_response) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)");
            $cryptoStmt->execute([$orderId, $userId, $price, 'IRT', $invoice['currency'], 'Plisio', $invoice['txn_id'], $invoice['txn_id'], $invoice['invoice_url'], 'order', 'PENDING', $meta, json_encode($invoice['raw'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)]);
            $cryptoId = (int)$pdo->lastInsertId();
            $pdo->prepare("UPDATE orders SET crypto_payment_id=? WHERE id=?")->execute([$cryptoId, $orderId]);
            $pdo->commit();
            return ['ok'=>true,'provider'=>'Plisio','order_id'=>$orderId,'crypto_payment_id'=>$cryptoId,'invoice_id'=>$invoice['txn_id'],'provider_order_id'=>'ORD-'.$orderId,'payment_url'=>$invoice['invoice_url'],'wallet_address'=>'','amount'=>$invoice['crypto_amount'],'amount_unit'=>$invoice['currency'],'network'=>'Plisio','token'=>$invoice['currency'],'selected_asset'=>$asset,'invoice'=>$invoice['raw']];
        }
        if (!function_exists('sw_create_invoice')) return ['ok' => false, 'error' => 'درگاه کریپتو فعال نیست؛ Plisio را فعال کنید یا پرداخت دستی ارز را استفاده کنید.'];
        $payload = sw_make_order_invoice_payload($order, $plan, $price, $userId);
        $resp = sw_create_invoice($payload);
        $json = $resp['json'];
        $result = sw_extract_result($json);
        $providerInvoiceId = sw_extract_invoice_id($json);
        $paymentUrl = sw_extract_payment_url($json);
        $walletAddress = sw_extract_wallet_address($json);
        $cryptoAmount = sw_extract_crypto_amount($json);

        $cryptoStmt = $pdo->prepare("INSERT INTO crypto_payments(order_id, user_id, amount, currency, crypto_currency, network, wallet_address, provider, provider_invoice_id, provider_order_id, payment_url, payment_type, status, raw_request, raw_response) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
        $cryptoStmt->execute([$orderId, $userId, $price, sw_amount_unit(), $cryptoAmount['unit'] ?: sw_default_token(), sw_default_network(), $walletAddress, 'SwapWallet', $providerInvoiceId, $payload['orderId'], $paymentUrl, 'order', 'PENDING', json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $resp['body']]);
        $cryptoId = (int)$pdo->lastInsertId();
        $pdo->prepare("UPDATE orders SET crypto_payment_id=? WHERE id=?")->execute([$cryptoId, $orderId]);
        $pdo->commit();

        return [
            'ok'=>true,
            'order_id'=>$orderId,
            'crypto_payment_id'=>$cryptoId,
            'invoice_id'=>$providerInvoiceId,
            'provider_order_id'=>$payload['orderId'],
            'payment_url'=>$paymentUrl,
            'wallet_address'=>$walletAddress,
            'amount'=>$cryptoAmount['number'],
            'amount_unit'=>$cryptoAmount['unit'],
            'network'=>sw_default_network(),
            'token'=>sw_default_token(),
            'invoice'=>$result,
        ];
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        if (function_exists('logError')) logError('[order crypto bot] ' . $e->getMessage());
        return ['ok'=>false, 'error'=>'خطای پرداخت کریپتو: ' . $e->getMessage()];
    }
}

function gemaccOrderCryptoInvoiceText(array $res, array $plan, int $amount): string {
    $txt = "🪙 **فاکتور پرداخت ارز دیجیتال ساخته شد**\n\n";
    $txt .= "📦 پلن: " . ($plan['title'] ?? 'سرویس') . "\n";
    $txt .= "💰 مبلغ سفارش: " . money($amount) . "\n";
    if (!empty($res['selected_asset'])) $txt .= gemaccCryptoQuoteText($amount, (string)$res['selected_asset']);
    if (!empty($res['amount']) || !empty($res['amount_unit'])) $txt .= "₿ مبلغ دقیق: " . trim(($res['amount'] ?? '') . ' ' . ($res['amount_unit'] ?? ($res['token'] ?? ''))) . "\n";
    if (!empty($res['network'])) $txt .= "🌐 شبکه: {$res['network']}\n";
    if (!empty($res['wallet_address'])) $txt .= "\n📬 آدرس کیف پول:\n`{$res['wallet_address']}`\n";
    if (!empty($res['invoice_id'])) $txt .= "\n🧾 شناسه فاکتور: `{$res['invoice_id']}`\n";
    if (!empty($res['order_id'])) $txt .= "🆔 شماره سفارش: #{$res['order_id']}\n";
    $txt .= "\n✅ بعد از تأیید شبکه، سفارش شما خودکار پردازش می‌شود.";
    return $txt;
}

function gemaccCryptoInvoiceText(array $res, int $amount): string {
    $txt = "🪙 **فاکتور پرداخت کریپتو ساخته شد**\n\n";
    $txt .= "💰 مبلغ شارژ: " . money($amount) . "\n";
    if (!empty($res['amount']) || !empty($res['amount_unit'])) {
        $txt .= "₿ مبلغ دقیق: " . trim(($res['amount'] ?? '') . ' ' . ($res['amount_unit'] ?? ($res['token'] ?? ''))) . "\n";
    }
    if (!empty($res['network'])) $txt .= "🌐 شبکه: {$res['network']}\n";
    if (!empty($res['wallet_address'])) $txt .= "\n📬 آدرس کیف پول:\n`{$res['wallet_address']}`\n";
    if (!empty($res['invoice_id'])) $txt .= "\n🧾 شناسه فاکتور: `{$res['invoice_id']}`\n";
    $txt .= "\n✅ بعد از تأیید شبکه، موجودی کیف پول شما خودکار شارژ می‌شود.";
    return $txt;
}

// ════════════════════════════════════════════════════════════════════════════
// MESSAGE HANDLER
// ════════════════════════════════════════════════════════════════════════════

if (isset($update['message'])) {
    $msg    = $update['message'];
    $chatId = $msg['chat']['id'];
    $user   = $msg['from'];
    $text   = $msg['text'] ?? '';
    $rawText = $text;
    $photo  = $msg['photo'] ?? null;

    // ─── Telegram Stars: successful_payment (v27.52) ───
    // Must be handled BEFORE force-join / blocked checks, otherwise a paid order
    // could be silently dropped. Delivery is idempotent.
    if (isset($msg['successful_payment'])) {
        $sp       = $msg['successful_payment'];
        $from     = (int)($msg['from']['id'] ?? 0);
        $payload  = (string)($sp['invoice_payload'] ?? '');
        $chargeId = (string)($sp['telegram_payment_charge_id'] ?? '');
        $starsPaid = (int)($sp['total_amount'] ?? 0);

        try {
            if (preg_match('/^STARS_ORDER_(\d+)$/', $payload, $m)) {
                $orderId = (int)$m[1];
                $pdo->beginTransaction();
                $stmtSp = $pdo->prepare("SELECT * FROM orders WHERE id=? AND payment_method='STARS' FOR UPDATE");
                $stmtSp->execute([$orderId]);
                $order = $stmtSp->fetch(PDO::FETCH_ASSOC);

                if (!$order) {
                    $pdo->commit();
                    error_log("[stars] successful_payment for unknown order {$orderId}");
                } elseif (strtolower((string)$order['payment_status']) === 'paid') {
                    $pdo->commit(); // already processed
                } else {
                    $meta = json_decode((string)($order['receipt_text'] ?? ''), true);
                    if (!is_array($meta)) $meta = [];
                    $meta['telegram_payment_charge_id'] = $chargeId;
                    $meta['stars_paid'] = $starsPaid;
                    $pdo->prepare("UPDATE orders SET payment_status='paid', status=IF(status='PENDING','PAID',status), receipt_text=? WHERE id=?")
                        ->execute([json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $orderId]);
                    $pdo->commit();
                    $starsResult = null;
                    if (function_exists('vpnApproveOrderAndNotify')) {
                        $starsResult = vpnApproveOrderAndNotify($pdo, $orderId, true);
                    }
                    if (is_array($starsResult) && function_exists('gemaccStarsNotifyAdminsAfterPayment')) {
                        gemaccStarsNotifyAdminsAfterPayment($pdo, $orderId, $starsResult, $starsPaid);
                    }
                }
            } elseif (preg_match('/^STARS_WALLET_(\d+)$/', $payload, $mw)) {
                // v27.53 (bug #4): credit the wallet on a successful Stars top-up.
                $txId = (int)$mw[1];
                $pdo->beginTransaction();
                $stmtTx = $pdo->prepare("SELECT * FROM wallet_transactions WHERE id=? AND payment_method='stars' FOR UPDATE");
                $stmtTx->execute([$txId]);
                $tx = $stmtTx->fetch(PDO::FETCH_ASSOC);
                if (!$tx) {
                    $pdo->commit();
                    error_log("[stars] successful_payment for unknown wallet tx {$txId}");
                } elseif (strtoupper((string)$tx['status']) === 'COMPLETED') {
                    $pdo->commit(); // already credited (idempotent)
                } elseif ((int)$tx['user_id'] !== $from) {
                    $pdo->commit();
                    error_log("[stars] wallet tx {$txId} user mismatch");
                } else {
                    $meta = json_decode((string)($tx['receipt_text'] ?? ''), true);
                    if (!is_array($meta)) $meta = [];
                    $meta['telegram_payment_charge_id'] = $chargeId;
                    $meta['stars_paid'] = $starsPaid;
                    $pdo->prepare("UPDATE wallet_transactions SET status='COMPLETED', processed_at=NOW(), receipt_text=? WHERE id=?")
                        ->execute([json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $txId]);
                    $pdo->prepare("UPDATE users SET balance=balance+? WHERE id=?")->execute([(int)$tx['amount'], (int)$tx['user_id']]);
                    $pdo->commit();
                    $newBalance = null;
                    try { $bs=$pdo->prepare("SELECT balance FROM users WHERE id=?"); $bs->execute([(int)$tx['user_id']]); $newBalance=(int)$bs->fetchColumn(); } catch (Throwable $ig) {}
                    $okMsg = "✅ کیف پول شما به مبلغ " . number_format((int)$tx['amount']) . " تومان با استارز شارژ شد.";
                    if ($newBalance !== null) $okMsg .= "\n💰 موجودی جدید: " . number_format($newBalance) . " تومان";
                    tg('sendMessage', ['chat_id' => $from, 'text' => $okMsg]);
                    exit('OK');
                }
            } else {
                error_log("[stars] successful_payment unrecognized payload: {$payload}");
            }
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            error_log('[stars successful_payment] ' . $e->getMessage());
        }

        $genericMsg = (strpos($payload, 'STARS_WALLET_') === 0)
            ? "✅ پرداخت استارز شما دریافت شد. موجودی کیف پول شما به‌روزرسانی می‌شود."
            : "✅ پرداخت استارز شما با موفقیت انجام شد. سرویس شما در حال آماده‌سازی است و از بخش «سرویس‌های من» قابل مشاهده است.";
        tg('sendMessage', ['chat_id' => $from, 'text' => $genericMsg]);
        exit('OK');
    }

    ensureUser($user);
    if (is_string($text) && preg_match('/^\/start\s+ref[_-]?\d+/i', trim($text))) {
        try { ReferralService::captureStartPayload($pdo, (int)$chatId, trim($text)); } catch (Throwable $ignored) {}
    }
    if (!isAdmin($chatId) && function_exists('gemaccUserIsBlocked') && gemaccUserIsBlocked((int)$chatId)) {
        tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'🚫 حساب شما داخل ربات مسدود شده است. دلیل احتمالی: رسید/فیش نامعتبر یا تخلف در استفاده. برای بررسی فقط از بخش پشتیبانی پیگیری کنید.']);
        exit('OK');
    }
    
    // ادمین از بررسی عضویت معاف است
    if (!isAdmin($chatId)) {
        // بررسی عضویت در کانال‌ها
        $memberCheck = $forceJoin->checkUserMembership($chatId, 'checkMembership');
        if (!$memberCheck['joined']) {
            $joinMsg = $forceJoin->buildJoinMessage($memberCheck['missing_channels']);
            tg('sendMessage', [
                'chat_id'      => $chatId,
                'text'         => $joinMsg['text'],
                'parse_mode'   => 'Markdown',
                'reply_markup' => json_encode($joinMsg['keyboard'], JSON_UNESCAPED_UNICODE),
            ]);
            exit('OK');
        }
    }
    
    // بررسی حالت فعلی کاربر باید قبل از تشخیص دکمه‌های منو انجام شود.
    // در نسخه قبلی اگر ادمین متن آموزش شامل کلمه «آموزش/پشتیبانی/بازگشت» می‌فرستاد،
    // state ویرایش آموزش پاک می‌شد و پیام مثل ورودی عادی پردازش می‌شد.
    $mode = getUserMode($chatId);
    $activeModeName = $mode['mode'] ?? null;
    $protectedInputModes = [
        'ADMIN_EDIT_TUTORIAL',
        'ADMIN_FAQ_ADD',
        'ADMIN_FAQ_EDIT',
        'ADMIN_CHANGE_WELCOME',
        'ADMIN_CHANGE_CARD_OWNER',
        'ADMIN_EDIT_CREDENTIAL',
        'ADMIN_EXTEND_CREDENTIAL',
        'ADMIN_ADD_PLAN_CATEGORY',
        'ADMIN_ADD_SALE_PLAN',
        'ADMIN_ADD_CREDENTIAL',
        'ADMIN_ADD_CREDENTIAL_BULK',
        'ADMIN_ADD_CREDENTIAL_CAPACITY',
        'ADMIN_ADD_V2RAY_CONFIG',
        'ADMIN_ADD_V2RAY_QR',
        'ADMIN_SET_PLAN_PRICE',
        'ADMIN_SET_PLAN_PURCHASE_PRICE',
        'ADMIN_SET_SETTING_VALUE',
        'WAITING_CRYPTO_RECEIPT',
        'WAITING_CARD_RECEIPT',
        'SUPPORT_WAITING_MESSAGE',
        'SUPPORT_MESSAGE',
        'ADMIN_CHAT_REPLY',
        'ADMIN_REPLY_TICKET',
        'USER_REPLY_TO_ADMIN',
    ];
    $isProtectedInputMode = $activeModeName && in_array($activeModeName, $protectedInputModes, true);

    // ═══════════════════ لغو flow با زدن دکمه منو ═══════════════════
    // در حالت‌های ورودی حساس مثل ویرایش آموزش، متن آزاد نباید به اشتباه دکمه منو تشخیص داده شود.
    if (gemaccIsBotNavigationText($text) && !$isProtectedInputMode) {
        clearUserMode($chatId);
        if (in_array(gemaccNormalizeBotText($text), ['🏠 منوی اصلی','🔙 بازگشت','❌ انصراف','/cancel'], true)) {
            sendMainMenu($chatId, '✅ عملیات قبلی متوقف شد.');
            exit('OK');
        }
    }
    if ($isProtectedInputMode && (in_array(gemaccNormalizeBotText($text), ['❌ انصراف','/cancel'], true) || gemaccIsExactMenuCommand($text))) {
        clearUserMode($chatId);
        sendMainMenu($chatId, '✅ عملیات قبلی متوقف شد. لطفاً گزینه موردنظر را دوباره انتخاب کنید.');
        exit('OK');
    }

    // حالت ممکن است بعد از لغو تغییر کرده باشد
    $mode = getUserMode($chatId);
    $activeModeName = is_array($mode) ? ($mode['mode'] ?? null) : null;
    $isProtectedInputMode = $activeModeName && in_array($activeModeName, $protectedInputModes, true);

    // دکمه‌های منوی اصلی فقط وقتی متن آزاد کاربر نیست canonical شوند.
    // در نسخه‌های قبلی متن پشتیبانی که شامل کلمه «پشتیبانی/آموزش/خرید» بود،
    // قبل از ذخیره به متن دکمه تبدیل می‌شد یا state پاک می‌شد؛ همین باعث ذخیره نشدن چت‌ها می‌شد.
    if (!$isProtectedInputMode) {
        $text = gemaccCanonicalMenuText($text);
    } else {
        $text = (string)$rawText;
    }
    
    // ═══════════════════ حالت‌های خاص ═══════════════════
    

    if ($mode && ($mode['mode'] ?? '') === 'USER_REPLY_TO_ADMIN') {
        $payload = $mode['payload'] ?? [];
        $adminId = (int)($payload['admin_id'] ?? 0);
        $replyText = trim((string)($msg['caption'] ?? $text ?? ''));
        $photoFileId = null;
        if (is_array($photo) && $photo) {
            $lastPhoto = end($photo);
            if (is_array($lastPhoto)) $photoFileId = (string)($lastPhoto['file_id'] ?? '');
        }
        if ($replyText === '' && !$photoFileId) {
            tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'❌ لطفاً متن پاسخ خود را ارسال کنید یا /cancel بزنید.']);
            exit('OK');
        }

        $targets = function_exists('gemaccPrivateReplyAdminTargets') ? gemaccPrivateReplyAdminTargets($adminId) : (function_exists('adminIds') ? array_map('intval', adminIds()) : [$adminId]);
        $displayName = trim((string)($user['first_name'] ?? '') . ' ' . (string)($user['last_name'] ?? ''));
        $username = trim((string)($user['username'] ?? ''));
        $nameLine = $displayName !== '' ? $displayName : ($username !== '' ? '@'.$username : 'کاربر');
        $safeName = function_exists('tgHtmlEscape') ? tgHtmlEscape($nameLine) : htmlspecialchars($nameLine, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        $safeUser = function_exists('tgHtmlEscape') ? tgHtmlEscape((string)$chatId) : htmlspecialchars((string)$chatId, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        if (mb_strlen($replyText, 'UTF-8') > 3500) {
            $replyText = mb_substr($replyText, 0, 3500, 'UTF-8') . '\n...';
        }
        $safeReply = function_exists('tgHtmlEscape') ? tgHtmlEscape($replyText) : htmlspecialchars($replyText, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        $header = "📨 <b>پاسخ کاربر به پیام خصوصی ادمین</b>\n"
            . "👤 کاربر: <b>{$safeName}</b> | <code>{$safeUser}</code>"
            . ($username !== '' ? "\n🔗 یوزرنیم: @" . (function_exists('tgHtmlEscape') ? tgHtmlEscape($username) : htmlspecialchars($username, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')) : '')
            . "\n━━━━━━━━━━━━━━\n";
        $sent = 0;
        foreach ($targets as $targetAdmin) {
            if (!$targetAdmin) continue;
            $params = [
                'chat_id' => (int)$targetAdmin,
                'parse_mode' => 'HTML',
                'reply_markup' => function_exists('gemaccAdminReplyToUserKeyboard') ? gemaccAdminReplyToUserKeyboard((int)$chatId) : json_encode(['inline_keyboard'=>[[['text'=>'✉️ پاسخ به این کاربر','callback_data'=>'ADMIN_USER_MSG_'.$chatId]]]], JSON_UNESCAPED_UNICODE),
            ];
            if ($photoFileId) {
                $params['photo'] = $photoFileId;
                $captionText = $safeReply !== '' ? $safeReply : '📎 تصویر ارسالی کاربر';
                if (mb_strlen(strip_tags($header . $captionText), 'UTF-8') > 980) {
                    $captionText = function_exists('tgHtmlEscape')
                        ? tgHtmlEscape(mb_substr($replyText, 0, 760, 'UTF-8') . '\n...')
                        : htmlspecialchars(mb_substr($replyText, 0, 760, 'UTF-8') . '\n...', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
                }
                $params['caption'] = $header . $captionText;
                $res = tg('sendPhoto', $params);
            } else {
                $params['text'] = $header . $safeReply;
                $res = tg('sendMessage', $params);
            }
            if (is_array($res) ? !empty($res['ok']) : (bool)$res) $sent++;
        }
        if (function_exists('gemaccLogPrivateMessage')) {
            gemaccLogPrivateMessage($adminId, (int)$chatId, 'user_to_admin', $replyText, null, $photoFileId ?: null);
        }
        clearUserMode($chatId);
        tg('sendMessage', ['chat_id'=>$chatId, 'text'=>$sent > 0 ? '✅ پاسخ شما برای ادمین ارسال شد.' : '❌ ارسال پاسخ ناموفق بود. لطفاً دوباره تلاش کنید یا از پشتیبانی پیام بدهید.']);
        exit('OK');
    }

    if ($mode && ($mode['mode'] ?? '') === 'ADMIN_CHAT_REPLY' && isAdmin($chatId)) {
        $payload = $mode['payload'] ?? [];
        $targetId = (int)($payload['user_id'] ?? 0);
        $chatType = (string)($payload['chat_type'] ?? 'private');
        $replyText = trim((string)($msg['caption'] ?? $text ?? ''));
        if ($targetId <= 0 || $replyText === '') {
            tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'❌ متن پاسخ یا شناسه کاربر نامعتبر است.']);
            exit('OK');
        }
        $sendType = $chatType === 'support' ? 'support' : 'private';
        $res = function_exists('gemaccAdminChatSendReply') ? gemaccAdminChatSendReply($sendType, (int)$chatId, $targetId, $replyText) : ['ok'=>false, 'error'=>'Chat service unavailable'];
        clearUserMode($chatId);
        if (empty($res['ok'])) {
            tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'❌ ارسال پاسخ ناموفق بود: '.($res['error'] ?? 'خطا'), 'reply_markup'=>json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE)]);
            exit('OK');
        }
        tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'✅ پاسخ برای کاربر ارسال شد.', 'reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'💬 بازگشت به چت','callback_data'=>'ADMIN_CHAT_VIEW_'.$sendType.'_'.$targetId]],[['text'=>'🏠 پنل مدیریت','callback_data'=>'ADMIN_HOME']]]], JSON_UNESCAPED_UNICODE)]);
        exit('OK');
    }

    if ($mode && ($mode['mode'] ?? '') === 'ADMIN_EDIT_CREDENTIAL' && isAdmin($chatId)) {
        $id=(int)($mode['payload']['credential_id'] ?? 0); $pair=trim($text);
        if(!$id || strpos($pair, ':')===false){ tg('sendMessage',['chat_id'=>$chatId,'text'=>'فرمت صحیح: username:password']); exit('OK'); }
        [$u,$p]=array_map('trim',explode(':',$pair,2));
        $capacity = null;
        if (preg_match('/(?:capacity|ظرفیت|devices?|کاربر)\s*[:=]?\s*(\d+)/iu', $pair, $mm)) { $capacity = max(1, min(100, (int)$mm[1])); }
        if ($capacity !== null) {
            $pdo->prepare("UPDATE credentials SET email=?, username=?, password=?, max_seats=GREATEST(?, COALESCE(used_seats,0)), status=IF(COALESCE(used_seats,0)>=GREATEST(?,COALESCE(used_seats,0)),'sold',IF(COALESCE(used_seats,0)>0,'partial','available')), updated_at=NOW() WHERE id=?")->execute([$u,$u,$p,$capacity,$capacity,$id]);
        } else {
            $pdo->prepare("UPDATE credentials SET email=?, username=?, password=?, updated_at=NOW() WHERE id=?")->execute([$u,$u,$p,$id]);
        }
        clearUserMode($chatId); tg('sendMessage',['chat_id'=>$chatId,'text'=>'✅ یوزرنیم/پسورد اشتراک اصلاح شد'.($capacity!==null?' و ظرفیت جدید ذخیره شد.':'.')]); exit('OK');
    }
    if ($mode && ($mode['mode'] ?? '') === 'ADMIN_EXTEND_CREDENTIAL' && isAdmin($chatId)) {
        $id=(int)($mode['payload']['credential_id'] ?? 0); $months=max(1,(int)preg_replace('/\D+/','',$text));
        if(!$id){ clearUserMode($chatId); tg('sendMessage',['chat_id'=>$chatId,'text'=>'❌ شناسه نامعتبر است']); exit('OK'); }
        $pdo->prepare("UPDATE credentials SET months=COALESCE(months,1)+?, expires_at=CASE WHEN expires_at IS NULL THEN NULL ELSE DATE_ADD(expires_at, INTERVAL ? MONTH) END, updated_at=NOW() WHERE id=?")->execute([$months,$months,$id]);
        clearUserMode($chatId); tg('sendMessage',['chat_id'=>$chatId,'text'=>'✅ مدت اشتراک افزایش یافت.']); exit('OK');
    }


    // افزودن کانفیگ V2Ray: متن کانفیگ سپس QR Code اختیاری
    if ($mode && ($mode['mode'] ?? '') === 'ADMIN_ADD_V2RAY_CONFIG' && isAdmin($chatId)) {
        $configText = trim((string)($msg['text'] ?? $msg['caption'] ?? $text ?? ''));
        if ($configText === '' || mb_strlen($configText, 'UTF-8') < 8) {
            tg('sendMessage',['chat_id'=>$chatId,'text'=>'❌ متن کانفیگ معتبر نیست. لینک کامل V2Ray مانند vless://، vmess://، trojan:// یا متن کانفیگ را ارسال کنید.']);
            exit('OK');
        }
        $payload = $mode['payload'] ?? [];
        $payload['config_text'] = $configText;
        setUserMode($chatId, 'ADMIN_ADD_V2RAY_QR', $payload);
        tg('sendMessage', [
            'chat_id'=>$chatId,
            'text'=>"✅ متن کانفیگ دریافت شد.\n\nاکنون تصویر QR Code را ارسال کنید. اگر QR ندارید، /skip را بفرستید.",
            'reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'❌ انصراف','callback_data'=>'ADMIN_ADD_CREDENTIAL']]]], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    if ($mode && ($mode['mode'] ?? '') === 'ADMIN_ADD_V2RAY_QR' && isAdmin($chatId)) {
        $payload = $mode['payload'] ?? [];
        $att = gemaccTelegramMessageAttachment($msg);
        $skip = strtolower(trim((string)($msg['text'] ?? $text ?? ''))) === '/skip';
        if (!$skip && (($att['type'] ?? '') !== 'photo') && !(($att['type'] ?? '') === 'document' && str_starts_with(strtolower((string)($att['mime'] ?? '')), 'image/'))) {
            tg('sendMessage',['chat_id'=>$chatId,'text'=>'❌ لطفاً تصویر QR Code را به‌صورت عکس/فایل تصویری ارسال کنید یا /skip را بفرستید.']);
            exit('OK');
        }
        $categoryId=(int)($payload['category_id'] ?? 0);
        $cat=null;
        if($categoryId>0){$st=$pdo->prepare("SELECT * FROM plan_categories WHERE id=? LIMIT 1");$st->execute([$categoryId]);$cat=$st->fetch(PDO::FETCH_ASSOC)?:null;}
        if(!$cat || gemaccServiceTypeFromHint((string)($cat['service_type']??''),(string)($cat['name']??''))!=='v2ray'){
            clearUserMode($chatId); tg('sendMessage',['chat_id'=>$chatId,'text'=>'❌ دسته V2Ray پیدا نشد. دوباره از «افزودن اشتراک» شروع کنید.']); exit('OK');
        }
        try {
            if (function_exists('vpnEnsureColumns')) vpnEnsureColumns($pdo);
            $configText=trim((string)($payload['config_text']??''));
            $key='v2ray_'.substr(hash('sha256',$configText.'|'.microtime(true).'|'.random_int(1,PHP_INT_MAX)),0,24);
            $trafficType=(string)($cat['traffic_type']??'unlimited');
            $trafficLimit=$trafficType==='limited'?max(1,(int)($cat['traffic_limit_gb']??1)):null;
            $months=max(1,(int)($cat['months']??1));
            $duration=(string)($cat['code']??'V2_1M_U');
            $qrFileId=$skip?null:trim((string)($att['file_id']??''));
            $st=$pdo->prepare("INSERT INTO credentials(category_id,email,username,password,config_text,qr_code_file_id,duration,months,protocol,max_seats,traffic_type,traffic_limit_gb,used_seats,sellable_until,is_active,is_sold,status,created_by) VALUES(?,?,?,?,?,?,?,?,?,1,?,?,0,DATE_ADD(CURDATE(),INTERVAL 10 YEAR),1,0,'available',?)");
            $st->execute([$categoryId,$key,$key,'-',$configText,$qrFileId,$duration,$months,'v2ray',$trafficType,$trafficLimit,(int)$chatId]);
            $newId=(int)$pdo->lastInsertId();
            $q=function_exists('vpnProcessWaitingQueue')?vpnProcessWaitingQueue($pdo,$duration,20,$categoryId):['processed'=>[]];
            clearUserMode($chatId);
            tg('sendMessage',[
                'chat_id'=>$chatId,
                'text'=>"✅ کانفیگ V2Ray #{$newId} ثبت شد.\n📦 دسته: ".($cat['name']??$duration)."\n📊 نوع: ".($trafficType==='limited'?($trafficLimit.' گیگ'):'نامحدود')."\n📷 QR: ".($qrFileId?'ثبت شد':'ثبت نشد')."\n🚚 سفارش‌های تحویل‌شده از صف: ".count($q['processed']??[]),
                'reply_markup'=>json_encode(kbAdminPanel(),JSON_UNESCAPED_UNICODE),
            ]);
            gemaccAdminActionNotify('کانفیگ V2Ray به موجودی اضافه شد',['شناسه'=>$newId,'دسته'=>$duration,'QR'=>$qrFileId?'دارد':'ندارد','تحویل صف'=>count($q['processed']??[])],(int)$chatId);
        } catch(Throwable $e) {
            tg('sendMessage',['chat_id'=>$chatId,'text'=>'❌ ثبت کانفیگ ناموفق بود: '.$e->getMessage()]);
        }
        exit('OK');
    }

    // ظرفیت هر اکانت بعد از دریافت username:password
    if ($mode && ($mode['mode'] ?? '') === 'ADMIN_ADD_CREDENTIAL_CAPACITY' && isAdmin($chatId)) {
        $payload = $mode['payload'] ?? [];
        $serviceTypeForCapacity = gemaccServiceTypeFromHint((string)($payload['service_type'] ?? 'openvpn_l2tp'));
        $capacity = max(1, min(100, (int)preg_replace('/[^0-9]/', '', (string)$text)));
        if ($capacity < 1) { tg('sendMessage',['chat_id'=>$chatId,'text'=>'❌ ظرفیت معتبر نیست. مثلاً 1 یا 15 یا 20 را ارسال کن.']); exit('OK'); }
        $categoryId = (int)($payload['category_id'] ?? 0);
        $duration = (string)($payload['duration'] ?? 'WS_1M_U');
        $trafficType = (string)($payload['traffic_type'] ?? 'unlimited');
        $trafficLimitGb = isset($payload['traffic_limit_gb']) ? (int)$payload['traffic_limit_gb'] : null;
        $durationLabel = (string)($payload['duration_label'] ?? $duration);
        $serviceType = $serviceTypeForCapacity;
        $validRows = is_array($payload['rows'] ?? null) ? $payload['rows'] : [];
        if (!$validRows) { clearUserMode($chatId); tg('sendMessage',['chat_id'=>$chatId,'text'=>'❌ اطلاعات اکانت‌ها پیدا نشد. دوباره افزودن اشتراک را شروع کن.']); exit('OK'); }
        $inserted=[]; $duplicates=[]; $failed=[]; $queueProcessedTotal=0;
        foreach ($validRows as $row) {
            $username = trim((string)($row['username'] ?? ''));
            $password = trim((string)($row['password'] ?? ''));
            if ($username==='' || $password==='') continue;
            try {
                $dupStmt = $pdo->prepare("SELECT id FROM credentials WHERE email=? OR username=? LIMIT 1");
                $dupStmt->execute([$username, $username]);
                $existingId = (int)($dupStmt->fetchColumn() ?: 0);
                if ($existingId > 0) { $duplicates[] = $username . ' (#' . $existingId . ')'; continue; }
                $credId = $credentials->add($username, $password, $duration, $chatId, $trafficType, $categoryId ?: null, $trafficLimitGb, $serviceType, $capacity);
                $inserted[] = ['id'=>$credId,'username'=>$username,'password'=>$password];
                if (function_exists('vpnProcessWaitingQueue')) {
                    $queueResult = vpnProcessWaitingQueue($pdo, $duration, 20, $categoryId ?: null);
                    $queueProcessedTotal += count($queueResult['processed'] ?? []);
                }
            } catch (Throwable $e) { $failed[] = $username . ' ← ' . mb_substr($e->getMessage(),0,80,'UTF-8'); }
        }
        clearUserMode($chatId);
        $txt = "✅ **نتیجه افزودن اشتراک**\n\n";
        $txt .= "📦 دسته: **{$durationLabel}**\n";
        $txt .= "👥 ظرفیت هر اکانت: **{$capacity} کاربر**\n";
        $txt .= "━━━━━━━━━━━━━━━━━━━━\n";
        $txt .= "✅ ثبت‌شده: **".count($inserted)."** عدد\n♻️ تکراری: **".count($duplicates)."** عدد\n❌ خطا: **".count($failed)."** عدد\n";
        if ($queueProcessedTotal>0) $txt .= "📨 تحویل خودکار از صف: **{$queueProcessedTotal}** سفارش\n";
        if ($inserted) { $txt .= "\n🆔 ثبت‌ها:\n"; foreach(array_slice($inserted,0,12) as $r) $txt .= "`#{$r['id']}` - `{$r['username']}`\n"; }
        if ($duplicates) { $txt .= "\n♻️ تکراری‌ها:\n"; foreach(array_slice($duplicates,0,8) as $d) $txt .= "`{$d}`\n"; }
        if ($failed) { $txt .= "\n❌ خطاها:\n"; foreach(array_slice($failed,0,5) as $f) $txt .= "`{$f}`\n"; }
        tg('sendMessage',['chat_id'=>$chatId,'text'=>$txt,'parse_mode'=>'Markdown','reply_markup'=>json_encode(kbAdminPanel(),JSON_UNESCAPED_UNICODE)]);
        gemaccAdminActionNotify('افزودن اشتراک از ربات انجام شد', ['دسته'=>$durationLabel, 'ظرفیت هر اکانت'=>$capacity, 'ثبت‌شده'=>count($inserted), 'تکراری'=>count($duplicates), 'خطا'=>count($failed), 'تحویل خودکار صف'=>$queueProcessedTotal], $chatId);
        exit('OK');
    }

    // حالت افزودن اشتراک (ادمین) - پشتیبانی از ثبت تکی و انبوه
    if ($mode && ($mode['mode'] ?? '') === 'ADMIN_ADD_CREDENTIAL' && isAdmin($chatId)) {
        $categoryId = (int)($mode['payload']['category_id'] ?? 0);
        $cat = null;
        if ($categoryId > 0) {
            $st = $pdo->prepare('SELECT * FROM plan_categories WHERE id=? LIMIT 1');
            $st->execute([$categoryId]);
            $cat = $st->fetch(PDO::FETCH_ASSOC) ?: null;
        }
        $duration = $cat['code'] ?? ($mode['payload']['duration'] ?? '1M');
        $trafficType = $cat['traffic_type'] ?? 'unlimited';
        $serviceType = gemaccServiceTypeFromHint((string)($cat['service_type'] ?? 'openvpn_l2tp'), (string)($cat['service_name'] ?? $cat['name'] ?? ''));
        $trafficLimitGb = ($trafficType === 'limited') ? (int)($cat['traffic_limit_gb'] ?? 1) : null;
        $durationLabel = $cat ? (($cat['name'] ?? $duration) . ' - ' . ($serviceType === 'windscribe' ? 'Windscribe' : ($trafficType === 'limited' ? ('حجمی ' . $trafficLimitGb . ' گیگ') : 'نامحدود'))) : Plans::getDurationLabel($duration);

        // هر خط باید username:password باشد. برای کپی انبوه، خط خالی نادیده گرفته می‌شود.
        $rawLines = preg_split('/\R+/u', trim((string)$text));
        $validRows = [];
        $invalidLines = [];
        $seenInput = [];

        foreach ($rawLines ?: [] as $i => $line) {
            $line = trim($line);
            if ($line === '') { continue; }
            if (strpos($line, ':') === false) {
                $invalidLines[] = ($i + 1) . ') ' . $line;
                continue;
            }
            [$username, $password] = array_map('trim', explode(':', $line, 2));
            if ($username === '' || $password === '') {
                $invalidLines[] = ($i + 1) . ') ' . $line;
                continue;
            }
            $inputKey = mb_strtolower($username, 'UTF-8');
            if (isset($seenInput[$inputKey])) {
                $invalidLines[] = ($i + 1) . ') ' . $line . '  ← تکراری در همین پیام';
                continue;
            }
            $seenInput[$inputKey] = true;
            $validRows[] = ['username' => $username, 'password' => $password, 'line' => $line];
        }

        if ($serviceType === 'windscribe' && count($validRows) > 1) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text' => "⚠️ افزودن Windscribe فقط تک‌تک انجام می‌شود.\n\nهر اکانت Windscribe ظرفیت جداگانه دارد؛ لطفاً فقط یک خط username:password ارسال کن تا در مرحله بعد ظرفیت همان اکانت را بپرسم.",
                'reply_markup' => json_encode(['inline_keyboard'=>[[['text'=>'◀️ انتخاب دسته','callback_data'=>'ADMIN_ADD_CREDENTIAL']]]], JSON_UNESCAPED_UNICODE),
            ]);
            exit('OK');
        }

        if (!$validRows) {
            tg('sendMessage', [
                'chat_id'    => $chatId,
                'text'       => "❌ هیچ اشتراک معتبری پیدا نشد.\n\nفرمت صحیح برای ثبت تکی یا انبوه:\n`username:password`\n\nمثال:\n`openz313:5350\nopenz314:5350\nopenz315:5350`",
                'parse_mode' => 'Markdown',
            ]);
            exit('OK');
        }

        setUserMode($chatId, 'ADMIN_ADD_CREDENTIAL_CAPACITY', [
            'category_id' => $categoryId,
            'duration' => $duration,
            'traffic_type' => $trafficType,
            'traffic_limit_gb' => $trafficLimitGb,
            'duration_label' => $durationLabel,
            'service_type' => $serviceType,
            'rows' => $validRows,
        ]);
        tg('sendMessage', [
            'chat_id' => $chatId,
            'text' => "👥 **ظرفیت دستگاه این اکانت**\n\nاین username/password چند دستگاه/کاربر ظرفیت دارد؟\n\nمثال: `1` یا `5` یا `15` یا `20`\nسیستم دقیقاً تا همین تعداد از این اکانت می‌فروشد و بعد فروش آن را متوقف می‌کند.",
            'parse_mode' => 'Markdown',
            'reply_markup' => json_encode(['inline_keyboard'=>[[['text'=>'❌ انصراف','callback_data'=>'ADMIN_ADD_CREDENTIAL']]]], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');

        $inserted = [];
        $duplicates = [];
        $failed = [];
        $queueProcessedTotal = 0;
        $queueProcessedOrders = [];

        foreach ($validRows as $row) {
            $username = $row['username'];
            $password = $row['password'];
            try {
                $dupStmt = $pdo->prepare("SELECT id FROM credentials WHERE email=? OR username=? LIMIT 1");
                $dupStmt->execute([$username, $username]);
                $existingId = (int)($dupStmt->fetchColumn() ?: 0);
                if ($existingId > 0) {
                    $duplicates[] = $username . ' (#' . $existingId . ')';
                    continue;
                }

                $credId = $credentials->add($username, $password, $duration, $chatId, $trafficType, $categoryId ?: null, $trafficLimitGb, $serviceType);
                $inserted[] = ['id' => $credId, 'username' => $username, 'password' => $password];

                // بعد از هر ثبت، صف همان دسته بررسی شود تا اگر سفارش منتظر وجود دارد همان لحظه تحویل شود.
                if (function_exists('vpnProcessWaitingQueue')) {
                    $queueResult = vpnProcessWaitingQueue($pdo, $duration, 20, $categoryId ?: null);
                    $queueProcessedTotal += count($queueResult['processed'] ?? []);
                    foreach (($queueResult['processed'] ?? []) as $qp) { $queueProcessedOrders[] = $qp; }
                }
            } catch (Throwable $e) {
                $msg = $e->getMessage();
                if (stripos($msg, 'duplicate') !== false || stripos($msg, 'uniq') !== false) {
                    $duplicates[] = $username;
                } else {
                    $failed[] = $username . ' ← ' . mb_substr($msg, 0, 80, 'UTF-8');
                }
            }
        }

        clearUserMode($chatId);

        $txt = "✅ **نتیجه افزودن اشتراک**\n\n";
        $txt .= "📦 دسته: **{$durationLabel}**\n";
        $txt .= "📊 نوع: " . ($serviceType === 'windscribe' ? 'Windscribe | ظرفیت قابل تعیین برای هر اکانت' : ($trafficType === 'limited' ? ('حجمی ' . $trafficLimitGb . ' گیگ') : 'نامحدود')) . "\n";
        $txt .= "━━━━━━━━━━━━━━━━━━━━\n";
        $txt .= "✅ ثبت‌شده: **" . count($inserted) . "** عدد\n";
        $txt .= "♻️ تکراری/قبلاً موجود: **" . count($duplicates) . "** عدد\n";
        $txt .= "⚠️ نامعتبر: **" . count($invalidLines) . "** خط\n";
        $txt .= "❌ خطای ثبت: **" . count($failed) . "** عدد\n";
        if ($queueProcessedTotal > 0) {
            $txt .= "📨 تحویل خودکار از صف: **{$queueProcessedTotal}** سفارش\n";
        }
        $txt .= "━━━━━━━━━━━━━━━━━━━━\n";

        if (count($inserted) === 1) {
            $one = $inserted[0];
            $txt .= "🆔 شناسه: `#{$one['id']}`\n";
            $txt .= "👤 یوزرنیم: `{$one['username']}`\n";
            $txt .= "🔐 رمز: `{$one['password']}`\n";
        } elseif (count($inserted) > 1) {
            $txt .= "🆔 آخرین ثبت‌ها:\n";
            foreach (array_slice($inserted, 0, 12) as $r) {
                $txt .= "`#{$r['id']}` - `{$r['username']}`\n";
            }
            if (count($inserted) > 12) $txt .= "... و " . (count($inserted) - 12) . " مورد دیگر\n";
        }

        if ($duplicates) {
            $txt .= "\n♻️ تکراری‌ها:\n";
            foreach (array_slice($duplicates, 0, 8) as $d) $txt .= "`{$d}`\n";
            if (count($duplicates) > 8) $txt .= "...\n";
        }
        if ($invalidLines) {
            $txt .= "\n⚠️ خطوط نامعتبر:\n";
            foreach (array_slice($invalidLines, 0, 6) as $bad) $txt .= "`{$bad}`\n";
            if (count($invalidLines) > 6) $txt .= "...\n";
        }
        if ($failed) {
            $txt .= "\n❌ خطاها:\n";
            foreach (array_slice($failed, 0, 5) as $f) $txt .= "`{$f}`\n";
            if (count($failed) > 5) $txt .= "...\n";
        }

        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => $txt,
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    


    if ($mode && $mode['mode'] === 'ADMIN_USER_SEARCH' && isAdmin($chatId)) {
        $q=trim($text);
        $like='%'.$q.'%';
        $st=$pdo->prepare("SELECT u.id,u.username,u.first_name,u.last_name,u.balance,COALESCE(s.orders_count,0) orders_count,COALESCE(s.total_spent,0) total_spent FROM users u LEFT JOIN (SELECT user_id,COUNT(*) orders_count,COALESCE(SUM(price),0) total_spent FROM orders WHERE status='DELIVERED' GROUP BY user_id) s ON s.user_id=u.id WHERE CAST(u.id AS CHAR) LIKE ? OR u.username LIKE ? OR u.first_name LIKE ? OR u.last_name LIKE ? ORDER BY s.total_spent DESC,u.created_at DESC LIMIT 10");
        $st->execute([$like,$like,$like,$like]); $rows=$st->fetchAll(PDO::FETCH_ASSOC); clearUserMode($chatId);
        $txt="🔎 نتیجه جستجو\n\n"; $kb=[];
        if(!$rows) $txt.="کاربری پیدا نشد.";
        foreach($rows as $u){ $name=trim(($u['first_name']??'').' '.($u['last_name']??'')) ?: ($u['username']?'@'.$u['username']:'کاربر'); $txt.="👤 {$name}\n🆔 `{$u['id']}` | موجودی: ".money((int)$u['balance'])." | خرید: ".(int)$u['orders_count']."\n\n"; $kb[]=[['text'=>$name.' - '.$u['id'],'callback_data'=>'ADMIN_USER_VIEW_'.$u['id']]]; }
        $kb[]=[['text'=>'◀️ کاربران','callback_data'=>'ADMIN_USERS']];
        tg('sendMessage',['chat_id'=>$chatId,'text'=>$txt,'parse_mode'=>'Markdown','reply_markup'=>json_encode(['inline_keyboard'=>$kb],JSON_UNESCAPED_UNICODE)]); exit('OK');
    }

    // حالت‌های مدیریت کاربران و فروشندگان (ادمین)
    if ($mode && isAdmin($chatId) && in_array($mode['mode'], ['ADMIN_USER_INCREASE','ADMIN_USER_DECREASE','ADMIN_USER_MESSAGE','ADMIN_USER_DISCOUNT','ADMIN_RESELLER_ADD'], true)) {
        $payload = $mode['payload'] ?? [];
        if ($mode['mode'] === 'ADMIN_USER_INCREASE' || $mode['mode'] === 'ADMIN_USER_DECREASE') {
            $targetId = (int)($payload['user_id'] ?? 0);
            $amount = (int)preg_replace('/\D+/', '', $text);
            if (!$targetId || $amount <= 0) {
                tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'❌ مبلغ یا کاربر نامعتبر است.']); exit('OK');
            }
            try {
                $pdo->beginTransaction();
                $st = $pdo->prepare("SELECT balance FROM users WHERE id=? FOR UPDATE"); $st->execute([$targetId]); $u = $st->fetch(PDO::FETCH_ASSOC);
                if (!$u) throw new Exception('کاربر یافت نشد');
                $delta = $mode['mode'] === 'ADMIN_USER_INCREASE' ? $amount : -$amount;
                if ($delta < 0 && (int)$u['balance'] < $amount) throw new Exception('موجودی کاربر کافی نیست');
                $pdo->prepare("UPDATE users SET balance=balance+? WHERE id=?")->execute([$delta, $targetId]);
                $type = $delta > 0 ? 'ADMIN_CREDIT' : 'ADMIN_DEBIT';
                $desc = $delta > 0 ? 'افزایش موجودی توسط مدیر' : 'کاهش موجودی توسط مدیر';
                $pdo->prepare("INSERT INTO wallet_transactions(user_id,amount,type,status,payment_method,currency,description,processed_by,processed_at) VALUES(?,?,?,?, 'admin','IRT',?,?,NOW())")->execute([$targetId, abs($delta), $type, 'APPROVED', $desc, $chatId]);
                $pdo->commit();
                tg('sendMessage', ['chat_id'=>$targetId, 'text'=>($delta>0?'✅ موجودی شما افزایش یافت: ':'⚠️ موجودی شما کاهش یافت: ') . money(abs($delta))]);
                clearUserMode($chatId);
                tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'✅ موجودی بروزرسانی شد.', 'reply_markup'=>json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE)]);
                gemaccAdminActionNotify($delta > 0 ? 'افزایش موجودی کاربر انجام شد' : 'کاهش موجودی کاربر انجام شد', ['کاربر'=>$targetId, 'مبلغ'=>money(abs($delta))], $chatId);
            } catch (Throwable $e) { if ($pdo->inTransaction()) $pdo->rollBack(); tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'❌ '.$e->getMessage()]); }
            exit('OK');
        }
        if ($mode['mode'] === 'ADMIN_USER_MESSAGE') {
            $targetId = (int)($payload['user_id'] ?? 0); if (!$targetId) exit('OK');
            $sendResult = function_exists('gemaccSendPrivateAdminMessageToUser')
                ? gemaccSendPrivateAdminMessageToUser($targetId, $text, (int)$chatId)
                : ['ok' => (bool)tg('sendMessage', ['chat_id'=>$targetId, 'text'=>$text])];
            clearUserMode($chatId);
            if (empty($sendResult['ok'])) {
                tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'❌ ارسال پیام خصوصی ناموفق بود.', 'reply_markup'=>json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE)]);
                exit('OK');
            }
            tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'✅ پیام خصوصی ارسال شد و دکمه «پاسخ به ادمین» برای کاربر فعال شد.', 'reply_markup'=>json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE)]);
            gemaccAdminActionNotify('پیام خصوصی برای کاربر ارسال شد', ['کاربر'=>$targetId, 'متن پیام'=>mb_substr($text,0,500,'UTF-8'), 'دکمه پاسخ'=>'فعال'], $chatId); exit('OK');
        }
        if ($mode['mode'] === 'ADMIN_USER_DISCOUNT') {
            $targetId = (int)($payload['user_id'] ?? 0); if (!$targetId) exit('OK');
            tg('sendMessage', ['chat_id'=>$targetId, 'text'=>"🎫 کد تخفیف اختصاصی شما:\n`".trim($text)."`", 'parse_mode'=>'Markdown']); clearUserMode($chatId);
            tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'✅ کد تخفیف ارسال شد.', 'reply_markup'=>json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE)]); exit('OK');
        }
        if ($mode['mode'] === 'ADMIN_RESELLER_ADD') {
            $parts = array_map('trim', explode(',', $text));
            $uid = (int)($parts[0] ?? 0); $percent = max(0, min(100, (float)($parts[1] ?? 0))); $fixed = max(0, (int)preg_replace('/\D+/', '', (string)($parts[2] ?? 0)));
            if (!$uid) { tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'❌ فرمت: user_id,percent,fixed']); exit('OK'); }
            $pdo->exec("CREATE TABLE IF NOT EXISTS resellers (user_id BIGINT NOT NULL PRIMARY KEY, discount_percent DECIMAL(5,2) NOT NULL DEFAULT 0, fixed_discount INT NOT NULL DEFAULT 0, is_active TINYINT(1) NOT NULL DEFAULT 1, notes TEXT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
            $pdo->prepare("INSERT INTO users(id,is_admin) VALUES(?,0) ON DUPLICATE KEY UPDATE is_admin=0")->execute([$uid]);
            $pdo->prepare("INSERT INTO resellers(user_id,discount_percent,fixed_discount,is_active) VALUES(?,?,?,1) ON DUPLICATE KEY UPDATE discount_percent=VALUES(discount_percent), fixed_discount=VALUES(fixed_discount), is_active=1")->execute([$uid,$percent,$fixed]);
            clearUserMode($chatId);
            tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'✅ فروشنده ذخیره شد.', 'reply_markup'=>json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE)]);
            gemaccAdminActionNotify('فروشنده ذخیره شد', ['کاربر'=>$uid, 'درصد'=>$percent, 'تخفیف ثابت'=>money($fixed)], $chatId); exit('OK');
        }
    }

    // حالت پیام به پشتیبانی - پشتیبانی کامل از متن، تصویر و فایل
    if ($mode && ($mode['mode'] ?? '') === 'SUPPORT_MESSAGE') {
        $att = gemaccTelegramMessageAttachment($msg);
        $supportText = trim((string)($att['text'] ?? $rawText ?? ''));
        $ticketId = (int)($mode['payload']['ticket_id'] ?? 0);
        $attachment = ['type'=>$att['type'], 'name'=>$att['name'], 'mime'=>$att['mime']];
        $res = function_exists('gemaccSupportAddUserMessageToTicket')
            ? gemaccSupportAddUserMessageToTicket($pdo, (int)$chatId, $ticketId, $supportText, $att['file_id'] ?: null, 'پیام پشتیبانی ربات', $attachment)
            : (function_exists('gemaccSupportAddUserMessage') ? gemaccSupportAddUserMessage($pdo, (int)$chatId, $supportText, $att['file_id'] ?: null, 'پیام پشتیبانی ربات', $attachment) : ['ok'=>false, 'error'=>'Support service unavailable']);
        if (!empty($res['ok'])) clearUserMode($chatId);
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => !empty($res['ok']) ? "✅ پیام شما در تیکت #".(int)($res['ticket_id'] ?? 0)." ثبت شد.\n\nپشتیبانی همینجا پاسخ شما را ارسال می‌کند." : ('❌ ارسال پیام ناموفق بود: '.($res['error'] ?? 'خطا').'\nلطفاً دوباره ارسال کنید.'),
            'reply_markup' => json_encode(!empty($res['ok']) ? kbMainMenu() : ['inline_keyboard'=>[[['text'=>'❌ انصراف','callback_data'=>'BACK_MAIN']]]], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    // اگر state کاربر پاک شد، پیام فقط تا ۴۵ دقیقه بعد از آخرین فعالیت وارد تیکت باز می‌شود.
    if (!$mode && !isAdmin($chatId) && !gemaccIsBotNavigationText($rawText)) {
        $att = gemaccTelegramMessageAttachment($msg);
        $autoSupportText = trim((string)($att['text'] ?? $rawText ?? ''));
        if ($autoSupportText !== '' || !empty($att['file_id'])) {
            try {
                $stAuto = $pdo->prepare("SELECT id FROM support_tickets WHERE user_id=? AND status IN ('OPEN','WAITING','ANSWERED') AND COALESCE(last_message_at, updated_at, created_at) >= DATE_SUB(NOW(), INTERVAL 45 MINUTE) ORDER BY COALESCE(last_message_at, updated_at, created_at) DESC, id DESC LIMIT 1");
                $stAuto->execute([(int)$chatId]);
                $autoTicketId = (int)($stAuto->fetchColumn() ?: 0);
                if ($autoTicketId > 0 && function_exists('gemaccSupportAddUserMessageToTicket')) {
                    $autoRes = gemaccSupportAddUserMessageToTicket($pdo, (int)$chatId, $autoTicketId, $autoSupportText, $att['file_id'] ?: null, 'ادامه تیکت پشتیبانی', ['type'=>$att['type'], 'name'=>$att['name'], 'mime'=>$att['mime']]);
                    if (!empty($autoRes['ok'])) {
                        tg('sendMessage', ['chat_id'=>$chatId, 'text'=>"✅ پیام شما در تیکت #".(int)$autoTicketId." ثبت شد."]);
                        exit('OK');
                    }
                }
            } catch (Throwable $e) { error_log('[support_auto_capture] '.$e->getMessage()); }
        }
    }
    
    // حالت پاسخ ادمین به تیکت
    if ($mode && $mode['mode'] === 'ADMIN_REPLY_TICKET' && isAdmin($chatId)) {
        $ticketId = $mode['payload']['ticket_id'];
        $userId   = $mode['payload']['user_id'];
        
        $support->addMessage($ticketId, $chatId, $text, true);
        
        // ارسال به کاربر
        tg('sendMessage', [
            'chat_id'    => $userId,
            'text'       => "📩 **پاسخ پشتیبانی:**\n\n{$text}",
            'parse_mode' => 'Markdown',
        ]);
        
        clearUserMode($chatId);
        
        tg('sendMessage', [
            'chat_id' => $chatId,
            'text'    => "✅ پاسخ ارسال شد.",
        ]);
        exit('OK');
    }
    
    // حالت افزودن کانال اجباری (ادمین)
    if ($mode && $mode['mode'] === 'ADMIN_ADD_CHANNEL' && isAdmin($chatId)) {
        $username = ltrim(trim($text), '@');
        
        if (empty($username)) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "❌ لطفاً یوزرنیم معتبر وارد کنید.",
            ]);
            exit('OK');
        }
        
        $forceJoin->addChannel($username, null, "https://t.me/{$username}");
        
        clearUserMode($chatId);
        
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => "✅ کانال @{$username} اضافه شد.",
            'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    
    // حالت تغییر/پاک کردن آدرس گروه پرسش و پاسخ (ادمین)



    if ($mode && $mode['mode'] === 'ADMIN_EDIT_WS_PLAN_COST_UNIT' && isAdmin($chatId)) {
        $unit = max(0, (int)preg_replace('/[^0-9]/', '', (string)$messageText));
        try { if (function_exists('gemaccCoreAddColumn')) gemaccCoreAddColumn($pdo, 'plans', 'purchase_price', 'INT NOT NULL DEFAULT 0 AFTER `price`'); else $pdo->exec("ALTER TABLE plans ADD COLUMN purchase_price INT NOT NULL DEFAULT 0 AFTER price"); } catch (Throwable $e) {}
        $pdo->prepare("UPDATE plans p JOIN plan_categories pc ON pc.id=p.category_id SET p.purchase_price=GREATEST(1,COALESCE(p.user_count,p.seats,1))*?, p.updated_at=NOW() WHERE p.deleted_at IS NULL AND pc.service_type='windscribe'")->execute([$unit]);
        setSetting('windscribe_purchase_price_per_user', (string)$unit);
        clearUserMode($chatId);
        tg('sendMessage', ['chat_id'=>$chatId,'text'=>"✅ قیمت خرید پلن‌های Windscribe بر اساس هر کاربر ذخیره شد.\n\nقیمت هر کاربر: ".money($unit),'reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'💹 بازگشت به قیمت خرید پلن‌ها','callback_data'=>'ADMIN_PLAN_COSTS']]]],JSON_UNESCAPED_UNICODE)]);
        exit('OK');
    }

    if ($mode && $mode['mode'] === 'ADMIN_EDIT_PLAN_COST_UNIT' && isAdmin($chatId)) {
        $unit = max(0, (int)preg_replace('/[^0-9]/', '', (string)$messageText));
        try { if (function_exists('gemaccCoreAddColumn')) gemaccCoreAddColumn($pdo, 'plans', 'purchase_price', 'INT NOT NULL DEFAULT 0 AFTER `price`'); else $pdo->exec("ALTER TABLE plans ADD COLUMN purchase_price INT NOT NULL DEFAULT 0 AFTER price"); } catch (Throwable $e) {}
        $pdo->prepare("UPDATE plans SET purchase_price=GREATEST(1,COALESCE(user_count,seats,1))*?, updated_at=NOW() WHERE deleted_at IS NULL")->execute([$unit]);
        setSetting('global_purchase_price_per_user', (string)$unit);
        clearUserMode($chatId);
        tg('sendMessage', ['chat_id'=>$chatId,'text'=>"✅ قیمت خرید همه پلن‌ها بر اساس هر کاربر ذخیره شد.

قیمت هر کاربر: ".money($unit),'reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'💹 بازگشت به قیمت خرید پلن‌ها','callback_data'=>'ADMIN_PLAN_COSTS']]]],JSON_UNESCAPED_UNICODE)]);
        exit('OK');
    }

    if ($mode && $mode['mode'] === 'ADMIN_ADD_ADMIN_ID' && isAdmin($chatId)) {
        $newId = preg_replace('/[^0-9]/','', (string)$text);
        if ($newId === '') { tg('sendMessage',['chat_id'=>$chatId,'text'=>'❌ آیدی عددی معتبر نیست.']); exit('OK'); }
        $ids = array_filter(array_map('trim', preg_split('/[,\s]+/', (string)getSetting('admin_extra_ids',''))));
        if ($newId !== '5516198479' && !in_array($newId,$ids,true)) $ids[]=$newId;
        setSetting('admin_extra_ids', implode(',', $ids));
        $pdo->prepare("INSERT INTO users(id,is_admin) VALUES(?,1) ON DUPLICATE KEY UPDATE is_admin=1")->execute([(int)$newId]);
        clearUserMode($chatId);
        tg('sendMessage',['chat_id'=>$chatId,'text'=>"✅ ادمین اضافه شد: `{$newId}`",'parse_mode'=>'Markdown','reply_markup'=>json_encode(kbAdminPanel(),JSON_UNESCAPED_UNICODE)]);
        exit('OK');
    }

    if ($mode && $mode['mode'] === 'ADMIN_EDIT_PLAN_COST' && isAdmin($chatId)) {
        $planId = (int)($mode['payload']['plan_id'] ?? 0);
        $cost = max(0, (int)preg_replace('/[^0-9]/', '', (string)$messageText));
        try { if (function_exists('gemaccCoreAddColumn')) gemaccCoreAddColumn($pdo, 'plans', 'purchase_price', 'INT NOT NULL DEFAULT 0 AFTER `price`'); else $pdo->exec("ALTER TABLE plans ADD COLUMN purchase_price INT NOT NULL DEFAULT 0 AFTER price"); } catch (Throwable $e) {}
        $stmt = $pdo->prepare("UPDATE plans SET purchase_price=?, updated_at=NOW() WHERE id=?");
        $stmt->execute([$cost, $planId]);
        clearUserMode($chatId);
        tg('sendMessage', [
            'chat_id' => $chatId,
            'text' => "✅ قیمت خرید پلن #{$planId} ذخیره شد: " . money($cost),
            'reply_markup' => json_encode(['inline_keyboard' => [[['text'=>'💹 بازگشت به قیمت خرید پلن‌ها','callback_data'=>'ADMIN_PLAN_COSTS']]]], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    if ($mode && $mode['mode'] === 'ADMIN_CHANGE_SUPPORT_GROUP' && isAdmin($chatId)) {
        $link = trim($text);
        if (in_array(mb_strtolower($link), ['حذف', 'پاک', 'delete', 'remove', '-'], true)) {
            $link = '';
        } elseif ($link !== '' && !preg_match('~^https://t\.me/[A-Za-z0-9_/+\-]+$~', $link)) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "❌ لینک معتبر نیست. نمونه صحیح:
https://t.me/your_group

برای حذف کامل، `حذف` را ارسال کنید.",
                'parse_mode' => 'Markdown',
                'reply_markup' => json_encode([
                    'inline_keyboard' => [[['text' => '❌ انصراف', 'callback_data' => 'ADMIN_SETTINGS']]],
                ], JSON_UNESCAPED_UNICODE),
            ]);
            exit('OK');
        }
        setSetting('support_group', $link);
        clearUserMode($chatId);
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => $link === '' ? "✅ آدرس گروه پرسش و پاسخ پاک شد و دکمه گروه نمایش داده نمی‌شود." : "✅ آدرس گروه پرسش و پاسخ ذخیره شد:
{$link}",
            'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    // حالت تغییر تلورانس پرداخت کارت‌به‌کارت (ادمین)
    if ($mode && $mode['mode'] === 'ADMIN_CHANGE_PAYMENT_TOLERANCE' && isAdmin($chatId)) {
        $raw = trim($text);
        $raw = str_replace([',', '٬', '،', 'تومان'], '', $raw);
        $amount = (int)preg_replace('/[^0-9]/', '', $raw);
        $kind = (string)($mode['payload']['kind'] ?? 'default');
        $key = $kind === 'volume' ? 'volume_payment_tolerance_toman' : 'default_payment_tolerance_toman';
        $label = $kind === 'volume' ? 'اشتراک‌های حجمی' : 'اشتراک‌های نامحدود/پیش‌فرض';

        if ($amount < 0 || $amount > 5000000) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "❌ مبلغ تلورانس معتبر نیست. عددی بین ۰ تا ۵,۰۰۰,۰۰۰ تومان ارسال کنید.",
                'reply_markup' => json_encode([
                    'inline_keyboard' => [[['text' => '❌ انصراف', 'callback_data' => 'ADMIN_PAYMENT_TOLERANCE']]],
                ], JSON_UNESCAPED_UNICODE),
            ]);
            exit('OK');
        }

        setSetting($key, (string)$amount);
        clearUserMode($chatId);
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => "✅ تلورانس پرداخت {$label} روی " . number_format($amount) . " تومان ذخیره شد.",
            'reply_markup' => json_encode([
                'inline_keyboard' => [
                    [['text' => '⚖️ مدیریت تلورانس قیمت', 'callback_data' => 'ADMIN_PAYMENT_TOLERANCE']],
                    [['text' => '🏠 پنل مدیریت', 'callback_data' => 'ADMIN']],
                ],
            ], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    // حالت تنظیم درگاه Plisio و نرخ ارز دستی (ادمین)
    if ($mode && in_array($mode['mode'], ['ADMIN_CHANGE_PLISIO_API_KEY','ADMIN_CHANGE_PLISIO_USD_RATE','ADMIN_CHANGE_PLISIO_CURRENCIES','ADMIN_CHANGE_MANUAL_USDT_RATE','ADMIN_CHANGE_MANUAL_TRX_RATE'], true) && isAdmin($chatId)) {
        $m = $mode['mode'];
        $value = trim($text);
        $key = '';
        $label = '';
        if ($m === 'ADMIN_CHANGE_PLISIO_API_KEY') { $key='plisio_api_key'; $label='کلید API/Secret Key درگاه Plisio'; if (in_array(mb_strtolower($value), ['حذف','پاک','-','delete'], true)) $value=''; }
        if ($m === 'ADMIN_CHANGE_PLISIO_USD_RATE') { $key='plisio_usd_rate_toman'; $label='نرخ دلار برای Plisio'; $value=(string)max(1, (int)preg_replace('/[^0-9]/','',$value)); }
        if ($m === 'ADMIN_CHANGE_PLISIO_CURRENCIES') { $key='plisio_currencies'; $label='ارزهای مجاز Plisio'; $value=strtoupper(preg_replace('/[^A-Za-z0-9_,]/','',$value)); }
        if ($m === 'ADMIN_CHANGE_MANUAL_USDT_RATE') { $key='manual_crypto_usdt_irt_rate'; $label='نرخ دستی تتر'; $value=(string)max(0, (int)preg_replace('/[^0-9]/','',$value)); }
        if ($m === 'ADMIN_CHANGE_MANUAL_TRX_RATE') { $key='manual_crypto_trx_irt_rate'; $label='نرخ دستی ترون'; $value=(string)max(0, (int)preg_replace('/[^0-9]/','',$value)); }
        if ($key === '') { clearUserMode($chatId); exit('OK'); }
        setSetting($key, $value);
        clearUserMode($chatId);
        tg('sendMessage', [
            'chat_id' => $chatId,
            'text' => "✅ {$label} ذخیره شد.",
            'reply_markup' => json_encode(['inline_keyboard' => [[['text'=>'🪙 مدیریت پرداخت ارز/Plisio','callback_data'=>'ADMIN_CRYPTO_GATEWAY']], [['text'=>'🏠 پنل مدیریت','callback_data'=>'ADMIN']]]], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    // حالت تغییر شماره کارت (ادمین)
    if ($mode && $mode['mode'] === 'ADMIN_CHANGE_CARD' && isAdmin($chatId)) {
        $cardNumber = preg_replace('/[^0-9]/', '', $text);
        
        if (strlen($cardNumber) !== 16) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "❌ شماره کارت باید ۱۶ رقم باشد.",
                'reply_markup' => json_encode([
                    'inline_keyboard' => [[['text' => '❌ انصراف', 'callback_data' => 'ADMIN_SETTINGS']]],
                ], JSON_UNESCAPED_UNICODE),
            ]);
            exit('OK');
        }
        
        setSetting('card_number', $cardNumber);
        
        clearUserMode($chatId);
        
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => "✅ شماره کارت به `{$cardNumber}` تغییر کرد.",
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    
    // حالت تغییر نام صاحب کارت (ادمین)
    if ($mode && $mode['mode'] === 'ADMIN_CHANGE_CARD_OWNER' && isAdmin($chatId)) {
        $cardOwner = trim($text);
        
        if (empty($cardOwner)) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "❌ نام صاحب کارت نمی‌تواند خالی باشد.",
            ]);
            exit('OK');
        }
        
        setSetting('card_owner_name', $cardOwner);
        
        clearUserMode($chatId);
        
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => "✅ نام صاحب کارت به «{$cardOwner}» تغییر کرد.",
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    

    // حالت مدیریت چند کارت بانکی (ادمین)
    if ($mode && $mode['mode'] === 'ADMIN_CHANGE_BANK_CARDS' && isAdmin($chatId)) {
        $lines = preg_split('/\R+/u', trim((string)$text));
        $valid = [];
        foreach ($lines ?: [] as $line) {
            $line = trim($line);
            if ($line === '') continue;
            $parts = array_map('trim', explode('|', $line));
            $number = preg_replace('/[^0-9]/', '', $parts[0] ?? '');
            if (strlen($number) !== 16) {
                tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'❌ شماره کارت نامعتبر است: '.$line."
فرمت صحیح: شماره کارت|نام صاحب کارت|نام بانک"]);
                exit('OK');
            }
            $valid[] = $number . '|' . ($parts[1] ?? '') . '|' . ($parts[2] ?? '');
        }
        if (!$valid) {
            tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'❌ حداقل یک کارت معتبر وارد کنید.']);
            exit('OK');
        }
        setSetting('bank_cards', implode("
", $valid));
        setSetting('card_number', explode('|', $valid[0])[0]);
        setSetting('card_owner_name', explode('|', $valid[0])[1] ?? '');
        clearUserMode($chatId);
        tg('sendMessage', ['chat_id'=>$chatId, 'text'=>'✅ کارت‌های بانکی چرخشی ذخیره شد. از این به بعد هر بار کاربر وارد مرحله واریز شود کارت بعدی نمایش داده می‌شود.', 'reply_markup'=>json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE)]);
        exit('OK');
    }

    // حالت تغییر پیام خوش‌آمد (ادمین)
    if ($mode && $mode['mode'] === 'ADMIN_CHANGE_WELCOME' && isAdmin($chatId)) {
        $welcomeMsg = trim($text);
        
        if (empty($welcomeMsg)) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "❌ پیام خوش‌آمد نمی‌تواند خالی باشد.",
            ]);
            exit('OK');
        }
        
        setSetting('welcome_message', $welcomeMsg);
        
        clearUserMode($chatId);
        
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => "✅ پیام خوش‌آمدگویی ذخیره شد:\n\n{$welcomeMsg}",
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    

    if ($mode && in_array(($mode['mode'] ?? ''), ['ADMIN_FAQ_ADD','ADMIN_FAQ_EDIT'], true) && isAdmin($chatId)) {
        gemaccEnsureFaqTable();
        $raw = trim((string)($msg['caption'] ?? '')) ?: trim((string)$text);
        if ($raw === '') { tg('sendMessage',['chat_id'=>$chatId,'text'=>'❌ متن سوال و جواب خالی است.']); exit('OK'); }
        $parts = preg_split('/\n\s*\n/u', $raw, 2);
        if (count($parts) < 2) $parts = preg_split('/\n/u', $raw, 2);
        $question = trim($parts[0] ?? '');
        $answer = trim($parts[1] ?? '');
        if ($question === '' || $answer === '') {
            tg('sendMessage',['chat_id'=>$chatId,'text'=>"❌ فرمت صحیح:\nسوال را در خط اول بفرستید، سپس جواب را در خط‌های بعدی.\n\nمثال:\nچطور سرویس دریافت می‌شود؟\nبعد از تایید پرداخت، سرویس در چت ربات ارسال می‌شود."]);
            exit('OK');
        }
        if (($mode['mode'] ?? '') === 'ADMIN_FAQ_EDIT') {
            $id=(int)($mode['payload']['faq_id'] ?? 0);
            $st=$pdo->prepare("UPDATE faq_items SET question=?, answer=?, updated_at=NOW() WHERE id=?");
            $st->execute([$question,$answer,$id]);
        } else {
            $max=(int)$pdo->query("SELECT COALESCE(MAX(display_order),0) FROM faq_items")->fetchColumn();
            $st=$pdo->prepare("INSERT INTO faq_items(question,answer,is_active,display_order) VALUES(?,?,1,?)");
            $st->execute([$question,$answer,$max+10]);
        }
        clearUserMode($chatId);
        tg('sendMessage',['chat_id'=>$chatId,'text'=>'✅ سوال متداول ذخیره شد.','reply_markup'=>json_encode(kbAdminPanel(),JSON_UNESCAPED_UNICODE)]);
        exit('OK');
    }

    // حالت ویرایش آموزش (ادمین)
    if ($mode && ($mode['mode'] ?? '') === 'ADMIN_EDIT_TUTORIAL' && isAdmin($chatId)) {
        $device = $mode['payload']['device'] ?? null;
        $serviceType = $mode['payload']['service_type'] ?? 'openvpn_l2tp';
        $captionText = trim((string)($msg['caption'] ?? ''));
        $inputText = trim((string)$text);

        if ($device) {
            $saved = false;
            $savedParts = [];

            // متن معمولی یا کپشن عکس/ویدیو/فایل آموزشی
            if ($inputText !== '' || $captionText !== '') {
                $tutorials->updateText($device, $inputText !== '' ? $inputText : $captionText, $serviceType);
                $saved = true;
                $savedParts[] = 'متن';
            }

            // ویدیو آموزشی
            $video = $msg['video'] ?? null;
            if ($video && !empty($video['file_id'])) {
                $tutorials->updateMedia($device, $video['file_id'], 'video', $serviceType);
                $saved = true;
                $savedParts[] = 'ویدیو';
            }

            // عکس آموزشی؛ بزرگ‌ترین سایز عکس تلگرام انتخاب می‌شود
            $photoList = $msg['photo'] ?? null;
            if (is_array($photoList) && count($photoList) > 0) {
                $lastPhoto = end($photoList);
                if (!empty($lastPhoto['file_id'])) {
                    $tutorials->updateMedia($device, $lastPhoto['file_id'], 'photo', $serviceType);
                    $saved = true;
                    $savedParts[] = 'تصویر';
                }
            }

            // فایل/پی‌دی‌اف/تصویر ارسال‌شده به صورت document
            $document = $msg['document'] ?? null;
            if ($document && !empty($document['file_id'])) {
                $tutorials->updateMedia($device, $document['file_id'], 'document', $serviceType);
                $saved = true;
                $savedParts[] = 'فایل';
            }

            if ($saved) {
                clearUserMode($chatId);
                $serviceName = Tutorials::getServiceName($serviceType);
                $deviceName = Tutorials::getDeviceName($device);
                $savedLabel = implode(' + ', array_unique($savedParts));
                tg('sendMessage', [
                    'chat_id'      => $chatId,
                    'text'         => "✅ آموزش {$serviceName} - {$deviceName} ذخیره شد.
📌 بخش ذخیره‌شده: {$savedLabel}",
                    'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
                ]);
            } else {
                tg('sendMessage', [
                    'chat_id' => $chatId,
                    'text'    => "❌ چیزی برای ذخیره دریافت نشد. لطفاً متن آموزش، عکس، ویدیو یا فایل آموزشی را ارسال کنید.",
                ]);
            }
        }
        exit('OK');
    }
    
    // حالت افزودن دسته/پلن فروش از ربات (هماهنگ با مینی‌اپ)
    if ($mode && ($mode['mode'] ?? '') === 'ADMIN_ADD_PLAN_CATEGORY' && isAdmin($chatId)) {
        $parts = array_map('trim', explode(',', $text, 5));
        $months = max(1, min(12, (int)($parts[0] ?? 1)));
        $trafficType = strtolower((string)($parts[1] ?? 'unlimited'));
        if (!in_array($trafficType, ['unlimited','limited'], true)) $trafficType = 'unlimited';
        $gb = $trafficType === 'limited' ? max(1, (int)preg_replace('/\D+/', '', (string)($parts[2] ?? 1))) : null;
        $name = trim((string)($parts[3] ?? ''));
        if ($name === '') {
            $names=[1=>'یک‌ماهه',2=>'دوماهه',3=>'سه‌ماهه',4=>'چهارماهه',5=>'پنج‌ماهه',6=>'شش‌ماهه',7=>'هفت‌ماهه',8=>'هشت‌ماهه',9=>'نه‌ماهه',10=>'ده‌ماهه',11=>'یازده‌ماهه',12=>'یک‌ساله'];
            $name = $names[$months] ?? ($months.' ماهه');
        }
        $serviceName = trim((string)($parts[4] ?? ''));
        $serviceType = gemaccServiceTypeFromHint($serviceName, $serviceName . ' ' . $name);
        if ($serviceName === '') $serviceName = $serviceType === 'windscribe' ? 'Windscribe' : ($serviceType === 'expressvpn' ? 'ExpressVPN' : ($serviceType === 'v2ray' ? 'V2Ray' : 'OpenVPN | L2TP'));
        $prefix = gemaccServiceCodePrefix($serviceType);
        $code = $prefix . ($trafficType === 'limited' ? ($months.'M_L'.$gb.'G') : ($months.'M_U'));
        $display = $months * 1000 + ($trafficType === 'limited' ? (int)$gb : 0);
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS plan_categories (id INT AUTO_INCREMENT PRIMARY KEY, code VARCHAR(32) NOT NULL UNIQUE, service_type VARCHAR(30) NOT NULL DEFAULT 'openvpn_l2tp', service_name VARCHAR(100) NULL, name VARCHAR(100) NOT NULL, months INT NOT NULL DEFAULT 1, traffic_type VARCHAR(20) NOT NULL DEFAULT 'unlimited', traffic_limit_gb INT UNSIGNED NULL, is_active TINYINT(1) DEFAULT 1, display_order INT DEFAULT 0, sort_order INT NOT NULL DEFAULT 0, deleted_at DATETIME NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
            $st=$pdo->prepare("INSERT INTO plan_categories(code,service_type,service_name,name,months,traffic_type,traffic_limit_gb,is_active,display_order) VALUES(?,?,?,?,?,?,?,1,?) ON DUPLICATE KEY UPDATE service_type=VALUES(service_type), service_name=VALUES(service_name), name=VALUES(name), months=VALUES(months), traffic_type=VALUES(traffic_type), traffic_limit_gb=VALUES(traffic_limit_gb), is_active=1, deleted_at=NULL, updated_at=NOW()");
            $st->execute([$code,$serviceType,$serviceName,$name,$months,$trafficType,$gb,$display]);
            clearUserMode($chatId);
            tg('sendMessage', ['chat_id'=>$chatId,'text'=>'✅ دسته پلن ذخیره شد.\nکد: '.$code,'reply_markup'=>json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE)]);
        } catch (Throwable $e) {
            tg('sendMessage', ['chat_id'=>$chatId,'text'=>'❌ خطا در ذخیره دسته: '.$e->getMessage()]);
        }
        exit('OK');
    }

    if ($mode && ($mode['mode'] ?? '') === 'ADMIN_ADD_PLAN_ITEM' && isAdmin($chatId)) {
        $payload = $mode['payload'] ?? [];
        $catId = (int)($payload['category_id'] ?? 0);
        $parts = array_map('trim', explode(',', $text, 3));
        $usersCount = max(1, (int)preg_replace('/\D+/', '', (string)($parts[0] ?? 1)));
        $price = max(0, (int)str_replace([',','٫',' ','،'], '', (string)($parts[1] ?? 0)));
        $title = trim((string)($parts[2] ?? ''));
        if (!$catId || $price <= 0) {
            tg('sendMessage', ['chat_id'=>$chatId,'text'=>'❌ فرمت نامعتبر است. نمونه: 1,250000,سرویس یک کاربره']);
            exit('OK');
        }
        $st=$pdo->prepare("SELECT * FROM plan_categories WHERE id=? AND deleted_at IS NULL LIMIT 1");
        $st->execute([$catId]);
        $cat=$st->fetch(PDO::FETCH_ASSOC);
        if (!$cat) { clearUserMode($chatId); tg('sendMessage', ['chat_id'=>$chatId,'text'=>'❌ دسته پلن پیدا نشد.']); exit('OK'); }
        $trafficType = $cat['traffic_type'] ?: 'unlimited';
        $gb = $trafficType === 'limited' ? (int)$cat['traffic_limit_gb'] : null;
        if ($title === '') {
            $trafficLabel = $trafficType === 'limited' ? ('حجمی '.$gb.' گیگ') : 'نامحدود';
            $title = 'سرویس '.$cat['name'].' - '.$trafficLabel.' '.gemaccServiceLabelFromRow($cat);
        }
        $serviceType = gemaccServiceTypeFromHint((string)($cat['service_type'] ?? 'openvpn_l2tp'), (string)(($cat['service_name'] ?? '').' '.($cat['name'] ?? '').' '.$title));
        if ($serviceType === 'v2ray') $usersCount = 1;
        $serviceLabel = gemaccServiceLabelFromRow($cat);
        $code=gemaccServiceCodePrefix($serviceType).$cat['code'].'_'.$usersCount.'_'.time();
        if ($serviceType !== 'openvpn_l2tp' && trim((string)$title) === '') { $title = 'اشتراک '.$serviceLabel.' '.$cat['name'].' | '.$usersCount.' کاربر'; }
        try {
            $st=$pdo->prepare("INSERT INTO plans(category_id,code,title,duration,months,seats,user_count,protocol,service_name,traffic_type,traffic_limit_gb,price,purchase_price,is_active,display_order) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,1,0)");
            $st->execute([$catId,$code,$title,$cat['code'],(int)$cat['months'],$usersCount,$usersCount,$serviceType,$serviceLabel,$trafficType,$gb,$price,0]);
            clearUserMode($chatId);
            tg('sendMessage', ['chat_id'=>$chatId,'text'=>'✅ پلن فروش ذخیره شد.','reply_markup'=>json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE)]);
        } catch (Throwable $e) {
            tg('sendMessage', ['chat_id'=>$chatId,'text'=>'❌ خطا در ذخیره پلن: '.$e->getMessage()]);
        }
        exit('OK');
    }

    // حالت ویرایش قیمت پلن (ادمین)
    if ($mode && $mode['mode'] === 'ADMIN_EDIT_PLAN_PRICE' && isAdmin($chatId)) {
        $planId = $mode['payload']['plan_id'] ?? null;
        $newPrice = (int)str_replace([',', '٫', ' ', '،'], '', $text);
        
        if ($planId && $newPrice > 0) {
            $plans->updatePrice($planId, $newPrice);
            
            clearUserMode($chatId);
            
            tg('sendMessage', [
                'chat_id'      => $chatId,
                'text'         => "✅ قیمت پلن به " . money($newPrice) . " تغییر کرد.",
                'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
            ]);
        } else {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "❌ قیمت نامعتبر است. لطفاً یک عدد وارد کنید.",
            ]);
        }
        exit('OK');
    }
    

    if ($mode && ($mode['mode'] ?? '') === 'ADMIN_DISCOUNT_BROADCAST' && isAdmin($chatId)) {
        $payload=$mode['payload'] ?? []; $discountId=(int)($payload['discount_id']??0); $target=$payload['target']??'all';
        $d=$discountCode->getById($discountId); if(!$d){ clearUserMode($chatId); tg('sendMessage',['chat_id'=>$chatId,'text'=>'❌ کد تخفیف پیدا نشد.']); exit('OK'); }
        $msg=trim((string)($caption ?: $text)); $photoId=$photo ? end($photo)['file_id'] : '';
        if($target==='buyers') $q="SELECT DISTINCT u.id FROM users u JOIN orders o ON o.user_id=u.id AND o.status='DELIVERED' WHERE COALESCE(u.is_blocked,0)=0";
        elseif($target==='non_buyers') $q="SELECT u.id FROM users u LEFT JOIN orders o ON o.user_id=u.id AND o.status='DELIVERED' WHERE o.id IS NULL AND COALESCE(u.is_blocked,0)=0";
        else $q="SELECT id FROM users WHERE COALESCE(is_blocked,0)=0";
        $users=$pdo->query($q)->fetchAll(PDO::FETCH_COLUMN);
        $html="🎁 <b>کد تخفیف ویژه GEM ACC</b>

".($msg!==''?htmlspecialchars($msg,ENT_QUOTES,'UTF-8')."

":'')."🎫 کد تخفیف قابل کپی:
<code>".htmlspecialchars($d['code'],ENT_QUOTES,'UTF-8')."</code>";
        $sent=0;$failed=0;
        foreach($users as $uid){
            if($photoId){ $res=tg('sendPhoto',['chat_id'=>(int)$uid,'photo'=>$photoId,'caption'=>$html,'parse_mode'=>'HTML']); }
            else { $res=tg('sendMessage',['chat_id'=>(int)$uid,'text'=>$html,'parse_mode'=>'HTML']); }
            if($res && !empty($res['ok'])) $sent++; else $failed++;
            if(($sent+$failed)%25===0) usleep(250000);
        }
        clearUserMode($chatId);
        tg('sendMessage',['chat_id'=>$chatId,'text'=>"✅ ارسال کد تخفیف انجام شد.
موفق: {$sent}
ناموفق: {$failed}",'reply_markup'=>json_encode(kbAdminPanel(),JSON_UNESCAPED_UNICODE)]);
        gemaccAdminActionNotify('ارسال گروهی کد تخفیف انجام شد', ['کد'=>$d['code'] ?? $discountId, 'گروه هدف'=>$target, 'ارسال موفق'=>$sent, 'ناموفق'=>$failed], $chatId);
        exit('OK');
    }

    // حالت افزودن کد تخفیف (ادمین)
    if ($mode && $mode['mode'] === 'ADMIN_ADD_DISCOUNT' && isAdmin($chatId)) {
        $step = $mode['payload']['step'] ?? 'code';
        $data = $mode['payload'] ?? [];
        
        if ($step === 'code') {
            $code = strtoupper(trim($text));
            if (strlen($code) < 3) {
                tg('sendMessage', ['chat_id' => $chatId, 'text' => '❌ کد باید حداقل ۳ کاراکتر باشد.']);
                exit('OK');
            }
            if ($discountCode->getByCode($code)) {
                tg('sendMessage', ['chat_id' => $chatId, 'text' => '❌ این کد قبلاً ثبت شده است.']);
                exit('OK');
            }
            
            $data['code'] = $code;
            $data['step'] = 'type';
            setUserMode($chatId, 'ADMIN_ADD_DISCOUNT', $data);
            
            tg('sendMessage', [
                'chat_id'      => $chatId,
                'text'         => "✅ کد: `{$code}`\n\nنوع تخفیف را انتخاب کنید:",
                'parse_mode'   => 'Markdown',
                'reply_markup' => json_encode([
                    'inline_keyboard' => [
                        [['text' => '📊 درصدی', 'callback_data' => 'DISCOUNT_TYPE_PERCENT']],
                        [['text' => '💰 مبلغ ثابت', 'callback_data' => 'DISCOUNT_TYPE_FIXED']],
                    ],
                ], JSON_UNESCAPED_UNICODE),
            ]);
        } elseif ($step === 'value') {
            $value = (int)str_replace([',', '٫', ' ', '،'], '', $text);
            if ($value <= 0) {
                tg('sendMessage', ['chat_id' => $chatId, 'text' => '❌ مقدار باید بیشتر از صفر باشد.']);
                exit('OK');
            }
            
            $data['value'] = $value;
            $data['step'] = 'payment_scope';
            setUserMode($chatId, 'ADMIN_ADD_DISCOUNT', $data);
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text' => "این کد تخفیف برای کدام روش پرداخت فعال باشد؟",
                'reply_markup' => json_encode(['inline_keyboard'=>[
                    [['text'=>'همه روش‌ها','callback_data'=>'DISCOUNT_SCOPE_ALL']],
                    [['text'=>'فقط کارت به کارت','callback_data'=>'DISCOUNT_SCOPE_CARD']],
                    [['text'=>'فقط ارز دیجیتال','callback_data'=>'DISCOUNT_SCOPE_CRYPTO']],
                ]], JSON_UNESCAPED_UNICODE),
            ]);
        }
        exit('OK');
    }
    
    // حالت تغییر درصد بونوس (ادمین)
    if ($mode && $mode['mode'] === 'ADMIN_EDIT_BONUS_PERCENT' && isAdmin($chatId)) {
        $percent = (int)$text;
        if ($percent < 1 || $percent > 100) {
            tg('sendMessage', ['chat_id' => $chatId, 'text' => '❌ درصد باید بین ۱ تا ۱۰۰ باشد.']);
            exit('OK');
        }
        
        setSetting('wallet_bonus_percent', (string)$percent);
        clearUserMode($chatId);
        
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => "✅ درصد بونوس به {$percent}٪ تغییر کرد.",
            'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    
    // حالت تغییر حداقل مبلغ بونوس (ادمین)
    if ($mode && $mode['mode'] === 'ADMIN_EDIT_BONUS_MIN' && isAdmin($chatId)) {
        $amount = (int)str_replace([',', '٫', ' ', '،'], '', $text);
        if ($amount < 10000) {
            tg('sendMessage', ['chat_id' => $chatId, 'text' => '❌ حداقل مبلغ باید ۱۰,۰۰۰ تومان یا بیشتر باشد.']);
            exit('OK');
        }
        
        setSetting('wallet_bonus_min_amount', (string)$amount);
        clearUserMode($chatId);
        
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => "✅ حداقل مبلغ بونوس به " . money($amount) . " تغییر کرد.",
            'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    
    // حالت تغییر درصد کش‌بک (ادمین)
    if ($mode && $mode['mode'] === 'ADMIN_EDIT_CASHBACK_PERCENT' && isAdmin($chatId)) {
        $percent = (int)$text;
        if ($percent < 1 || $percent > 100) {
            tg('sendMessage', ['chat_id' => $chatId, 'text' => '❌ درصد باید بین ۱ تا ۱۰۰ باشد.']);
            exit('OK');
        }
        
        setSetting('cashback_percent', (string)$percent);
        clearUserMode($chatId);
        
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => "✅ درصد کش‌بک به {$percent}٪ تغییر کرد.",
            'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    

    if ($mode && $mode['mode'] === 'BOT_ORDER_DISCOUNT') {
        $planId=(int)($mode['payload']['plan_id'] ?? 0);
        $plan=$plans->getById($planId);
        if(!$plan){ clearUserMode($chatId); tg('sendMessage',['chat_id'=>$chatId,'text'=>'❌ پلن یافت نشد.']); exit('OK'); }
        $code=function_exists('gemacc_normalize_discount_code') ? gemacc_normalize_discount_code($text) : strtoupper(trim($text));
        $discount=['valid'=>false,'discount_amount'=>0,'final_price'=>(int)$plan['price']];
        if($code !== '' && function_exists('gemacc_discount_preview')) $discount=gemacc_discount_preview($pdo,(int)$chatId,$code,(int)$plan['price'],(int)$planId);
        if(empty($discount['valid'])){
            clearUserMode($chatId);
            tg('sendMessage',['chat_id'=>$chatId,'text'=>'کد تخفیف نداری توی جشنواره بعدی حتما واست ارسال میشه❤️']);
            gemaccShowBotPaymentOptions((int)$chatId, 0, $plan, (int)$plan['price'], null, 0);
            exit('OK');
        }
        setUserMode($chatId,'BOT_ORDER_DISCOUNT_APPLIED',['plan_id'=>$planId,'discount_code'=>$code,'discount_amount'=>(int)($discount['discount_amount']??0),'final_price'=>(int)($discount['final_price']??$plan['price'])]);
        tg('sendMessage',['chat_id'=>$chatId,'text'=>'✅ کد تخفیف اعمال شد. حالا روش پرداخت را انتخاب کنید.']);
        gemaccShowBotPaymentOptions((int)$chatId, 0, $plan, (int)($discount['final_price']??$plan['price']), $code, (int)($discount['discount_amount']??0));
        exit('OK');
    }

    // حالت افزایش موجودی با کریپتو
    if ($mode && $mode['mode'] === 'WALLET_DEPOSIT_STARS') {
        $amount = (int)str_replace([',', '٫', ' ', '،'], '', $text);
        if ($amount < 10000) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "❌ حداقل مبلغ شارژ ۱۰,۰۰۰ تومان است.",
                'reply_markup' => json_encode(['inline_keyboard' => [[['text' => '◀️ بازگشت', 'callback_data' => 'ADD_BALANCE']]]], JSON_UNESCAPED_UNICODE),
            ]);
            exit('OK');
        }
        gemaccBotStartStarsWalletTopup((int)$chatId, $amount, 0, null);
        exit('OK');
    }

    if ($mode && $mode['mode'] === 'WALLET_DEPOSIT_CRYPTO') {
        $amount = (int)str_replace([',', '٫', ' ', '،'], '', $text);
        if ($amount < 10000) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "❌ حداقل مبلغ واریز ۱۰,۰۰۰ تومان است.",
                'reply_markup' => json_encode(['inline_keyboard' => [[['text' => '◀️ بازگشت', 'callback_data' => 'WALLET']]]], JSON_UNESCAPED_UNICODE),
            ]);
            exit('OK');
        }
        tg('sendMessage', ['chat_id' => $chatId, 'text' => '⏳ در حال ساخت فاکتور کریپتو...']);
        $res = gemaccCreateWalletCryptoInvoice((int)$chatId, $amount);
        clearUserMode($chatId);
        if (empty($res['ok'])) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text' => '❌ ' . ($res['error'] ?? 'خطا در ساخت فاکتور کریپتو'),
                'reply_markup' => json_encode(['inline_keyboard' => [[['text' => '🔁 تلاش دوباره', 'callback_data' => 'ADD_BALANCE_CRYPTO'], ['text' => '◀️ بازگشت', 'callback_data' => 'WALLET']]]], JSON_UNESCAPED_UNICODE),
            ]);
            exit('OK');
        }
        $kb = [];
        if (!empty($res['payment_url'])) $kb[] = [['text' => '🚀 باز کردن صفحه پرداخت', 'url' => $res['payment_url']]];
        $kb[] = [['text' => '◀️ بازگشت به کیف پول', 'callback_data' => 'WALLET']];
        tg('sendMessage', [
            'chat_id' => $chatId,
            'text' => gemaccCryptoInvoiceText($res, $amount),
            'parse_mode' => 'Markdown',
            'reply_markup' => json_encode(['inline_keyboard' => $kb], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    // حالت افزایش موجودی
    if ($mode && $mode['mode'] === 'WALLET_DEPOSIT') {
        $amount = (int)str_replace([',', '٫', ' ', '،'], '', $text);
        
        if ($amount < 10000) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "❌ حداقل مبلغ واریز ۱۰,۰۰۰ تومان است.",
                'reply_markup' => json_encode([
                    'inline_keyboard' => [[['text' => '◀️ بازگشت', 'callback_data' => 'WALLET']]],
                ], JSON_UNESCAPED_UNICODE),
            ]);
            exit('OK');
        }
        
        $payableAmount = $amount;
        setUserMode($chatId, 'WALLET_DEPOSIT_RECEIPT', ['amount' => $amount, 'payable_amount'=>$payableAmount]);
        
        $paymentCard = function_exists('gemaccGetPaymentCard') ? gemaccGetPaymentCard(true) : ['number'=>getCardNumber(),'owner'=>getSetting('card_owner_name',''),'bank'=>''];
        $cardNumber = (string)($paymentCard['number'] ?? getCardNumber());
        $cardOwner = (string)($paymentCard['owner'] ?? getSetting('card_owner_name', ''));
        $cardBank = (string)($paymentCard['bank'] ?? '');
        
        $txt = "💳 **افزایش موجودی**\n\n";
        $txt .= "━━━━━━━━━━━━━━━━━━━━\n";
        $txt .= "💰 مبلغ شارژ حساب: " . money($amount) . "\n";
        $txt .= gemaccCardPayNotice($payableAmount, $amount);
        $txt .= "━━━━━━━━━━━━━━━━━━━━\n\n";
        $txt .= "📋 شماره کارت:\n`{$cardNumber}`\n";
        if (!empty($cardBank)) { $txt .= "🏦 بانک: {$cardBank}\n"; }
        if ($cardOwner) {
            $txt .= "👤 به نام: {$cardOwner}\n";
        }
        $txt .= "\n📸 لطفاً مبلغ را واریز کرده و:\n";
        $txt .= "• **عکس رسید** را ارسال کنید\n";
        $txt .= "• یا **شماره پیگیری/توضیحات** را بنویسید";
        
        tg('sendMessage', [
            'chat_id'    => $chatId,
            'text'       => $txt,
            'parse_mode' => 'Markdown',
            'reply_markup' => json_encode([
                'inline_keyboard' => [[['text' => '❌ انصراف', 'callback_data' => 'WALLET']]],
            ], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    
    // حالت دریافت رسید واریز (عکس یا متن)
    if ($mode && $mode['mode'] === 'WALLET_DEPOSIT_RECEIPT') {
        // بررسی ارسال عکس یا متن رسید
        $hasPhoto = !empty($photo);
        $hasText = !empty($text);
        
        if (!$hasPhoto && !$hasText) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "❌ لطفاً عکس رسید یا شماره پیگیری/توضیحات متنی را ارسال کنید.",
            ]);
            exit('OK');
        }

        if ($hasText && gemaccIsBotNavigationText($text)) {
            clearUserMode($chatId);
            sendMainMenu($chatId, '✅ عملیات قبلی متوقف شد. لطفاً دستور جدید را دوباره انتخاب کنید.');
            exit('OK');
        }
        
        $amount      = $mode['payload']['amount'];
        $depositPaymentMethod = $mode['payload']['payment_method'] ?? 'card';
        $photoId     = $hasPhoto ? end($photo)['file_id'] : null;
        $receiptText = $hasText ? $text : null;
        $payableAmount = (int)($mode['payload']['payable_amount'] ?? $amount);
        if ($payableAmount > 0 && $payableAmount !== (int)$amount) {
            $receiptText = trim((string)$receiptText . "\nمبلغ دقیق اعلام‌شده برای واریز: " . number_format($payableAmount) . " تومان (" . ($payableAmount*10) . " ریال)");
        }

        // اگر رسید تصویری است، آن را داخل mini_app/uploads ذخیره کن تا در پنل هم دیده شود
        $storedReceipt = null;
        if ($hasPhoto && $photoId) {
            $storedReceipt = saveTelegramFileToUploads($photoId, 'deposit', (int)$chatId);
        }

        // در دیتابیس بهتر است نام فایل ذخیره شود (اگر ذخیره نشد، file_id تلگرام ذخیره می‌شود)
        if ($depositPaymentMethod === 'crypto') {
            $stmt=$pdo->prepare("INSERT INTO wallet_transactions(user_id,amount,type,status,payment_method,currency,description,receipt_file_id,receipt_text) VALUES(?,?, 'DEPOSIT','PENDING','crypto','IRT','شارژ حساب کاربری با ارز دیجیتال',?,?)");
            $stmt->execute([$chatId,$amount,$storedReceipt ?: $photoId,$receiptText]);
            $txId=(int)$pdo->lastInsertId();
            $pdo->prepare("INSERT INTO crypto_payments(user_id,wallet_transaction_id,amount,currency,crypto_currency,network,wallet_address,provider,payment_type,status,receipt_file_id,receipt_text) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)")->execute([$chatId,$txId,$amount,'IRT','USDT/TRX','TRON',getSetting('manual_crypto_usdt_address', getSetting('manual_crypto_trx_address','')),'ManualCrypto','wallet_topup','PENDING',$storedReceipt ?: $photoId,$receiptText]);
        } else {
            $txId = $wallet->createDepositRequest($chatId, $amount, $storedReceipt ?: $photoId, $receiptText);
        }
        
        clearUserMode($chatId);
        
        // اطلاع به کاربر
        $receiptType = ($depositPaymentMethod === 'crypto' ? 'ارز دیجیتال ' : '') . ($hasPhoto ? '(با تصویر رسید)' : '(با رسید متنی)');
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => "✅ درخواست شما ثبت شد {$receiptType}\n\n💵 مبلغ: " . money($amount) . "\n\nپس از تأیید ادمین، موجودی به کیف پول شما اضافه می‌شود.",
            'reply_markup' => json_encode(kbMainMenu(), JSON_UNESCAPED_UNICODE),
        ]);
        
        // اطلاع به ادمین
        $userName = ($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? '');
        $userLink = ($user['username'] ?? '') ? '@' . $user['username'] : '-';
        
        // نکته: برای جلوگیری از خطای parse_mode و از دست رفتن نوتیفیکیشن، پیام ادمین را بدون Markdown می‌فرستیم
        $adminMsg  = "💰 درخواست واریز جدید #{$txId}\n\n";
        $adminMsg .= "━━━━━━━━━━━━━━━━━━━━\n";
        $adminMsg .= "👤 نام: {$userName}\n";
        $adminMsg .= "🆔 آیدی عددی: {$chatId}\n";
        $adminMsg .= "📱 یوزرنیم: {$userLink}\n";
        $adminMsg .= "━━━━━━━━━━━━━━━━━━━━\n";
        $adminMsg .= "💵 مبلغ: " . money($amount) . "\n";
        $adminMsg .= "💳 روش: " . ($depositPaymentMethod === 'crypto' ? 'ارز دیجیتال' : 'کارت به کارت') . "\n";
        if ($receiptText) {
            $adminMsg .= "📝 رسید متنی:\n{$receiptText}\n";
        }
        if ($storedReceipt) {
            $adminMsg .= "🖼️ رسید تصویری در پنل: /mini_app/uploads/{$storedReceipt}\n";
        }
        $adminMsg .= "━━━━━━━━━━━━━━━━━━━━";
        
        $adminButtons = [
            'inline_keyboard' => [
                [
                    ['text' => '✅ تأیید واریز', 'callback_data' => "ADMIN_APPROVE_DEPOSIT__{$txId}"],
                    ['text' => '❌ رد واریز', 'callback_data' => "ADMIN_REJECT_DEPOSIT__{$txId}"],
                ],
            ],
        ];
        
        $admins = array_filter(array_map('trim', explode(',', ADMIN_IDS)));
        foreach ($admins as $adminId) {
            if ($hasPhoto) {
                // ارسال با عکس
                tg('sendPhoto', [
                    'chat_id'      => $adminId,
                    'photo'        => $photoId,
                    'caption'      => $adminMsg,
                    'reply_markup' => json_encode($adminButtons, JSON_UNESCAPED_UNICODE),
                ]);
            } else {
                // ارسال بدون عکس (فقط متن)
                tg('sendMessage', [
                    'chat_id'      => $adminId,
                    'text'         => $adminMsg,
                    'reply_markup' => json_encode($adminButtons, JSON_UNESCAPED_UNICODE),
                ]);
            }
        }
        
        logError("WALLET_DEPOSIT_RECEIPT: txId={$txId}, amount={$amount}, hasPhoto=" . ($hasPhoto ? 'yes' : 'no') . ", stored=" . ($storedReceipt ? 'yes' : 'no') . ", admins=" . count($admins));
        
        exit('OK');
    }
    
    // حالت پرداخت کارت به کارت (عکس یا متن)
    if ($mode && $mode['mode'] === 'ORDER_RECEIPT') {
        // بررسی ارسال عکس یا متن رسید
        $hasPhoto = !empty($photo);
        $hasText = !empty($text);
        
        if (!$hasPhoto && !$hasText) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "❌ لطفاً عکس رسید یا شماره پیگیری/توضیحات متنی را ارسال کنید.",
            ]);
            exit('OK');
        }
        if ($hasText && gemaccIsBotNavigationText($text)) {
            clearUserMode($chatId);
            sendMainMenu($chatId, '✅ عملیات قبلی متوقف شد. لطفاً دستور جدید را دوباره انتخاب کنید.');
            exit('OK');
        }
        
        $planId      = $mode['payload']['plan_id'];
        $photoId     = $hasPhoto ? end($photo)['file_id'] : null;
        $receiptText = $hasText ? $text : null;
        $plan        = $plans->getById($planId);
        $discountCode = (string)($mode['payload']['discount_code'] ?? '');
        $discountAmount = (int)($mode['payload']['discount_amount'] ?? 0);
        $finalPrice = (int)($mode['payload']['final_price'] ?? ($plan['price'] ?? 0));
        $walletCreditUsed = (int)($mode['payload']['wallet_credit_used'] ?? 0);
        $orderPaymentMethod = strtoupper((string)($mode['payload']['payment_method'] ?? 'CARD'));
        
        if (!$plan) {
            clearUserMode($chatId);
            tg('sendMessage', ['chat_id' => $chatId, 'text' => '❌ پلن یافت نشد.']);
            exit('OK');
        }
        
        // اگر رسید تصویری است، آن را داخل mini_app/uploads ذخیره کن تا در پنل هم دیده شود
        $storedReceipt = null;
        if ($hasPhoto && $photoId) {
            $storedReceipt = saveTelegramFileToUploads($photoId, 'receipt', (int)$chatId);
        }

        if ($walletCreditUsed > 0) {
            try {
                $pdo->beginTransaction();
                $actualWalletUsed = gemaccBotDeductWalletCredit($pdo, (int)$chatId, $walletCreditUsed, 'کسر موجودی کیف پول برای سفارش ربات');
                $pdo->commit();
                $walletCreditUsed = $actualWalletUsed;
            } catch (Throwable $e) { if($pdo->inTransaction()) $pdo->rollBack(); clearUserMode($chatId); tg('sendMessage',['chat_id'=>$chatId,'text'=>'❌ خطا در کسر موجودی کیف پول: '.$e->getMessage()]); exit('OK'); }
        }

        $orderId = $orders->create(
            $chatId,
            $plan['duration'],
            $plan['seats'],
            $finalPrice,
            $orderPaymentMethod,
            $storedReceipt ?: $photoId,
            json_encode(['note'=>$receiptText,'discount_code'=>$discountCode,'discount_amount'=>$discountAmount,'original_price'=>(int)$plan['price'],'original_final_price'=>(int)($mode['payload']['original_final_price'] ?? $finalPrice),'wallet_credit_used'=>$walletCreditUsed,'payable_amount'=>(int)($mode['payload']['payable_amount'] ?? $finalPrice)], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)
        );
        try {
            $trafficType = $plan['traffic_type'] ?? $plan['category_traffic_type'] ?? 'unlimited';
            $trafficLimitGb = ($trafficType === 'limited') ? ($plan['traffic_limit_gb'] ?? $plan['category_traffic_limit_gb'] ?? null) : null;
            $serviceType = gemaccServiceTypeFromHint((string)($plan['protocol'] ?? ($plan['service_type'] ?? 'openvpn_l2tp')), (string)($plan['title'] ?? ''));
            $pdo->prepare("UPDATE orders SET plan_id=?, category_id=?, plan_code=?, plan_title=?, service_type=?, months=?, required_accounts=?, traffic_type=?, traffic_limit_gb=? WHERE id=?")
                ->execute([(int)($plan['id'] ?? 0), $plan['category_id'] ?? null, $plan['code'] ?? null, $plan['title'] ?? 'OpenVPN | L2TP', $serviceType, (int)($plan['months'] ?? $plan['category_months'] ?? 1), max(1,(int)($plan['seats'] ?? $plan['user_count'] ?? 1)), $trafficType, $trafficLimitGb, (int)$orderId]);
        } catch (Throwable $e) { error_log('[bot order metadata] '.$e->getMessage()); }
        if ($orderPaymentMethod === 'CRYPTO') {
            $asset = gemaccCryptoNormalizeAsset((string)($mode['payload']['crypto_asset'] ?? 'USDT')) ?: 'USDT';
            try { $cw = gemaccCryptoWalletForAsset($asset); } catch (Throwable $e) { $cw=['asset'=>$asset,'network'=>'TRON','address'=>'']; }
            $pdo->prepare("INSERT INTO crypto_payments(order_id,user_id,amount,currency,crypto_currency,network,wallet_address,provider,payment_type,status,receipt_file_id,receipt_text) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)")->execute([$orderId,$chatId,$finalPrice,'IRT',$cw['asset'],$cw['network'],$cw['address'],'ManualCrypto','order','PENDING',$storedReceipt ?: $photoId,$receiptText]);
        }
        if($discountCode && function_exists('gemacc_discount_preview') && function_exists('gemacc_discount_apply')){ $d=gemacc_discount_preview($pdo,(int)$chatId,$discountCode,(int)$plan['price'],(int)$planId, 'wallet'); if(!empty($d['valid'])) gemacc_discount_apply($pdo,$d,(int)$chatId,(int)$orderId); }
        
        clearUserMode($chatId);
        
        // اطلاع به کاربر
        $receiptType = ($orderPaymentMethod === 'CRYPTO' ? 'ارز دیجیتال ' : '') . ($hasPhoto ? '(با تصویر رسید)' : '(با رسید متنی)');
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => "✅ سفارش شما ثبت شد {$receiptType}\n\n🛒 سفارش: #{$orderId}\n📦 پلن: {$plan['title']}\n💵 مبلغ: " . money($finalPrice) . ($walletCreditUsed>0 ? "\n💎 کسر از کیف پول: " . money($walletCreditUsed) : "") . "\n\nپس از تأیید ادمین، اشتراک برای شما ارسال می‌شود.",
            'reply_markup' => json_encode(kbMainMenu(), JSON_UNESCAPED_UNICODE),
        ]);
        
        // اطلاع به ادمین
        $userName = ($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? '');
        $userLink = ($user['username'] ?? '') ? '@' . $user['username'] : '-';
        
        // نکته: برای جلوگیری از خطای parse_mode و از دست رفتن نوتیفیکیشن، پیام ادمین را بدون Markdown می‌فرستیم
        $adminMsg  = "🛒 سفارش جدید #{$orderId}\n\n";
        $adminMsg .= "━━━━━━━━━━━━━━━━━━━━\n";
        $adminMsg .= "👤 نام: {$userName}\n";
        $adminMsg .= "🆔 آیدی عددی: {$chatId}\n";
        $adminMsg .= "📱 یوزرنیم: {$userLink}\n";
        $adminMsg .= "━━━━━━━━━━━━━━━━━━━━\n";
        $adminMsg .= "📦 پلن: {$plan['title']}\n";
        $adminMsg .= "💵 مبلغ: " . money($finalPrice) . "\n";
        $adminMsg .= "💳 روش: " . ($orderPaymentMethod === 'CRYPTO' ? 'ارز دیجیتال' : 'کارت به کارت') . "\n";
        if ($receiptText) {
            $adminMsg .= "📝 رسید متنی:\n{$receiptText}\n";
        }
        if ($storedReceipt) {
            $adminMsg .= "🖼️ رسید تصویری در پنل: /mini_app/uploads/{$storedReceipt}\n";
        }
        $adminMsg .= "━━━━━━━━━━━━━━━━━━━━";
        
        $adminButtons = [
            'inline_keyboard' => [
                [
                    ['text' => '✅ تأیید سفارش', 'callback_data' => "ADMIN_APPROVE_ORDER__{$orderId}"],
                    ['text' => '❌ رد سفارش', 'callback_data' => "ADMIN_REJECT_ORDER__{$orderId}"],
                ],
                [
                    ['text' => '⚠️ اخطار فیش فیک', 'callback_data' => "ADMIN_FAKE_RECEIPT_ORDER__{$orderId}"],
                ],
            ],
        ];
        
        $admins = array_filter(array_map('trim', explode(',', ADMIN_IDS)));
        foreach ($admins as $adminId) {
            if ($hasPhoto) {
                // ارسال با عکس
                tg('sendPhoto', [
                    'chat_id'      => $adminId,
                    'photo'        => $photoId,
                    'caption'      => $adminMsg,
                    'reply_markup' => json_encode($adminButtons, JSON_UNESCAPED_UNICODE),
                ]);
            } else {
                // ارسال بدون عکس (فقط متن)
                tg('sendMessage', [
                    'chat_id'      => $adminId,
                    'text'         => $adminMsg,
                    'reply_markup' => json_encode($adminButtons, JSON_UNESCAPED_UNICODE),
                ]);
            }
        }

        logError("ORDER_RECEIPT: orderId={$orderId}, planId={$planId}, hasPhoto=" . ($hasPhoto ? 'yes' : 'no') . ", stored=" . ($storedReceipt ? 'yes' : 'no') . ", admins=" . count($admins));
        
        exit('OK');
    }
    
    // حالت پیام همگانی
    if ($mode && $mode['mode'] === 'ADMIN_BROADCAST' && isAdmin($chatId)) {
        $messageText = $text ?? '';
        $broadcastPhoto = $photo ? end($photo)['file_id'] : null;
        
        // اگر عکس فوروارد شده با caption
        if (!$messageText && isset($msg['caption'])) {
            $messageText = $msg['caption'];
        }
        
        // پردازش توکن‌ها - هر دکمه یک ردیف جدا (عمودی)
        $buttons = [];
        if (strpos($messageText, '/dash') !== false) {
            $messageText = str_replace('/dash', '', $messageText);
            $buttons[] = [['text' => '💰 کیف پول من', 'callback_data' => 'WALLET']];
        }
        if (strpos($messageText, '/plans') !== false) {
            $messageText = str_replace('/plans', '', $messageText);
            $buttons[] = [['text' => '🛒 خرید اشتراک', 'callback_data' => 'BUY']];
        }
        if (strpos($messageText, '/sup') !== false) {
            $messageText = str_replace('/sup', '', $messageText);
            $buttons[] = [['text' => '💬 پشتیبانی', 'callback_data' => 'SUPPORT']];
        }
        if (strpos($messageText, '/app') !== false) {
            $messageText = str_replace('/app', '', $messageText);
            $miniappUrl = defined('MINIAPP_URL') ? MINIAPP_URL : '';
            if ($miniappUrl) {
                $buttons[] = [['text' => '📲 ورود به مینی‌اپ', 'web_app' => ['url' => $miniappUrl]]];
            }
        }
        
        $messageText = trim($messageText);
        
        if (empty($messageText) && !$broadcastPhoto) {
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "❌ پیام نمی‌تواند خالی باشد.",
            ]);
            exit('OK');
        }
        
        clearUserMode($chatId);
        
        // گرفتن لیست کاربران
        $stmt = $pdo->query("SELECT id FROM users");
        $users = $stmt->fetchAll(PDO::FETCH_COLUMN);
        
        $successCount = 0;
        $failedCount = 0;
        
        foreach ($users as $userId) {
            // اگر عکس داشت، ارسال با عکس
            if ($broadcastPhoto) {
                $params = [
                    'chat_id'    => $userId,
                    'photo'      => $broadcastPhoto,
                    'caption'    => $messageText,
                    'parse_mode' => 'Markdown',
                ];
                
                if (!empty($buttons)) {
                    $params['reply_markup'] = json_encode(['inline_keyboard' => $buttons], JSON_UNESCAPED_UNICODE);
                }
                
                $result = tg('sendPhoto', $params);
            } else {
                $params = [
                    'chat_id'    => $userId,
                    'text'       => $messageText,
                    'parse_mode' => 'Markdown',
                ];
                
                if (!empty($buttons)) {
                    $params['reply_markup'] = json_encode(['inline_keyboard' => $buttons], JSON_UNESCAPED_UNICODE);
                }
                
                $result = tg('sendMessage', $params);
            }
            
            if ($result && isset($result['ok']) && $result['ok']) {
                $successCount++;
            } else {
                $failedCount++;
            }
            
            usleep(50000); // 50ms delay
        }
        
        $photoNote = $broadcastPhoto ? ' (با تصویر)' : '';
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => "📢 **پیام همگانی ارسال شد{$photoNote}**\n\n✅ موفق: {$successCount}\n❌ ناموفق: {$failedCount}",
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    
    // ═══════════════════ دستورات اصلی ═══════════════════
    
    if ($text === '/start' || preg_match('/^\/start\s+ref[_-]?\d+/i', trim((string)$text))) {
        sendMainMenu($chatId);
        exit('OK');
    }
    
    if ($text === '/admin' && isAdmin($chatId)) {
        tg('sendMessage', [
            'chat_id'      => $chatId,
            'text'         => "👑 **پنل مدیریت**\n\nگزینه مورد نظر را انتخاب کنید:",
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    
    // Note: clearUserMode برای دکمه‌های منو در بالا انجام شده است
    
    // دکمه‌های منو
    switch ($text) {
        case '🛒 خرید':
        case '🛒 خرید فیلترشکن':
        case '🟩 🛒 خرید فیلترشکن':
        case '🛍️ خرید اشتراک':
            $txt = "🛒 **خرید سرویس GEM ACC**\n\n";
            $txt .= "برای تهیه فیلترشکن، سرویس مورد نظر خود را از گزینه‌های زیر انتخاب کنید.\n";
            $txt .= "ابتدا نوع سرویس را انتخاب کنید؛ سپس پلن‌های همان سرویس نمایش داده می‌شوند.";
            
            tg('sendMessage', [
                'chat_id'      => $chatId,
                'text'         => $txt,
                'parse_mode'   => 'Markdown',
                'reply_markup' => json_encode(method_exists($plans, 'buildServiceKeyboard') ? $plans->buildServiceKeyboard() : $plans->buildDurationKeyboard(), JSON_UNESCAPED_UNICODE),
            ]);
            break;
            
        case '📦 سرویس‌های من':
        case '📡 فیلترشکن من':
        case '🟦 📡 فیلترشکن من':
        case '📲 سرویس‌های من':
            gemaccShowMyServices($chatId, null, 1);
            break;
            
        case '👤 حساب کاربری':
        case '💰 کیف پول':
        case '🟨 💰 کیف پول':
        case '💎 کیف پول':
            $balance = $wallet->getBalance($chatId);
            
            $txt = gemaccAccountText($chatId);
            
            $kb = [
                'inline_keyboard' => [
                    [['text' => '➕ افزایش موجودی', 'callback_data' => 'ADD_BALANCE']],
                    [['text' => '📜 تاریخچه تراکنش‌ها', 'callback_data' => 'WALLET_HISTORY']],
                    [['text' => '🎫 کدهای تخفیف من', 'callback_data' => 'MY_DISCOUNTS']],
                    [['text' => '◀️ بازگشت به منو', 'callback_data' => 'BACK_MAIN']],
                ],
            ];
            
            tg('sendMessage', [
                'chat_id'      => $chatId,
                'text'         => $txt,
                'parse_mode'   => 'Markdown',
                'reply_markup' => json_encode($kb, JSON_UNESCAPED_UNICODE),
            ]);
            break;
            
        case '📚 آموزش':
        case '🟩 ⚙️ 🧪 تست فیلترشکن':
        case '📚 آموزش':
        case '🟩 📚 آموزش':
        case '🎓 آموزش':
            $tutorialMsg = $tutorials->buildServiceSelectionMessage();
            tg('sendMessage', [
                'chat_id'      => $chatId,
                'text'         => $tutorialMsg['text'],
                'parse_mode'   => 'Markdown',
                'reply_markup' => json_encode($tutorialMsg['keyboard'], JSON_UNESCAPED_UNICODE),
            ]);
            break;
            
        case '❓ سوالات متداول':
        case '❔ سوالات متداول':
            gemaccShowFaqList($chatId, null, false);
            break;

        case '❓ درباره ما':
            $aboutText = getSetting('about_text', "🌐 فروشگاه GEM ACC\n\n✅ فروش سرویس‌های OpenVPN/L2TP، Windscribe، ExpressVPN و V2Ray\n✅ تحویل سریع و مدیریت سرویس‌ها داخل ربات و مینی‌اپ\n✅ پشتیبانی و آموزش اختصاصی هر سرویس");
            
            tg('sendMessage', [
                'chat_id'      => $chatId,
                'text'         => $aboutText,
                'parse_mode'   => 'Markdown',
                'reply_markup' => json_encode(kbMainMenu(), JSON_UNESCAPED_UNICODE),
            ]);
            break;
            
        case '💬 پشتیبانی':
        case '📱 پشتیبانی':
        case '🟥 📱 پشتیبانی':
        case '💭 پشتیبانی':
            $supportGroup = trim((string)getSetting('support_group', SUPPORT_GROUP));
            $rows = [];
            if ($supportGroup !== '') {
                $rows[] = [['text' => '👥 گروه پرسش و پاسخ', 'url' => $supportGroup]];
            }
            $rows[] = [['text' => '✉️ ارسال تیکت', 'callback_data' => 'SEND_SUPPORT_MESSAGE']];
            $rows[] = [['text' => '◀️ بازگشت به منو', 'callback_data' => 'BACK_MAIN']];
            $kb = ['inline_keyboard' => $rows];
            
            tg('sendMessage', [
                'chat_id'      => $chatId,
                'text'         => "💭 **پشتیبانی**\n\n📞 از گزینه‌های زیر استفاده کنید:",
                'parse_mode'   => 'Markdown',
                'reply_markup' => json_encode($kb, JSON_UNESCAPED_UNICODE),
            ]);
            break;
        
        case '👑 پنل مدیریت':
            if (isAdmin($chatId)) {
                tg('sendMessage', [
                    'chat_id'      => $chatId,
                    'text'         => "👑 **پنل مدیریت**\n\n🛠 گزینه مورد نظر را انتخاب کنید:",
                    'parse_mode'   => 'Markdown',
                    'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
                ]);
            } else {
                sendMainMenu($chatId);
            }
            break;
            
        default:
            sendMainMenu($chatId);
            break;
    }
    
    exit('OK');
}

// ════════════════════════════════════════════════════════════════════════════
// TELEGRAM STARS PAYMENT (v27.52)
// ════════════════════════════════════════════════════════════════════════════

// Step 1: approve the pre-checkout (must answer within ~10s or the payment fails).
if (isset($update['pre_checkout_query'])) {
    $pcq = $update['pre_checkout_query'];
    $pcqId = (string)($pcq['id'] ?? '');
    $payload = (string)($pcq['invoice_payload'] ?? '');
    $ok = false;
    $errText = 'پرداخت نامعتبر است.';

    try {
        if (preg_match('/^STARS_ORDER_(\d+)$/', $payload, $m)) {
            $orderId = (int)$m[1];
            $st = $pdo->prepare("SELECT id, user_id, status, payment_status, created_at, stars_expires_at FROM orders WHERE id=? AND payment_method='STARS' LIMIT 1");
            $st->execute([$orderId]);
            $ord = $st->fetch(PDO::FETCH_ASSOC);
            if (!$ord) {
                $errText = 'سفارش یافت نشد.';
            } elseif ((int)$ord['user_id'] !== (int)($pcq['from']['id'] ?? 0)) {
                $errText = 'این سفارش متعلق به شما نیست.';
            } elseif (strtolower((string)$ord['payment_status']) === 'paid') {
                $errText = 'این سفارش قبلاً پرداخت شده است.';
            } elseif (in_array((string)($ord['status'] ?? ''), ['CANCELLED','REJECTED'], true)) {
                $errText = 'این سفارش لغو شده است. لطفاً دوباره از صفحه پرداخت اقدام کنید.';
            } else {
                $expiresAt = !empty($ord['stars_expires_at']) ? strtotime((string)$ord['stars_expires_at']) : ((strtotime((string)($ord['created_at'] ?? 'now')) ?: time()) + (function_exists('gemaccStarsPendingAutoCancelSeconds') ? gemaccStarsPendingAutoCancelSeconds() : 15));
                if (time() >= $expiresAt) {
                    if (function_exists('gemaccStarsAutoCancelPendingOrder')) { gemaccStarsAutoCancelPendingOrder($pdo, $orderId, (int)$ord['user_id'], true, false); }
                    $errText = 'مهلت پرداخت این سفارش تمام شده است. لطفاً دوباره از صفحه پرداخت اقدام کنید.';
                } else {
                    $ok = true;
                }
            }
        } elseif (preg_match('/^STARS_WALLET_(\d+)$/', $payload, $mw)) {
            $txId = (int)$mw[1];
            $st = $pdo->prepare("SELECT id, user_id, status FROM wallet_transactions WHERE id=? AND payment_method='stars' LIMIT 1");
            $st->execute([$txId]);
            $tx = $st->fetch(PDO::FETCH_ASSOC);
            if (!$tx) {
                $errText = 'درخواست شارژ یافت نشد.';
            } elseif ((int)$tx['user_id'] !== (int)($pcq['from']['id'] ?? 0)) {
                $errText = 'این درخواست متعلق به شما نیست.';
            } elseif (strtoupper((string)$tx['status']) === 'COMPLETED') {
                $errText = 'این شارژ قبلاً انجام شده است.';
            } else {
                $ok = true;
            }
        }
    } catch (Throwable $e) {
        error_log('[stars pre_checkout] ' . $e->getMessage());
        $ok = false;
        $errText = 'خطای داخلی. لطفاً بعداً تلاش کنید.';
    }

    $params = ['pre_checkout_query_id' => $pcqId, 'ok' => $ok];
    if (!$ok) $params['error_message'] = $errText;
    tg('answerPreCheckoutQuery', $params);
    exit('OK');
}

// ════════════════════════════════════════════════════════════════════════════
// CALLBACK QUERY HANDLER
// ════════════════════════════════════════════════════════════════════════════

if (isset($update['callback_query'])) {
    $cb     = $update['callback_query'];
    $chatId = $cb['from']['id'];
    $data   = $cb['data'];
    $msgId  = $cb['message']['message_id'] ?? null;
    
    ensureUser($cb['from']);
    if (!isAdmin($chatId) && function_exists('gemaccUserIsBlocked') && gemaccUserIsBlocked((int)$chatId)) {
        tg('answerCallbackQuery', ['callback_query_id'=>$cb['id'], 'text'=>'حساب شما داخل ربات مسدود است.', 'show_alert'=>true]);
        exit('OK');
    }

    // آماده‌سازی پاسخ callback (برای جلوگیری از خطای "query is too old / invalid")
    $GLOBALS['__cb_answered'] = false;
    $GLOBALS['__cb_current']  = $cb;
    $GLOBALS['cb'] = $cb;
    register_shutdown_function(function () {
        if (!empty($GLOBALS['__cb_current']) && empty($GLOBALS['__cb_answered'])) {
            tg('answerCallbackQuery', ['callback_query_id' => $GLOBALS['__cb_current']['id']]);
            $GLOBALS['__cb_answered'] = true;
        }
    });

    // ═══════════════════ بررسی عضویت ═══════════════════
    
    if ($data === 'CHECK_MEMBERSHIP') {
        $memberCheck = $forceJoin->checkUserMembership($chatId, 'checkMembership');
        
        if ($memberCheck['joined']) {
            tg('deleteMessage', ['chat_id' => $chatId, 'message_id' => $msgId]);
            sendMainMenu($chatId, "✅ عضویت شما تأیید شد!\n\nبه ربات خوش آمدید.");
        } else {
            // نمایش مجدد پیام با لیست کانال‌های باقی‌مانده
            $joinMsg = $forceJoin->buildJoinMessage($memberCheck['missing_channels']);
            
            tg('editMessageText', [
                'chat_id'      => $chatId,
                'message_id'   => $msgId,
                'text'         => "❌ **هنوز در همه کانال‌ها عضو نشده‌اید!**\n\n" . $joinMsg['text'],
                'parse_mode'   => 'Markdown',
                'reply_markup' => json_encode($joinMsg['keyboard'], JSON_UNESCAPED_UNICODE),
            ]);
        }
        exit('OK');
    }
    
    // ═══════════════════ پاسخ کاربر به پیام خصوصی ادمین ═══════════════════
    if (preg_match('/^USER_REPLY_ADMIN_(\d+)$/', (string)$data, $m)) {
        $adminId = (int)$m[1];
        if ($adminId <= 0) {
            tg('answerCallbackQuery', ['callback_query_id'=>$cb['id'], 'text'=>'ادمین مقصد نامعتبر است.', 'show_alert'=>true]);
            exit('OK');
        }
        setUserMode($chatId, 'USER_REPLY_TO_ADMIN', ['admin_id'=>$adminId, 'source_message_id'=>(int)($msgId ?? 0)]);
        tg('answerCallbackQuery', ['callback_query_id'=>$cb['id'], 'text'=>'حالت پاسخ فعال شد']);
        $txt = "✍️ پاسخ خود را برای ادمین ارسال کنید.\n\nپیام بعدی شما مستقیم برای ادمین ارسال می‌شود. برای لغو، /cancel را بزنید.";
        tg('sendMessage', ['chat_id'=>$chatId, 'text'=>$txt]);
        exit('OK');
    }

    // ═══════════════════ منوی اصلی ═══════════════════
    
    
    if ($data === 'FAQ') { gemaccShowFaqList($chatId, $msgId, false); exit('OK'); }
    if (strpos($data,'FAQ_VIEW_')===0) { gemaccShowFaqItem($chatId,$msgId,(int)str_replace('FAQ_VIEW_','',$data),false); exit('OK'); }
    if ($data === 'ADMIN_FAQ') { if(!isAdmin($chatId)){ exit('OK'); } gemaccShowFaqList($chatId,$msgId,true); exit('OK'); }
    if ($data === 'ADMIN_FAQ_ADD') { if(!isAdmin($chatId)){ exit('OK'); } setUserMode($chatId,'ADMIN_FAQ_ADD',[]); tg('editMessageText',['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>"➕ *افزودن سوال متداول*\n\nفرمت ارسال:\nسوال در خط اول\nجواب از خط دوم به بعد\n\nمثال:\nچطور سرویس دریافت می‌شود؟\nبعد از تایید پرداخت، سرویس داخل چت ربات ارسال می‌شود.",'parse_mode'=>'Markdown','reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'❌ انصراف','callback_data'=>'ADMIN_FAQ']]]],JSON_UNESCAPED_UNICODE)]); exit('OK'); }
    if (strpos($data,'ADMIN_FAQ_VIEW_')===0) { if(!isAdmin($chatId)){ exit('OK'); } gemaccShowFaqItem($chatId,$msgId,(int)str_replace('ADMIN_FAQ_VIEW_','',$data),true); exit('OK'); }
    if (strpos($data,'ADMIN_FAQ_EDIT_')===0) { if(!isAdmin($chatId)){ exit('OK'); } $fid=(int)str_replace('ADMIN_FAQ_EDIT_','',$data); setUserMode($chatId,'ADMIN_FAQ_EDIT',['faq_id'=>$fid]); tg('editMessageText',['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>"✏️ *ویرایش سوال متداول*\n\nفرمت ارسال:\nسوال در خط اول\nجواب از خط دوم به بعد",'parse_mode'=>'Markdown','reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'❌ انصراف','callback_data'=>'ADMIN_FAQ_VIEW_'.$fid]]]],JSON_UNESCAPED_UNICODE)]); exit('OK'); }
    if (strpos($data,'ADMIN_FAQ_TOGGLE_')===0) { if(!isAdmin($chatId)){ exit('OK'); } $fid=(int)str_replace('ADMIN_FAQ_TOGGLE_','',$data); gemaccEnsureFaqTable(); $pdo->prepare("UPDATE faq_items SET is_active=IF(is_active=1,0,1) WHERE id=?")->execute([$fid]); gemaccShowFaqItem($chatId,$msgId,$fid,true); exit('OK'); }
    if (strpos($data,'ADMIN_FAQ_DELETE_')===0) { if(!isAdmin($chatId)){ exit('OK'); } $fid=(int)str_replace('ADMIN_FAQ_DELETE_','',$data); gemaccEnsureFaqTable(); $pdo->prepare("DELETE FROM faq_items WHERE id=?")->execute([$fid]); gemaccShowFaqList($chatId,$msgId,true); exit('OK'); }

if ($data === 'BACK_MAIN') {
        clearUserMode($chatId);
        tg('deleteMessage', ['chat_id' => $chatId, 'message_id' => $msgId]);
        sendMainMenu($chatId);
        exit('OK');
    }
    
    // ═══════════════════ خرید ═══════════════════
    
    if ($data === 'BUY') {
        if (!gemaccSalesEnabled()) { answerCbOnce($cb, gemaccSalesClosedMessage(), true); exit('OK'); }
        answerCbOnce($cb);
        $txt = "🛒 **خرید سرویس GEM ACC**\n\n";
        $txt .= "برای تهیه فیلترشکن، سرویس مورد نظر خود را از گزینه‌های زیر انتخاب کنید.\n";
        $txt .= "ابتدا نوع سرویس را انتخاب کنید؛ سپس پلن‌های همان سرویس نمایش داده می‌شوند.";

        gemaccEditTextOrSendFromCallback((int)$chatId, $msgId ? (int)$msgId : null, [
            'text'         => $txt,
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode(method_exists($plans, 'buildServiceKeyboard') ? $plans->buildServiceKeyboard() : $plans->buildDurationKeyboard(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    
    
    // ═══════════════════════════════════════════════
    // ADMIN - Credentials (Add / Toggle / List)
    // ═══════════════════════════════════════════════

    // اگر دکمه‌های ادمین توسط غیرادمین زده شد
    if (strpos($data, 'ADMIN_') === 0 && !isAdmin($chatId)) {
        answerCbOnce($cb, '⛔️ دسترسی فقط برای ادمین است', true);
        exit('OK');
    }

    // مرکز چت کاربران - v27.49
    if ($data === 'ADMIN_CHATS') {
        answerCbOnce($cb);
        $kb = ['inline_keyboard' => [
            [['text'=>'🕘 آخرین چت‌ها', 'callback_data'=>'ADMIN_CHAT_LIST_all']],
            [['text'=>'👥 چت کاربران / پشتیبانی', 'callback_data'=>'ADMIN_CHAT_LIST_support']],
            [['text'=>'🔒 چت خصوصی', 'callback_data'=>'ADMIN_CHAT_LIST_private']],
            [['text'=>'✅ همه تیکت‌ها خوانده شد', 'callback_data'=>'ADMIN_TICKETS_MARK_ALL_READ']],
            [['text'=>'🗑 پاک‌کردن همه تیکت‌ها', 'callback_data'=>'ADMIN_TICKETS_DELETE_ALL']],
            [['text'=>'◀️ پنل مدیریت', 'callback_data'=>'ADMIN_HOME']],
        ]];
        tg('editMessageText', [
            'chat_id'=>$chatId,
            'message_id'=>$msgId,
            'text'=>"💬 <b>مرکز چت کاربران</b>

یکی از دسته‌ها را انتخاب کنید:

• آخرین چت‌ها: ترکیب پشتیبانی و خصوصی
• چت کاربران: پیام‌های پشتیبانی/تیکت‌ها
• چت خصوصی: پیام مستقیم ادمین و کاربر",
            'parse_mode'=>'HTML',
            'reply_markup'=>json_encode($kb, JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }



    if ($data === 'ADMIN_TICKETS_MARK_ALL_READ') {
        $res = function_exists('gemaccSupportMarkAllTicketsRead') ? gemaccSupportMarkAllTicketsRead($pdo, (int)$chatId) : ['ok'=>false,'error'=>'Ticket service unavailable'];
        answerCbOnce($cb, !empty($res['ok']) ? 'همه تیکت‌ها خوانده‌شده شدند' : (string)($res['error'] ?? 'خطا'), true);
        if (!empty($res['ok']) && function_exists('gemaccAdminActionNotify')) {
            gemaccAdminActionNotify('همه تیکت‌ها خوانده‌شده شدند', ['پیام‌ها'=>$res['messages'] ?? 0, 'تیکت‌ها'=>$res['tickets'] ?? 0], (int)$chatId);
        }
        exit('OK');
    }

    if ($data === 'ADMIN_TICKETS_DELETE_ALL') {
        $res = function_exists('gemaccSupportDeleteAllTickets') ? gemaccSupportDeleteAllTickets($pdo, (int)$chatId) : ['ok'=>false,'error'=>'Ticket service unavailable'];
        answerCbOnce($cb, !empty($res['ok']) ? 'همه تیکت‌ها پاک شدند' : (string)($res['error'] ?? 'خطا'), true);
        if (!empty($res['ok']) && function_exists('gemaccAdminActionNotify')) {
            gemaccAdminActionNotify('همه تیکت‌های پشتیبانی پاک شدند', ['پیام‌ها'=>$res['messages'] ?? 0, 'تیکت‌ها'=>$res['tickets'] ?? 0], (int)$chatId);
        }
        tg('editMessageText', [
            'chat_id'=>$chatId,
            'message_id'=>$msgId,
            'text'=>"✅ عملیات انجام شد.\n\nتیکت‌های پاک‌شده: " . (int)($res['tickets'] ?? 0) . "\nپیام‌های پاک‌شده: " . (int)($res['messages'] ?? 0),
            'reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'◀️ مرکز چت','callback_data'=>'ADMIN_CHATS']]]], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    if (preg_match('/^ADMIN_CHAT_LIST_(all|private|support)$/', (string)$data, $m)) {
        answerCbOnce($cb);
        $type = $m[1];
        $rows = [];
        $items = function_exists('gemaccAdminChatRecent') ? gemaccAdminChatRecent($type, 12, '') : [];
        foreach ($items as $it) {
            $uid = (int)($it['user_id'] ?? 0);
            if (!$uid) continue;
            $label = (string)($it['user_label'] ?? ('کاربر '.$uid));
            $typeLabel = (string)($it['type_label'] ?? $type);
            $rows[] = [[
                'text' => mb_substr($label . ' | ' . $typeLabel, 0, 56, 'UTF-8'),
                'callback_data' => 'ADMIN_CHAT_VIEW_' . (string)$it['type'] . '_' . $uid,
            ]];
        }
        if (!$rows) $rows[] = [['text'=>'هنوز چتی ثبت نشده', 'callback_data'=>'ADMIN_CHATS']];
        $rows[] = [['text'=>'◀️ مرکز چت', 'callback_data'=>'ADMIN_CHATS']];
        gemaccEditTextOrSendFromCallback((int)$chatId, (int)$msgId, [
            'chat_id'=>$chatId,
            'message_id'=>$msgId,
            'text'=>"💬 <b>لیست چت‌ها</b>

برای مشاهده تاریخچه و پاسخ، یک کاربر را انتخاب کنید.",
            'parse_mode'=>'HTML',
            'reply_markup'=>json_encode(['inline_keyboard'=>$rows], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    if (preg_match('/^ADMIN_CHAT_VIEW_(all|private|support)_(\d+)$/', (string)$data, $m)) {
        answerCbOnce($cb);
        $type = $m[1];
        $uid = (int)$m[2];
        $messages = function_exists('gemaccAdminChatMessages') ? gemaccAdminChatMessages($type, $uid, 14) : [];
        $uLabel = 'کاربر '.$uid;
        try {
            $st = $pdo->prepare("SELECT id, username, first_name, last_name FROM users WHERE id=? LIMIT 1");
            $st->execute([$uid]);
            $ur = $st->fetch(PDO::FETCH_ASSOC) ?: [];
            $name = trim((string)($ur['first_name'] ?? '') . ' ' . (string)($ur['last_name'] ?? ''));
            $uLabel = $name !== '' ? $name : (!empty($ur['username']) ? '@'.ltrim((string)$ur['username'],'@') : $uLabel);
        } catch (Throwable $e) {}
        $txt = "💬 <b>چت با " . (function_exists('tgHtmlEscape') ? tgHtmlEscape($uLabel) : htmlspecialchars($uLabel, ENT_QUOTES, 'UTF-8')) . "</b>
";
        $txt .= "🆔 <code>{$uid}</code>
━━━━━━━━━━━━━━
";
        if (!$messages) {
            $txt .= "هنوز پیامی برای این کاربر ثبت نشده است.";
        } else {
            foreach ($messages as $mm) {
                $dir = (string)($mm['direction'] ?? '');
                $src = (string)($mm['source_label'] ?? 'چت');
                $prefix = $dir === 'admin_to_user' ? '👑 ادمین' : '👤 کاربر';
                $body = trim((string)($mm['message_text'] ?? ''));
                if ($body === '' && !empty($mm['telegram_file_id'])) $body = '📎 فایل/تصویر';
                $body = mb_substr($body, 0, 280, 'UTF-8');
                $safe = function_exists('tgHtmlEscape') ? tgHtmlEscape($body) : htmlspecialchars($body, ENT_QUOTES, 'UTF-8');
                $txt .= "
<b>{$prefix}</b> ({$src})
{$safe}
";
            }
        }
        if (mb_strlen(strip_tags($txt), 'UTF-8') > 3900) $txt = mb_substr($txt, 0, 3800, 'UTF-8') . "
...";
        $replyType = $type === 'support' ? 'support' : 'private';
        $kb = ['inline_keyboard'=>[
            [['text'=>'✍️ پاسخ به کاربر', 'callback_data'=>'ADMIN_CHAT_REPLY_'.$replyType.'_'.$uid]],
            [['text'=>'👤 پروفایل کاربر', 'callback_data'=>'ADMIN_USER_VIEW_'.$uid]],
            [['text'=>'◀️ لیست چت‌ها', 'callback_data'=>'ADMIN_CHAT_LIST_'.$type]],
        ]];
        gemaccEditTextOrSendFromCallback((int)$chatId, (int)$msgId, [
            'chat_id'=>$chatId,
            'message_id'=>$msgId,
            'text'=>$txt,
            'parse_mode'=>'HTML',
            'reply_markup'=>json_encode($kb, JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    if (preg_match('/^ADMIN_CHAT_REPLY_(private|support)_(\d+)$/', (string)$data, $m)) {
        answerCbOnce($cb, 'متن پاسخ را ارسال کنید');
        $type = $m[1];
        $uid = (int)$m[2];
        setUserMode($chatId, 'ADMIN_CHAT_REPLY', ['user_id'=>$uid, 'chat_type'=>$type]);
        tg('sendMessage', [
            'chat_id'=>$chatId,
            'text'=>"✍️ متن پاسخ به کاربر {$uid} را ارسال کنید.
برای لغو /cancel را بزنید.",
            'reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'❌ انصراف','callback_data'=>'ADMIN_CHAT_VIEW_'.$type.'_'.$uid]]]], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    // صفحه اصلی پنل ادمین (داخل همین پیام)
    if ($data === 'ADMIN_HOME') {
        answerCbOnce($cb);
        tg('editMessageText', [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => "👑 **پنل مدیریت**\n\nیک گزینه را انتخاب کنید:",
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    // شروع افزودن اشتراک: انتخاب دسته واقعی پلن، نه دکمه‌های قدیمی 1M/1Y
    if ($data === 'ADMIN_ADD_CREDENTIAL' || strpos($data, 'ADMIN_ADD_CREDENTIAL_CAT_') === 0) {
        answerCbOnce($cb);
        if ($data === 'ADMIN_ADD_CREDENTIAL') {
            $cats = method_exists($plans, 'getActiveCategories') ? $plans->getActiveCategories() : [];
            $rows = [];
            foreach ($cats as $cat) {
                $svc = gemaccServiceLabelFromRow($cat);
                $traffic = (($cat['traffic_type'] ?? 'unlimited') === 'limited') ? ('حجمی ' . (int)($cat['traffic_limit_gb'] ?? 0) . ' گیگ') : 'نامحدود';
                $rows[] = [[
                    'text' => '➕ ' . ($cat['name'] ?? $cat['code']) . ' - ' . $svc . ' - ' . $traffic,
                    'callback_data' => 'ADMIN_ADD_CREDENTIAL_CAT_' . (int)$cat['id'],
                ]];
            }
            if (!$rows) $rows[] = [['text' => 'اول دسته پلن بسازید', 'callback_data' => 'ADMIN_PLANS']];
            $rows[] = [['text' => '◀️ پنل مدیریت', 'callback_data' => 'ADMIN_HOME']];
            tg('editMessageText', [
                'chat_id'      => $chatId,
                'message_id'   => $msgId,
                'text'         => "➕ **افزودن اشتراک جدید**

دسته واقعی اشتراک را انتخاب کنید:",
                'parse_mode'   => 'Markdown',
                'reply_markup' => json_encode(['inline_keyboard' => $rows], JSON_UNESCAPED_UNICODE),
            ]);
            exit('OK');
        }
        $categoryId = (int)str_replace('ADMIN_ADD_CREDENTIAL_CAT_', '', $data);
        $st = $pdo->prepare('SELECT * FROM plan_categories WHERE id=? AND is_active=1 AND deleted_at IS NULL LIMIT 1');
        $st->execute([$categoryId]);
        $cat = $st->fetch(PDO::FETCH_ASSOC);
        if (!$cat) { answerCbOnce($cb, '❌ دسته پیدا نشد', true); exit('OK'); }
        $svc = gemaccServiceLabelFromRow($cat);
        $traffic = (($cat['traffic_type'] ?? 'unlimited') === 'limited') ? ('حجمی ' . (int)($cat['traffic_limit_gb'] ?? 0) . ' گیگ') : 'نامحدود';
        $selectedServiceType = gemaccServiceTypeFromHint((string)($cat['service_type'] ?? ''), (string)($cat['service_name'] ?? $cat['name'] ?? ''));
        if ($selectedServiceType === 'v2ray') {
            setUserMode($chatId, 'ADMIN_ADD_V2RAY_CONFIG', ['duration'=>$cat['code'], 'category_id'=>$categoryId]);
            $txt = "⚡ **افزودن کانفیگ V2Ray - {$cat['name']} - {$traffic}**

متن کامل کانفیگ را ارسال کنید. در مرحله بعد تصویر QR Code را دریافت می‌کنم.

نمونه:
vless://... یا vmess://... یا trojan://...";
        } else {
            setUserMode($chatId, 'ADMIN_ADD_CREDENTIAL', ['duration' => $cat['code'], 'category_id' => $categoryId]);
            $txt = "➕ **افزودن اشتراک {$cat['name']} - {$svc} - {$traffic}**

";
            $txt .= "لطفاً اشتراک‌ها را به فرمت زیر ارسال کنید. ثبت تکی و انبوه پشتیبانی می‌شود:
username:password

مثال تکی:
openz313:5350

مثال انبوه:
openz313:5350
openz314:5350
openz315:5350
openz316:5350

بعد از ارسال username:password، ربات از شما ظرفیت دستگاه/کاربر همان اکانت را می‌پرسد؛ مثلاً 1، 5، 15 یا 20 کاربر.";
        }
        tg('editMessageText', [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => $txt,
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode(['inline_keyboard' => [[['text' => '❌ انصراف', 'callback_data' => 'ADMIN_ADD_CREDENTIAL']]]], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }


function gemaccAdminShowSoldCredentials($chatId, $msgId=null, int $page=1): void {
    global $pdo;
    $limit=8; $offset=max(0,($page-1)*$limit);
    $sql="SELECT c.*, COALESCE(u.first_name,u.username,c.sold_to_user_id,c.assigned_user_id,'-') buyer_name
          FROM credentials c LEFT JOIN users u ON u.id=COALESCE(c.sold_to_user_id,c.assigned_user_id)
          WHERE COALESCE(c.used_seats,0)>0 OR COALESCE(c.is_sold,0)>0 OR LOWER(COALESCE(c.status,'')) IN ('sold','delivered','assigned','used')
          ORDER BY COALESCE(c.delivered_at,c.sold_at,c.updated_at,c.created_at) DESC LIMIT {$limit} OFFSET {$offset}";
    $rows=$pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
    $txt="✅ **اشتراک‌های فروخته‌شده**\n\n";
    $kb=[];
    foreach($rows as $r){
        $type=(($r['traffic_type']??'unlimited')==='limited') ? ('حجمی '.(int)($r['traffic_limit_gb']??0).' گیگ') : 'نامحدود';
        $txt .= "#{$r['id']} | {$r['email']} | {$type} | کاربر: {$r['buyer_name']}\n";
        $kb[]=[['text'=>'🔐 پسورد #'.$r['id'], 'callback_data'=>'ADMIN_SOLD_EDIT_'.$r['id']], ['text'=>'➕ ماه #'.$r['id'], 'callback_data'=>'ADMIN_SOLD_EXTEND_'.$r['id']], ['text'=>'⏸ توقف فروش #'.$r['id'], 'callback_data'=>'ADMIN_CRED_PAUSE_'.$r['id']], ['text'=>'▶️ فعال #'.$r['id'], 'callback_data'=>'ADMIN_CRED_RESUME_'.$r['id']], ['text'=>'🗑 حذف از حساب #'.$r['id'], 'callback_data'=>'ADMIN_SOLD_DETACH_'.$r['id']]];
    }
    if(!$rows) $txt.="موردی وجود ندارد.";
    $nav=[]; if($page>1)$nav[]=['text'=>'⬅️ قبلی','callback_data'=>'ADMIN_SOLD_PAGE_'.($page-1)]; if(count($rows)===$limit)$nav[]=['text'=>'➡️ بعدی','callback_data'=>'ADMIN_SOLD_PAGE_'.($page+1)]; if($nav)$kb[]=$nav;
    $kb[]=[['text'=>'◀️ بازگشت','callback_data'=>'ADMIN_CREDENTIALS']];
    $payload=['chat_id'=>$chatId,'text'=>$txt,'parse_mode'=>'Markdown','reply_markup'=>json_encode(['inline_keyboard'=>$kb],JSON_UNESCAPED_UNICODE)]; if($msgId)$payload['message_id']=$msgId;
    tg($msgId?'editMessageText':'sendMessage',$payload);
}
function gemaccAdminDetachCredential(int $id): bool {
    global $pdo;
    $pdo->beginTransaction();
    $st=$pdo->prepare("SELECT * FROM credentials WHERE id=? FOR UPDATE"); $st->execute([$id]); $c=$st->fetch(PDO::FETCH_ASSOC); if(!$c){$pdo->rollBack(); return false;}
    $pdo->prepare("DELETE FROM deliveries WHERE credential_id=?")->execute([$id]);
    if(!empty($c['order_id'])){ $cnt=$pdo->prepare("SELECT COUNT(*) FROM deliveries WHERE order_id=?"); $cnt->execute([(int)$c['order_id']]); $left=(int)$cnt->fetchColumn(); $pdo->prepare("UPDATE orders SET delivered_accounts=?, delivery_status=IF(?=0,'queued','partial'), status=IF(?=0,'QUEUED','DELIVERED') WHERE id=?")->execute([$left,$left,$left,(int)$c['order_id']]); }
    $pdo->prepare("UPDATE credentials SET used_seats=0,is_sold=0,status='available',sold_to_user_id=NULL,order_id=NULL,assigned_user_id=NULL,assigned_order_id=NULL,sold_at=NULL,delivered_at=NULL,first_connected_at=NULL,expires_at=NULL,updated_at=NOW() WHERE id=?")->execute([$id]);
    $pdo->commit(); return true;
}

    // رندر لیست اشتراک‌ها (ادمین)
    $renderAdminCreds = function(int $page = 1) use ($credentials, $chatId, $msgId) {
        $limit  = 10;
        $page   = max(1, $page);
        $offset = ($page - 1) * $limit;

        $list = $credentials->getList($limit, $offset);

        $txt = "🔐 **مدیریت اشتراک‌ها**\n\n";
        $txt .= "لیست اکانت‌های آماده فروش/نیمه‌فروخته. با توقف فروش، اکانت از فروش موقت خارج می‌شود؛ با انتقال به فروخته‌شده دیگر در این لیست نمایش داده نمی‌شود.\n\n";
        if (!$list) {
            $txt .= "— موردی یافت نشد.";
        } else {
            foreach ($list as $c) {
                $isSold = ((int)($c['used_seats'] ?? 0)>0 || (int)($c['is_sold'] ?? 0)>0 || in_array(strtolower((string)($c['status'] ?? 'available')), ['sold','delivered','assigned','used'], true));
                $status = $isSold ? '🛒 فروخته‌شده' : '📦 فروخته‌نشده';
                $durLbl = Plans::getDurationLabel($c['duration'] ?? '1M');
                $rem    = (int)($c['remaining_seats'] ?? 0);
                $used   = (int)($c['used_seats'] ?? 0);
                $max    = (int)($c['max_seats'] ?? 0);
                $sell   = (!$isSold && ((int)($c['is_sellable'] ?? 0) === 1)) ? '🟢 آماده فروش' : ($isSold ? '🛒 تحویل‌شده' : '🔴 غیرقابل فروش');
                $id     = (int)$c['id'];
                $email  = $c['email'] ?? '';

                $txt .= "━━━━━━━━━━━━━━━━━━━━\n";
                $txt .= "🆔 `#{$id}` | {$status}\n";
                $txt .= "📅 {$durLbl} | {$sell}\n";
                $txt .= "📧 `{$email}`\n";
                $txt .= "👥 ظرفیت: {$used}/{$max} | باقی‌مانده: {$rem}\n";
            }
        }

        $kbRows = [];

        foreach ($list as $c) { $id=(int)$c['id']; $paused=(int)($c['sales_paused']??0)===1; $kbRows[] = [['text'=>'✏️ اصلاح #'.$id, 'callback_data'=>'ADMIN_SOLD_EDIT_'.$id], ['text'=>($paused?'▶️ فعال فروش':'⏸ توقف فروش').' #'.$id, 'callback_data'=>($paused?'ADMIN_CRED_RESUME_':'ADMIN_CRED_PAUSE_').$id], ['text'=>'✅ انتقال به فروخته‌شده #'.$id, 'callback_data'=>'ADMIN_CRED_MOVE_SOLD_'.$id]]; }

        // صفحه‌بندی
        $navRow = [];
        if ($page > 1) $navRow[] = ['text' => '⬅️ قبلی', 'callback_data' => 'ADMIN_CRED_PAGE_' . ($page - 1)];
        if ($list && count($list) === $limit) $navRow[] = ['text' => '➡️ بعدی', 'callback_data' => 'ADMIN_CRED_PAGE_' . ($page + 1)];
        if ($navRow) $kbRows[] = $navRow;

        $kbRows[] = [
            ['text' => '➕ افزودن اشتراک', 'callback_data' => 'ADMIN_ADD_CREDENTIAL'],
            ['text' => '◀️ پنل مدیریت', 'callback_data' => 'ADMIN_HOME'],
        ];
        $kbRows[] = [
            ['text' => '◀️ منوی اصلی', 'callback_data' => 'BACK_MAIN'],
        ];

        tg('editMessageText', [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => $txt,
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode(['inline_keyboard' => $kbRows], JSON_UNESCAPED_UNICODE),
        ]);
    };

    if ($data === 'ADMIN_CREDENTIALS') {
        answerCbOnce($cb);
        $renderAdminCreds(1);
        exit('OK');
    }

    if (strpos($data, 'ADMIN_CRED_PAGE_') === 0) {
        $page = (int)str_replace('ADMIN_CRED_PAGE_', '', $data);
        answerCbOnce($cb);
        $renderAdminCreds($page);
        exit('OK');
    }


    if ($data === 'ADMIN_SOLD_CREDENTIALS') { answerCbOnce($cb); gemaccAdminShowSoldCredentials($chatId,$msgId,1); exit('OK'); }
    if (strpos($data,'ADMIN_SOLD_PAGE_')===0) { answerCbOnce($cb); gemaccAdminShowSoldCredentials($chatId,$msgId,max(1,(int)str_replace('ADMIN_SOLD_PAGE_','',$data))); exit('OK'); }
    if (strpos($data,'ADMIN_SOLD_DETACH_')===0) { $id=(int)str_replace('ADMIN_SOLD_DETACH_','',$data); $ok=gemaccAdminDetachCredential($id); answerCbOnce($cb,$ok?'✅ از حساب کاربر حذف شد':'❌ اشتراک پیدا نشد',true); gemaccAdminShowSoldCredentials($chatId,$msgId,1); exit('OK'); }
    if (strpos($data,'ADMIN_SOLD_EXTEND_')===0) { $id=(int)str_replace('ADMIN_SOLD_EXTEND_','',$data); setUserMode($chatId,'ADMIN_EXTEND_CREDENTIAL',['credential_id'=>$id]); tg('editMessageText',['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>'چند ماه به این اشتراک اضافه شود؟ عدد را ارسال کنید.','reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'◀️ بازگشت','callback_data'=>'ADMIN_SOLD_CREDENTIALS']]]],JSON_UNESCAPED_UNICODE)]); exit('OK'); }
    if (strpos($data,'ADMIN_SOLD_EDIT_')===0) { $id=(int)str_replace('ADMIN_SOLD_EDIT_','',$data); setUserMode($chatId,'ADMIN_EDIT_CREDENTIAL',['credential_id'=>$id]); tg('editMessageText',['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>'یوزرنیم و پسورد جدید را به شکل username:password ارسال کنید.','reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'◀️ بازگشت','callback_data'=>'ADMIN_SOLD_CREDENTIALS']]]],JSON_UNESCAPED_UNICODE)]); exit('OK'); }

    if (strpos($data, 'ADMIN_CRED_PAUSE_') === 0 || strpos($data, 'ADMIN_CRED_RESUME_') === 0) {
        $isPause = strpos($data, 'ADMIN_CRED_PAUSE_') === 0;
        $id = (int)str_replace($isPause ? 'ADMIN_CRED_PAUSE_' : 'ADMIN_CRED_RESUME_', '', $data);
        if ($id <= 0) { answerCbOnce($cb, '❌ شناسه نامعتبر است', true); exit('OK'); }
        $paused = $isPause ? 1 : 0;
        $pdo->prepare("UPDATE credentials SET sales_paused=?, status=IF(?=1,'paused',IF(COALESCE(used_seats,0)>=COALESCE(max_seats,1),'sold',IF(COALESCE(used_seats,0)>0,'partial','available'))), updated_at=NOW() WHERE id=?")->execute([$paused,$paused,$id]);
        answerCbOnce($cb, $paused ? '⏸ فروش این اشتراک متوقف شد' : '▶️ فروش این اشتراک فعال شد', true);
        $renderAdminCreds(1);
        exit('OK');
    }

    if (strpos($data, 'ADMIN_CRED_MOVE_SOLD_') === 0) {
        $id = (int)str_replace('ADMIN_CRED_MOVE_SOLD_', '', $data);
        if ($id <= 0) { answerCbOnce($cb, '❌ شناسه نامعتبر است', true); exit('OK'); }
        $pdo->prepare("UPDATE credentials SET sales_paused=1, is_sold=1, used_seats=GREATEST(COALESCE(used_seats,0),COALESCE(max_seats,1)), status='sold', updated_at=NOW() WHERE id=?")->execute([$id]);
        answerCbOnce($cb, '✅ اشتراک از فروش خارج و به فروخته‌شده‌ها منتقل شد', true);
        $renderAdminCreds(1);
        exit('OK');
    }

    if (strpos($data, 'ADMIN_CRED_TOGGLE_') === 0) {
        answerCbOnce($cb, 'دکمه خاموش/روشن اشتراک‌ها حذف شده است', true);
        exit('OK');
    }

    if (strpos($data, 'BUY_SERVICE_') === 0) {
        if (!gemaccSalesEnabled()) { answerCbOnce($cb, gemaccSalesClosedMessage(), true); exit('OK'); }
        answerCbOnce($cb);
        $serviceType = gemaccServiceTypeFromHint(str_replace('BUY_SERVICE_', '', (string)$data));
        $label = gemaccServiceShortLabel($serviceType);
        gemaccEditTextOrSendFromCallback((int)$chatId, $msgId ? (int)$msgId : null, [
            'text'=>"🛒 <b>پلن‌های {$label}</b>

مدت و نوع حجم موردنظر را انتخاب کنید:",
            'parse_mode'=>'HTML',
            'reply_markup'=>json_encode(method_exists($plans,'buildCategoryKeyboardForService') ? $plans->buildCategoryKeyboardForService($serviceType) : $plans->buildDurationKeyboard(), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    if (strpos($data, 'BUY_DURATION_') === 0) {
        if (!gemaccSalesEnabled()) { answerCbOnce($cb, gemaccSalesClosedMessage(), true); exit('OK'); }
        $duration = str_replace('BUY_DURATION_', '', $data);
        $cat = $plans->getCategoryByCode($duration);
        $durationLabel = $cat ? $cat['name'] : Plans::getDurationLabel($duration);
        $categoryServiceType = $cat ? gemaccServiceTypeFromHint((string)($cat['service_type'] ?? 'openvpn_l2tp'), (string)(($cat['service_name'] ?? '') . ' ' . ($cat['name'] ?? ''))) : 'openvpn_l2tp';
        
        $txt = "🛒 **خرید اشتراک {$durationLabel}**

";
        $txt .= $categoryServiceType === 'v2ray' ? "پلن موردنظر را انتخاب کنید:" : "پلن یا تعداد کاربر موردنظر را انتخاب کنید:";
        
        tg('editMessageText', [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => $txt,
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode($plans->buildSeatsKeyboard($duration), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    
    if (strpos($data, 'BUY_PLAN_') === 0) {
        if (!gemaccSalesEnabled()) { answerCbOnce($cb, gemaccSalesClosedMessage(), true); exit('OK'); }
        // ادمین از بررسی عضویت معاف است
        if (!isAdmin($chatId)) {
            // بررسی عضویت در کانال‌ها
            $memberCheck = $forceJoin->checkUserMembership($chatId, 'checkMembership');
            if (!$memberCheck['joined']) {
                $joinMsg = $forceJoin->buildJoinMessage($memberCheck['missing_channels']);
                tg('editMessageText', [
                    'chat_id'      => $chatId,
                    'message_id'   => $msgId,
                    'text'         => $joinMsg['text'],
                    'parse_mode'   => 'Markdown',
                    'reply_markup' => json_encode($joinMsg['keyboard'], JSON_UNESCAPED_UNICODE),
                ]);
                exit('OK');
            }
        }
        
        $planId = (int)str_replace('BUY_PLAN_', '', $data);
        $plan = $plans->getById($planId);
        
        if (!$plan) {
            answerCbOnce($cb, '❌ پلن یافت نشد!', true);
            exit('OK');
        }
        
        gemaccShowBotPaymentOptions((int)$chatId, (int)$msgId, $plan, (int)$plan['price'], null, 0);
        exit('OK');
    }
    
    if (strpos($data, 'BOT_DISCOUNT_') === 0) {
        $planId=(int)str_replace('BOT_DISCOUNT_','',$data);
        setUserMode($chatId,'BOT_ORDER_DISCOUNT',['plan_id'=>$planId]);
        tg('editMessageText',['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>'🎫 کد تخفیف را ارسال کنید:','reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'◀️ بازگشت','callback_data'=>'BUY_PLAN_'.$planId]]]],JSON_UNESCAPED_UNICODE)]);
        exit('OK');
    }

    if (strpos($data, 'PAY_USE_WALLET_') === 0) {
        if (!gemaccSalesEnabled()) { answerCbOnce($cb, gemaccSalesClosedMessage(), true); exit('OK'); }
        $planId=(int)str_replace('PAY_USE_WALLET_','',$data);
        $plan=$plans->getById($planId);
        if(!$plan){ answerCbOnce($cb,'❌ پلن یافت نشد!',true); exit('OK'); }
        $state=gemaccBotCurrentOrderState((int)$chatId,$planId,$plan);
        $modeNow=getUserMode($chatId);
        if($modeNow && ($modeNow['mode'] ?? '')==='BOT_ORDER_WALLET_APPLIED' && (int)($modeNow['payload']['plan_id'] ?? 0)===$planId){
            answerCbOnce($cb,'موجودی کیف پول قبلاً روی این سفارش اعمال شده است.',false);
            gemaccShowBotPaymentOptions((int)$chatId,(int)$msgId,$plan,(int)($modeNow['payload']['final_price'] ?? $state['final_price']),$state['discount_code'] ?: null,(int)$state['discount_amount']);
            exit('OK');
        }
        $balance=$wallet->getBalance((int)$chatId);
        $walletUse=min(max(0,$balance), max(0,(int)$state['final_price']));
        if($walletUse<=0){ answerCbOnce($cb,'موجودی قابل استفاده ندارید.',true); exit('OK'); }
        $newFinal=max(0,(int)$state['final_price']-$walletUse);
        setUserMode($chatId,'BOT_ORDER_WALLET_APPLIED',['plan_id'=>$planId,'discount_code'=>$state['discount_code'],'discount_amount'=>$state['discount_amount'],'base_price'=>(int)$state['final_price'],'original_final_price'=>(int)$state['final_price'],'wallet_credit_used'=>$walletUse,'final_price'=>$newFinal]);
        answerCbOnce($cb,'✅ موجودی کیف پول روی این سفارش اعمال شد.',false);
        gemaccShowBotPaymentOptions((int)$chatId,(int)$msgId,$plan,$newFinal,$state['discount_code'] ?: null,(int)$state['discount_amount']);
        exit('OK');
    }


    // پرداخت با کیف پول
    if (strpos($data, 'PAY_WALLET_') === 0) {
        if (!gemaccSalesEnabled()) { answerCbOnce($cb, gemaccSalesClosedMessage(), true); exit('OK'); }
        $planId = (int)str_replace('PAY_WALLET_', '', $data);
        $plan = $plans->getById($planId);
        
        if (!$plan) {
            answerCbOnce($cb, '❌ پلن یافت نشد!', true);
            exit('OK');
        }

        $botWalletRequestId = 'bot-wallet-' . $chatId . '-' . $planId . '-' . (int)$msgId;
        if (function_exists('gemacc_idempotency_begin')) {
            $dupe = gemacc_idempotency_begin($pdo, (int)$chatId, $botWalletRequestId, 'bot_wallet_order');
            if ($dupe) {
                answerCbOnce($cb, 'این خرید قبلاً ثبت یا در حال پردازش است. لطفاً سفارش‌های خود را بررسی کنید.', true);
                exit('OK');
            }
        }
        
        // پرداخت
        $discMode = getUserMode($chatId);
        $discountCode=''; $discountAmount=0; $finalPrice=(int)$plan['price']; $walletCreditUsed=0;
        if($discMode && in_array(($discMode['mode'] ?? ''), ['BOT_ORDER_DISCOUNT_APPLIED','BOT_ORDER_WALLET_APPLIED'], true) && (int)($discMode['payload']['plan_id'] ?? 0)===$planId){
            $discountCode=(string)($discMode['payload']['discount_code'] ?? ''); $discountAmount=(int)($discMode['payload']['discount_amount'] ?? 0); $finalPrice=(int)($discMode['payload']['final_price'] ?? $plan['price']);
            if($discountCode && function_exists('gemacc_discount_preview')){ $chk=gemacc_discount_preview($pdo,(int)$chatId,$discountCode,(int)$plan['price'],(int)$planId,'wallet'); if(empty($chk['valid'])){ answerCbOnce($cb, $chk['message'] ?? 'این کد برای کیف پول معتبر نیست', true); exit('OK'); } $discountAmount=(int)($chk['discount_amount']??0); $finalPrice=(int)($chk['final_price']??$finalPrice); }
        }
        $payResult = $wallet->pay($chatId, $finalPrice, "خرید {$plan['title']}" . ($discountCode ? " با کد {$discountCode}" : ''));
        
        if (!$payResult['success']) {
            if (function_exists('gemacc_idempotency_finish')) gemacc_idempotency_finish($pdo, (int)$chatId, $botWalletRequestId ?? '', 'bot_wallet_order', null, ['ok'=>false,'error'=>$payResult['message'] ?? 'wallet failed']);
            answerCbOnce($cb, $payResult['message'], true);
            exit('OK');
        }
        
        // ایجاد سفارش
        $orderId = $orders->create($chatId, $plan['duration'], $plan['seats'], $finalPrice, 'WALLET', null, $discountCode ? json_encode(['discount_code'=>$discountCode,'discount_amount'=>$discountAmount,'original_price'=>(int)$plan['price']], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) : null);
        try { $trafficType = $plan['traffic_type'] ?? $plan['category_traffic_type'] ?? 'unlimited'; $trafficLimitGb = ($trafficType === 'limited') ? ($plan['traffic_limit_gb'] ?? $plan['category_traffic_limit_gb'] ?? null) : null; $serviceType = gemaccServiceTypeFromHint((string)($plan['protocol'] ?? ($plan['service_type'] ?? 'openvpn_l2tp')), (string)($plan['title'] ?? '')); $pdo->prepare("UPDATE orders SET plan_id=?, category_id=?, plan_code=?, plan_title=?, service_type=?, months=?, required_accounts=?, traffic_type=?, traffic_limit_gb=? WHERE id=?")->execute([(int)($plan['id'] ?? 0), $plan['category_id'] ?? null, $plan['code'] ?? null, $plan['title'] ?? 'OpenVPN | L2TP', $serviceType, (int)($plan['months'] ?? $plan['category_months'] ?? 1), max(1,(int)($plan['seats'] ?? $plan['user_count'] ?? 1)), $trafficType, $trafficLimitGb, (int)$orderId]); } catch (Throwable $e) { error_log('[bot wallet order metadata] '.$e->getMessage()); }
        if($discountCode && function_exists('gemacc_discount_preview') && function_exists('gemacc_discount_apply')){ $d=gemacc_discount_preview($pdo,(int)$chatId,$discountCode,(int)$plan['price'],(int)$planId,'wallet'); if(!empty($d['valid'])) gemacc_discount_apply($pdo,$d,(int)$chatId,(int)$orderId); }
        clearUserMode($chatId);
        if (function_exists('gemacc_idempotency_finish')) gemacc_idempotency_finish($pdo, (int)$chatId, $botWalletRequestId ?? '', 'bot_wallet_order', (int)$orderId, ['ok'=>true,'order_id'=>(int)$orderId]);
        
        // تخصیص موجودی با موتور یکپارچه (OpenVPN/L2TP، Windscribe، ExpressVPN و V2Ray)
        $approveResult = function_exists('vpnApproveOrderAndNotify')
            ? vpnApproveOrderAndNotify($pdo, (int)$orderId, false)
            : $orders->approve($orderId);
        
        if ($approveResult['success']) {
            // ارسال اطلاعات اکانت
            $deliveredCredential = $approveResult['credentials'][0] ?? $approveResult['credential'] ?? [];
            $deliveredServiceType = gemaccServiceTypeFromHint((string)($deliveredCredential['service_type'] ?? 'openvpn_l2tp'));
            if ($deliveredServiceType === 'v2ray') {
                gemaccSendV2RayCard((int)$chatId, $msgId ? (int)$msgId : null, buildDeliveryMessage($approveResult['credentials'] ?? $approveResult['credential']), kbDelivery($deliveredServiceType), gemaccResolveV2RayPhoto((array)$deliveredCredential));
            } else {
                tg('editMessageText', [
                    'chat_id'      => $chatId,
                    'message_id'   => $msgId,
                    'text'         => buildDeliveryMessage($approveResult['credentials'] ?? $approveResult['credential']),
                    'parse_mode'   => 'HTML',
                    'reply_markup' => json_encode(kbDelivery($deliveredServiceType), JSON_UNESCAPED_UNICODE),
                ]);
            }
        } else {
            // رفته به صف انتظار
            if (isset($approveResult['queued']) && $approveResult['queued']) {
                tg('editMessageText', [
                    'chat_id'    => $chatId,
                    'message_id' => $msgId,
                    'text'       => "⏳ **سفارش شما ثبت شد**\n\nدر حال حاضر موجودی کافی نداریم.\nبه محض اضافه شدن اشتراک جدید، سفارش شما پردازش می‌شود.\n\n🆔 شماره سفارش: #{$orderId}",
                    'parse_mode' => 'Markdown',
                ]);
                
                // اطلاع به ادمین
                notifyAdmins(
                    "⚠️ **کمبود موجودی!**\n\nسفارش #{$orderId} به صف انتظار رفت.\n\n📅 مدت: " . Plans::getDurationLabel($plan['duration']) . "\n👥 تعداد: {$plan['seats']} صندلی\n\nلطفاً اشتراک جدید اضافه کنید.",
                    [
                        'inline_keyboard' => [
                            [['text' => '➕ افزودن اشتراک جدید در همین دسته', 'callback_data' => "ADMIN_ADD_CREDENTIAL_CAT_" . (int)($plan['category_id'] ?? 0)]],
                        ],
                    ]
                );
            } else {
                // استرداد
                $wallet->refund($chatId, $finalPrice, 'استرداد - خطا در پردازش');
                
                tg('editMessageText', [
                    'chat_id'    => $chatId,
                    'message_id' => $msgId,
                    'text'       => "❌ خطا در پردازش سفارش.\nمبلغ به کیف پول شما برگردانده شد.",
                ]);
            }
        }
        exit('OK');
    }
    

    if (strpos($data, 'BOT_USE_DISCOUNT_') === 0) {
        $rest = substr($data, strlen('BOT_USE_DISCOUNT_'));
        $parts = explode('_', $rest, 2);
        $planId = (int)($parts[0] ?? 0);
        $code = strtoupper(trim((string)($parts[1] ?? '')));
        $plan = $plans->getById($planId);
        if (!$plan || $code === '') { answerCbOnce($cb, 'کد یا پلن نامعتبر است', true); exit('OK'); }
        $discount = function_exists('gemacc_discount_preview') ? gemacc_discount_preview($pdo,(int)$chatId,$code,(int)$plan['price'],(int)$planId) : ['valid'=>false];
        if(empty($discount['valid'])){
            answerCbOnce($cb, 'کد تخفیف نداری؛ توی جشنواره بعدی حتما واست ارسال میشه❤️', true);
            gemaccShowBotPaymentOptions((int)$chatId, (int)$msgId, $plan, (int)$plan['price'], null, 0);
            exit('OK');
        }
        setUserMode($chatId,'BOT_ORDER_DISCOUNT_APPLIED',['plan_id'=>$planId,'discount_code'=>$code,'discount_amount'=>(int)($discount['discount_amount']??0),'final_price'=>(int)($discount['final_price']??$plan['price'])]);
        gemaccShowBotPaymentOptions((int)$chatId, (int)$msgId, $plan, (int)($discount['final_price']??$plan['price']), $code, (int)($discount['discount_amount']??0));
        exit('OK');
    }

    // پرداخت ارز دیجیتال برای خرید سرویس - انتخاب دستی یا درگاه
    // مهم: این شرط باید فقط PAY_CRYPTO_{planId} را بگیرد و نباید
    // PAY_CRYPTO_GATEWAY_{planId} یا PAY_CRYPTO_MANUAL_{planId} را ببلعد؛
    // وگرنه planId صفر می‌شود و خطای «پلن یافت نشد» نمایش داده می‌شود.
    if (preg_match('/^PAY_CRYPTO_(\d+)$/', $data, $m)) {
        if (!gemaccSalesEnabled()) { answerCbOnce($cb, gemaccSalesClosedMessage(), true); exit('OK'); }
        $planId = (int)$m[1];
        $plan = $plans->getById($planId);
        if (!$plan) { answerCbOnce($cb, '❌ پلن یافت نشد!', true); exit('OK'); }
        $discMode = getUserMode($chatId);
        $discountCode=''; $discountAmount=0; $finalPrice=(int)$plan['price']; $walletCreditUsed=0;
        if($discMode && in_array(($discMode['mode'] ?? ''), ['BOT_ORDER_DISCOUNT_APPLIED','BOT_ORDER_WALLET_APPLIED'], true) && (int)($discMode['payload']['plan_id'] ?? 0)===$planId){
            $discountCode=(string)($discMode['payload']['discount_code'] ?? '');
            $discountAmount=(int)($discMode['payload']['discount_amount'] ?? 0);
            $finalPrice=(int)($discMode['payload']['final_price'] ?? $plan['price']);
            $walletCreditUsed=(int)($discMode['payload']['wallet_credit_used'] ?? 0);
        }
        setUserMode($chatId, 'BOT_CRYPTO_CHOICE', ['plan_id'=>$planId,'discount_code'=>$discountCode,'discount_amount'=>$discountAmount,'wallet_credit_used'=>$walletCreditUsed,'final_price'=>$finalPrice]);
        $rows=[];
        foreach (gemaccCryptoEnabledAssets() as $asset) {
            $label = gemaccCryptoAssetLabel($asset);
            $rows[] = [['text'=>'⚡ درگاه فوری - '.$label, 'callback_data'=>"PAY_CRYPTO_GATEWAY_{$asset}_{$planId}"]];
            $rows[] = [['text'=>'🧾 پرداخت دستی - '.$label, 'callback_data'=>"PAY_CRYPTO_MANUAL_{$asset}_{$planId}"]];
        }
        $rows[] = [['text'=>'◀️ بازگشت', 'callback_data'=>"BUY_PLAN_{$planId}"]];
        $buttons = ['inline_keyboard'=>$rows];
        tg('editMessageText', ['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>gemaccCryptoChoiceText($plan,$finalPrice),'parse_mode'=>'Markdown','reply_markup'=>json_encode($buttons,JSON_UNESCAPED_UNICODE)]);
        exit('OK');
    }

    if (preg_match('/^PAY_CRYPTO_MANUAL_(USDT|TRX)_(\d+)$/', $data, $m) || preg_match('/^PAY_CRYPTO_MANUAL_(\d+)$/', $data, $legacy)) {
        if (!gemaccSalesEnabled()) { answerCbOnce($cb, gemaccSalesClosedMessage(), true); exit('OK'); }
        $asset = isset($m) && $m ? $m[1] : 'USDT';
        $planId = isset($m) && $m ? (int)$m[2] : (int)$legacy[1];
        $plan = $plans->getById($planId);
        if (!$plan) { answerCbOnce($cb, '❌ پلن یافت نشد!', true); exit('OK'); }
        $choice = getUserMode($chatId);
        $discountCode=''; $discountAmount=0; $finalPrice=(int)$plan['price'];
        if($choice && in_array(($choice['mode'] ?? ''), ['BOT_CRYPTO_CHOICE','BOT_ORDER_DISCOUNT_APPLIED','BOT_ORDER_WALLET_APPLIED'], true) && (int)($choice['payload']['plan_id'] ?? 0)===$planId){
            $discountCode=(string)($choice['payload']['discount_code'] ?? '');
            $discountAmount=(int)($choice['payload']['discount_amount'] ?? 0);
            $finalPrice=(int)($choice['payload']['final_price'] ?? $plan['price']);
            if($discountCode && function_exists('gemacc_discount_preview')){ $chk=gemacc_discount_preview($pdo,(int)$chatId,$discountCode,(int)$plan['price'],(int)$planId,'crypto'); if(empty($chk['valid'])){ answerCbOnce($cb, $chk['message'] ?? 'این کد برای ارز دیجیتال معتبر نیست', true); exit('OK'); } $discountAmount=(int)($chk['discount_amount']??0); $finalPrice=(int)($chk['final_price']??$finalPrice); }
        }
        try { $w=gemaccCryptoWalletForAsset($asset); if(empty($w['address'])) throw new RuntimeException('آدرس ولت این ارز تنظیم نشده است.'); }
        catch(Throwable $e){ answerCbOnce($cb, $e->getMessage(), true); exit('OK'); }
        setUserMode($chatId, 'ORDER_RECEIPT', ['plan_id'=>$planId,'discount_code'=>$discountCode,'discount_amount'=>$discountAmount,'final_price'=>$finalPrice,'payment_method'=>'CRYPTO','crypto_asset'=>$asset]);
        tg('editMessageText', ['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>gemaccManualCryptoText($finalPrice,$plan,$asset),'parse_mode'=>'Markdown','reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'◀️ بازگشت','callback_data'=>"PAY_CRYPTO_{$planId}"]],[['text'=>'❌ انصراف','callback_data'=>'BACK_MAIN']]]],JSON_UNESCAPED_UNICODE)]);
        exit('OK');
    }

    if (preg_match('/^PAY_CRYPTO_GATEWAY_(USDT|TRX)_(\d+)$/', $data, $m) || preg_match('/^PAY_CRYPTO_GATEWAY_(\d+)$/', $data, $legacy)) {
        if (!gemaccSalesEnabled()) { answerCbOnce($cb, gemaccSalesClosedMessage(), true); exit('OK'); }
        $asset = isset($m) && $m ? $m[1] : 'USDT';
        $planId = isset($m) && $m ? (int)$m[2] : (int)$legacy[1];
        $plan = $plans->getById($planId);
        if (!$plan) { answerCbOnce($cb, '❌ پلن یافت نشد!', true); exit('OK'); }
        $choice = getUserMode($chatId);
        $discountCode=''; $discountAmount=0; $finalPrice=(int)$plan['price'];
        if($choice && in_array(($choice['mode'] ?? ''), ['BOT_CRYPTO_CHOICE','BOT_ORDER_DISCOUNT_APPLIED','BOT_ORDER_WALLET_APPLIED'], true) && (int)($choice['payload']['plan_id'] ?? 0)===$planId){
            $discountCode=(string)($choice['payload']['discount_code'] ?? '');
            $discountAmount=(int)($choice['payload']['discount_amount'] ?? 0);
            $finalPrice=(int)($choice['payload']['final_price'] ?? $plan['price']);
            if($discountCode && function_exists('gemacc_discount_preview')){ $chk=gemacc_discount_preview($pdo,(int)$chatId,$discountCode,(int)$plan['price'],(int)$planId,'crypto'); if(empty($chk['valid'])){ answerCbOnce($cb, $chk['message'] ?? 'این کد برای ارز دیجیتال معتبر نیست', true); exit('OK'); } $discountAmount=(int)($chk['discount_amount']??0); $finalPrice=(int)($chk['final_price']??$finalPrice); }
        }
        $res = gemaccCreateOrderCryptoInvoice((int)$chatId, $plan, $finalPrice, $discountCode, $discountAmount, $asset, (int)($choice['payload']['wallet_credit_used'] ?? 0));
        if(empty($res['ok'])) { answerCbOnce($cb, $res['error'] ?? 'خطای ساخت درگاه', true); exit('OK'); }
        if(!empty($res['payment_url'])) gemaccSendGatewayLinkToUser((int)$chatId, (int)($res['order_id'] ?? 0), (string)$res['payment_url'], $finalPrice);
        tg('editMessageText', ['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>gemaccOrderCryptoInvoiceText($res,$plan,$finalPrice) . "\n\n✅ لینک درگاه در همین چت هم ارسال شد.", 'parse_mode'=>'Markdown', 'reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'⚡ ورود به درگاه پرداخت','url'=>(string)$res['payment_url']]],[['text'=>'◀️ بازگشت','callback_data'=>"PAY_CRYPTO_{$planId}"]]]],JSON_UNESCAPED_UNICODE)]);
        exit('OK');
    }

    // پرداخت کارت به کارت
    if (strpos($data, 'PAY_CARD_') === 0) {
        if (!gemaccSalesEnabled()) { answerCbOnce($cb, gemaccSalesClosedMessage(), true); exit('OK'); }
        $planId = (int)str_replace('PAY_CARD_', '', $data);
        $plan = $plans->getById($planId);
        
        if (!$plan) {
            answerCbOnce($cb, '❌ پلن یافت نشد!', true);
            exit('OK');
        }
        
        $paymentCard = function_exists('gemaccGetPaymentCard') ? gemaccGetPaymentCard(true) : ['number'=>getCardNumber(),'owner'=>getSetting('card_owner_name',''),'bank'=>''];
        $cardNumber = (string)($paymentCard['number'] ?? getCardNumber());
        $cardOwner = (string)($paymentCard['owner'] ?? getSetting('card_owner_name', ''));
        $cardBank = (string)($paymentCard['bank'] ?? '');
        
        $discMode = getUserMode($chatId);
        $discountCode=''; $discountAmount=0; $finalPrice=(int)$plan['price'];
        if($discMode && in_array(($discMode['mode'] ?? ''), ['BOT_ORDER_DISCOUNT_APPLIED','BOT_ORDER_WALLET_APPLIED'], true) && (int)($discMode['payload']['plan_id'] ?? 0)===$planId){
            $discountCode=(string)($discMode['payload']['discount_code'] ?? ''); $discountAmount=(int)($discMode['payload']['discount_amount'] ?? 0); $finalPrice=(int)($discMode['payload']['final_price'] ?? $plan['price']);
            if($discountCode && function_exists('gemacc_discount_preview')){ $chk=gemacc_discount_preview($pdo,(int)$chatId,$discountCode,(int)$plan['price'],(int)$planId,'card'); if(empty($chk['valid'])){ answerCbOnce($cb, $chk['message'] ?? 'این کد برای کارت به کارت معتبر نیست', true); exit('OK'); } $discountAmount=(int)($chk['discount_amount']??0); $finalPrice=(int)($chk['final_price']??$finalPrice); }
        }
        $originalFinalPrice = $finalPrice;
        $payableFinalPrice = gemaccCardPayableAmount($finalPrice, (int)$chatId, 'order-card-'.$planId, gemaccBotPaymentToleranceToman($plan));
        setUserMode($chatId, 'ORDER_RECEIPT', ['plan_id' => $planId, 'discount_code'=>$discountCode, 'discount_amount'=>$discountAmount, 'final_price'=>$payableFinalPrice, 'original_final_price'=>$originalFinalPrice, 'wallet_credit_used'=>(int)($discMode['payload']['wallet_credit_used'] ?? 0), 'payable_amount'=>$payableFinalPrice]);
        
        $txt = "💳 **پرداخت کارت به کارت**\n\n";
        $txt .= "━━━━━━━━━━━━━━━━━━━━\n";
        $txt .= "📦 پلن: {$plan['title']}\n";
        $txt .= gemaccCardPayNotice($payableFinalPrice, $originalFinalPrice);
        if($discountAmount>0) $txt .= "🎫 تخفیف: " . money($discountAmount) . " (`{$discountCode}`)\n";
        $txt .= "━━━━━━━━━━━━━━━━━━━━\n\n";
        $txt .= "📋 شماره کارت:\n`{$cardNumber}`\n";
        if (!empty($cardBank)) { $txt .= "🏦 بانک: {$cardBank}\n"; }
        if ($cardOwner) {
            $txt .= "👤 به نام: {$cardOwner}\n";
        }
        $txt .= "\n📸 لطفاً مبلغ را واریز کرده و:\n";
        $txt .= "• **عکس رسید** را ارسال کنید\n";
        $txt .= "• یا **شماره پیگیری/توضیحات** را بنویسید";
        
        tg('editMessageText', [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => $txt,
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode([
                'inline_keyboard' => [
                    [['text' => '◀️ بازگشت', 'callback_data' => "BUY_PLAN_{$planId}"]],
                    [['text' => '❌ انصراف', 'callback_data' => 'BACK_MAIN']],
                ],
            ], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    
    // ═══════════════════ لغو سفارش پرداخت‌نشده استارز ═══════════════════
    if (preg_match('/^CANCEL_STARS_ORDER_(\d+)$/', $data, $mCancelStars)) {
        $orderId = (int)$mCancelStars[1];
        $res = gemaccStarsCancelPendingOrder($pdo, $orderId, (int)$chatId);
        answerCbOnce($cb, !empty($res['ok']) ? '✅ سفارش استارز لغو شد.' : ('❌ '.($res['error'] ?? 'لغو سفارش ناموفق بود')), true);
        try {
            tg('editMessageReplyMarkup', [
                'chat_id' => $chatId,
                'message_id' => $msgId,
                'reply_markup' => ['inline_keyboard' => []],
            ]);
        } catch (Throwable $ignored) {}
        exit('OK');
    }

    // ═══════════════════ پرداخت با استارز تلگرام (داخل ربات) v27.53 ═══════════════════
    if (preg_match('/^PAY_STARS_(\d+)$/', $data, $mStars)) {
        if (!gemaccSalesEnabled()) { answerCbOnce($cb, gemaccSalesClosedMessage(), true); exit('OK'); }
        if (getSetting('stars_enabled','0') !== '1') { answerCbOnce($cb, '⭐ پرداخت با استارز فعلاً غیرفعال است.', true); exit('OK'); }
        $planId = (int)$mStars[1];
        $plan = $plans->getById($planId);
        if (!$plan) { answerCbOnce($cb, '❌ پلن یافت نشد!', true); exit('OK'); }

        // Resolve discount/final price the same way the card flow does.
        $discMode = getUserMode($chatId);
        $discountCode=''; $discountAmount=0; $finalPrice=(int)$plan['price'];
        if($discMode && in_array(($discMode['mode'] ?? ''), ['BOT_ORDER_DISCOUNT_APPLIED','BOT_ORDER_WALLET_APPLIED'], true) && (int)($discMode['payload']['plan_id'] ?? 0)===$planId){
            $discountCode=(string)($discMode['payload']['discount_code'] ?? ''); $discountAmount=(int)($discMode['payload']['discount_amount'] ?? 0); $finalPrice=(int)($discMode['payload']['final_price'] ?? $plan['price']);
            if($discountCode && function_exists('gemacc_discount_preview')){ $chk=gemacc_discount_preview($pdo,(int)$chatId,$discountCode,(int)$plan['price'],(int)$planId,'stars'); if(empty($chk['valid'])){ answerCbOnce($cb, $chk['message'] ?? 'این کد برای استارز معتبر نیست', true); exit('OK'); } $discountAmount=(int)($chk['discount_amount']??0); $finalPrice=(int)($chk['final_price']??$finalPrice); $discount=$chk; }
        }
        $finalPrice = max(0,(int)$finalPrice);

        $rate = gemaccStarsRateToman();
        $starsAmount = gemaccStarsAmountFromToman($finalPrice);

        try {
            $pdo->beginTransaction();
            $months = max(1,(int)($plan['months'] ?? 1));
            $requestedSeats = max(1,(int)($plan['seats'] ?? $plan['user_count'] ?? 1));
            $rawSvc = strtolower((string)($plan['protocol'] ?? 'openvpn_l2tp'));
            $serviceType = in_array($rawSvc,['windscribe','expressvpn','v2ray'],true) ? $rawSvc : 'openvpn_l2tp';
            $trafficType = $plan['traffic_type'] ?? 'unlimited';
            $trafficLimitGb = $trafficType === 'limited' ? ($plan['traffic_limit_gb'] ?? null) : null;
            $meta = json_encode(['original_price'=>(int)$plan['price'],'discount_code'=>$discountCode,'discount_amount'=>$discountAmount,'final_price'=>$finalPrice,'stars_amount'=>$starsAmount,'payment'=>'stars','source'=>'bot'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);

            $purchasePrice = function_exists('gemacc_plan_effective_purchase_price') ? gemacc_plan_effective_purchase_price($plan) : (int)($plan['purchase_price'] ?? 0);
            $autoCancelSeconds = function_exists('gemaccStarsPendingAutoCancelSeconds') ? gemaccStarsPendingAutoCancelSeconds() : 15;
            $st = $pdo->prepare("INSERT INTO orders(user_id,plan_id,category_id,plan_code,plan_title,service_type,duration,months,requested_seats,required_accounts,delivered_accounts,traffic_type,traffic_limit_gb,price,purchase_price,payment_method,payment_status,delivery_status,status,receipt_text,stars_expires_at) VALUES(?,?,?,?,?,?,?,?,?,?,0,?,?,?,?,'STARS','pending','pending','PENDING',?,DATE_ADD(NOW(), INTERVAL {$autoCancelSeconds} SECOND))");
            $st->execute([$chatId,$planId,$plan['category_id']??null,$plan['code']??null,$plan['title']??'OpenVPN | L2TP',$serviceType,$plan['duration']??null,$months,$requestedSeats,$requestedSeats,$trafficType,$trafficLimitGb,$finalPrice,$purchasePrice,$meta]);
            $orderId = (int)$pdo->lastInsertId();
            if (function_exists('gemaccStarsEnqueueAutoCancel')) { gemaccStarsEnqueueAutoCancel($pdo, $orderId, (int)$chatId); }
            if(!empty($discountCode) && isset($discount) && !empty($discount['valid']) && function_exists('gemacc_discount_apply')) { gemacc_discount_apply($pdo,$discount,(int)$chatId,$orderId); }

            // sendInvoice: prices is a NATIVE array. Invoice text now includes the full selected service summary.
            $invoiceDescription = gemaccStarsInvoiceDescription($plan, $finalPrice, $starsAmount, $requestedSeats);
            $payLabel = '⭐ پرداخت ' . $starsAmount . ' استارز';
            $invResp = tg('sendInvoice', [
                'chat_id'     => $chatId,
                'title'       => mb_substr('خرید '.($plan['title'] ?? 'سرویس'),0,32),
                'description' => $invoiceDescription,
                'payload'     => 'STARS_ORDER_'.$orderId,
                'currency'    => 'XTR',
                'prices'      => [['label'=>number_format($finalPrice).' تومان = '.$starsAmount.'⭐','amount'=>$starsAmount]],
                'reply_markup'=> [
                    'inline_keyboard' => [
                        [[ 'text' => $payLabel, 'pay' => true ]],
                        [[ 'text' => '❌ لغو سفارش', 'callback_data' => 'CANCEL_STARS_ORDER_'.$orderId ]],
                    ],
                ],
            ]);
            $okInv = is_array($invResp) ? (bool)($invResp['ok'] ?? false) : false;
            if (!$okInv) {
                $pdo->rollBack();
                $errd = is_array($invResp) ? ($invResp['description'] ?? '') : '';
                answerCbOnce($cb, '❌ ساخت فاکتور استارز ناموفق بود'.($errd?": {$errd}":'.'), true);
                exit('OK');
            }
            $pdo->commit();
            answerCbOnce($cb, "⭐ فاکتور استارز ({$starsAmount}⭐) ارسال شد. اگر تا ۱۵ ثانیه پرداخت انجام نشود، سفارش خودکار لغو می‌شود.");
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            error_log('[bot PAY_STARS] '.$e->getMessage());
            answerCbOnce($cb, '❌ خطا در ساخت فاکتور استارز. بعداً تلاش کنید.', true);
        }
        exit('OK');
    }

    // ═══════════════════ کیف پول ═══════════════════
    
    if ($data === 'WALLET') {
        $balance = $wallet->getBalance($chatId);
        
        $txt = gemaccAccountText($chatId);
        
        $kb = [
            'inline_keyboard' => [
                [['text' => '➕ افزایش موجودی', 'callback_data' => 'ADD_BALANCE']],
                [['text' => '📜 تاریخچه تراکنش‌ها', 'callback_data' => 'WALLET_HISTORY']],
                [['text' => '🎫 کدهای تخفیف من', 'callback_data' => 'MY_DISCOUNTS']],
                [['text' => '◀️ بازگشت', 'callback_data' => 'BACK_MAIN']],
            ],
        ];
        
        tg('editMessageText', [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => $txt,
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode($kb, JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    
    if ($data === 'MY_DISCOUNTS') {
        try {
            $pdo->exec("CREATE TABLE IF NOT EXISTS user_discount_codes (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, discount_id INT UNSIGNED NULL, code VARCHAR(50) NOT NULL, title VARCHAR(255) NULL, message TEXT NULL, source VARCHAR(50) DEFAULT 'admin', is_active TINYINT(1) NOT NULL DEFAULT 1, used_at DATETIME NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(id), UNIQUE KEY uniq_user_code(user_id,code), INDEX idx_udc_user(user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
            $st=$pdo->prepare("SELECT code,title,used_at,created_at FROM user_discount_codes WHERE user_id=? AND is_active=1 ORDER BY used_at IS NULL DESC, created_at DESC LIMIT 20"); $st->execute([$chatId]); $rows=$st->fetchAll(PDO::FETCH_ASSOC);
            $txt="🎫 **کدهای تخفیف من**\n\n";
            if(!$rows) $txt.="هنوز کد تخفیف اختصاصی ندارید.";
            foreach($rows as $r){ $txt.="`{$r['code']}` - ".($r['title'] ?: 'کد اختصاصی')." - ".($r['used_at']?'مصرف‌شده':'قابل استفاده')."\n"; }
        } catch(Throwable $e){ $txt='فعلاً کدی ثبت نشده است.'; }
        tg('editMessageText',['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>$txt,'parse_mode'=>'Markdown','reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'◀️ حساب کاربری','callback_data'=>'WALLET']]]],JSON_UNESCAPED_UNICODE)]);
        exit('OK');
    }

    if (strpos($data, 'WALLET_CRYPTO_AMOUNT_') === 0 || strpos($data, 'WALLET_CARD_AMOUNT_') === 0) {
        $isCrypto = strpos($data, 'WALLET_CRYPTO_AMOUNT_') === 0;
        $amountPart = str_replace($isCrypto ? 'WALLET_CRYPTO_AMOUNT_' : 'WALLET_CARD_AMOUNT_', '', $data);
        if ($amountPart === 'CUSTOM') {
            setUserMode($chatId, $isCrypto ? 'WALLET_DEPOSIT_CRYPTO' : 'WALLET_DEPOSIT', []);
            tg('editMessageText',['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>'✍️ مبلغ دلخواه را به تومان ارسال کنید:','reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'◀️ بازگشت','callback_data'=>'ADD_BALANCE']]]],JSON_UNESCAPED_UNICODE)]);
            exit('OK');
        }
        $amount=(int)$amountPart;
        if($isCrypto){ setUserMode($chatId, 'WALLET_DEPOSIT_RECEIPT', ['amount'=>$amount,'payment_method'=>'crypto']); tg('editMessageText',['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>gemaccManualCryptoText($amount),'parse_mode'=>'Markdown','reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'❌ انصراف','callback_data'=>'WALLET']]]],JSON_UNESCAPED_UNICODE)]); exit('OK'); }
        $payableAmount = $amount;
        setUserMode($chatId, 'WALLET_DEPOSIT_RECEIPT', ['amount'=>$amount, 'payable_amount'=>$payableAmount]);
        $cardNumber=getCardNumber(); $cardOwner=getSetting('card_owner_name','');
        $txt="💳 **افزایش موجودی کارت‌به‌کارت**\n\n💰 مبلغ شارژ حساب: ".money($amount)."\n".gemaccCardPayNotice($payableAmount,$amount)."\n📋 شماره کارت:\n`{$cardNumber}`\n".($cardOwner?"👤 به نام: {$cardOwner}\n":'')."\nعکس رسید یا رسید متنی/شماره پیگیری را ارسال کنید.";
        tg('editMessageText',['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>$txt,'parse_mode'=>'Markdown','reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'❌ انصراف','callback_data'=>'WALLET']]]],JSON_UNESCAPED_UNICODE)]); exit('OK');
    }

    if ($data === 'ADD_BALANCE_CRYPTO') {
        setUserMode($chatId, 'WALLET_DEPOSIT_CRYPTO', []);
        $txt = "🪙 **افزایش موجودی با ارز دیجیتال**

";
        $txt .= "یکی از مبلغ‌های آماده را انتخاب کنید یا مبلغ دلخواه بفرستید.\n\n";
        $txt .= "یکی از مبلغ‌های آماده را انتخاب کنید یا مبلغ دلخواه بفرستید.\n\n💡 حداقل مبلغ: ۱۰,۰۰۰ تومان";
        tg('editMessageText', [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => $txt,
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode(gemaccWalletAmountKeyboard('WALLET_CRYPTO_AMOUNT','ADD_BALANCE'), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    // ── Wallet top-up with Telegram Stars (v27.53, bug #4) ──
    if ($data === 'ADD_BALANCE_STARS') {
        if (getSetting('stars_enabled','0') !== '1') { answerCbOnce($cb, '⭐ پرداخت با استارز فعلاً غیرفعال است.', true); exit('OK'); }
        clearUserMode($chatId);
        $rate = max(1,(int)getSetting('stars_rate_toman','7000'));
        $txt = "⭐ **شارژ کیف پول با استارز تلگرام**\n\nمبلغ شارژ را انتخاب کنید یا مبلغ دلخواه بفرستید.\n\n💱 نرخ فعلی: هر ⭐ معادل ".money($rate)."\n💡 حداقل مبلغ: ۱۰,۰۰۰ تومان";
        tg('editMessageText', [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => $txt,
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode(gemaccWalletAmountKeyboard('WALLET_STARS_AMOUNT','ADD_BALANCE'), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    if (strpos($data, 'WALLET_STARS_AMOUNT_') === 0) {
        if (getSetting('stars_enabled','0') !== '1') { answerCbOnce($cb, '⭐ پرداخت با استارز فعلاً غیرفعال است.', true); exit('OK'); }
        $amountPart = str_replace('WALLET_STARS_AMOUNT_', '', $data);
        if ($amountPart === 'CUSTOM') {
            setUserMode($chatId, 'WALLET_DEPOSIT_STARS', []);
            tg('editMessageText',['chat_id'=>$chatId,'message_id'=>$msgId,'text'=>'✍️ مبلغ دلخواه شارژ را به تومان ارسال کنید:','reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'◀️ بازگشت','callback_data'=>'ADD_BALANCE']]]],JSON_UNESCAPED_UNICODE)]);
            exit('OK');
        }
        $amount = (int)$amountPart;
        gemaccBotStartStarsWalletTopup((int)$chatId, $amount, (int)$msgId, $cb);
        exit('OK');
    }

    if ($data === 'ADD_BALANCE') {
        clearUserMode($chatId);
        $bonusEnabled = getSetting('wallet_bonus_enabled', '0') === '1';
        $bonusPercent = (int)getSetting('wallet_bonus_percent', '10');
        $bonusMinAmount = (int)getSetting('wallet_bonus_min_amount', '100000');
        $txt = "💳 **افزایش موجودی**

روش شارژ کیف پول را انتخاب کنید.

";
        $txt .= "🪙 پرداخت کریپتو همین‌جا داخل ربات فاکتور می‌سازد و دیگر اول به مینی‌اپ منتقل نمی‌شوید.";
        if ($bonusEnabled) {
            $txt .= "

🎁 بونوس شارژ: بالای " . number_format($bonusMinAmount) . " تومان، {$bonusPercent}٪ بیشتر دریافت می‌کنید.";
        }
        tg('editMessageText', [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => $txt,
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode([
                'inline_keyboard' => array_values(array_filter([
                    [['text' => '💳 کارت‌به‌کارت', 'callback_data' => 'ADD_BALANCE_CARD']],
                    [['text' => '🪙 ارز دیجیتال', 'callback_data' => 'ADD_BALANCE_CRYPTO']],
                    (getSetting('stars_enabled','0') === '1') ? [['text' => '⭐ استارز تلگرام', 'callback_data' => 'ADD_BALANCE_STARS']] : null,
                    [['text' => '◀️ بازگشت', 'callback_data' => 'WALLET']],
                ])),
            ], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    if ($data === 'ADD_BALANCE_CARD') {
        setUserMode($chatId, 'WALLET_DEPOSIT', []);
        $txt = "💳 **افزایش موجودی کارت‌به‌کارت**

مبلغ مورد نظر را به تومان ارسال کنید:

💡 حداقل مبلغ: ۱۰,۰۰۰ تومان";
        tg('editMessageText', [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => $txt,
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode(gemaccWalletAmountKeyboard('WALLET_CARD_AMOUNT','ADD_BALANCE'), JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    if ($data === 'WALLET_HISTORY') {
        $history = $wallet->getUserHistory($chatId, 10);
        
        if (empty($history)) {
            $txt = "📜 تاریخچه تراکنش‌ها خالی است.";
        } else {
            $txt = "📜 **تاریخچه تراکنش‌ها**\n\n";
            foreach ($history as $tx) {
                $typeLabel = Wallet::getTypeLabel($tx['type']);
                $statusLabel = Wallet::getStatusLabel($tx['status']);
                $sign = in_array($tx['type'], ['DEPOSIT', 'REFUND', 'ADMIN']) ? '+' : '-';
                
                $txt .= "{$typeLabel} | {$sign}" . money($tx['amount']) . "\n";
                $txt .= "📊 {$statusLabel} | " . jdate('d/m', strtotime($tx['created_at'])) . "\n";
                $txt .= "━━━━━━━━━━━━━━\n";
            }
        }
        
        tg('editMessageText', [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => $txt,
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode([
                'inline_keyboard' => [[['text' => '◀️ بازگشت', 'callback_data' => 'WALLET']]],
            ], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    

    // v27.50: legacy ticket reply buttons and close buttons used by support notifications
    if (preg_match('/^(?:ADMIN_REPLY|TICKET_REPLY)__(\d+)__(\d+)$/', (string)$data, $m)) {
        answerCbOnce($cb, 'متن پاسخ را ارسال کنید');
        $ticketId = (int)$m[1];
        $uid = (int)$m[2];
        setUserMode($chatId, 'ADMIN_CHAT_REPLY', ['user_id'=>$uid, 'chat_type'=>'support', 'ticket_id'=>$ticketId]);
        tg('sendMessage', [
            'chat_id'=>$chatId,
            'text'=>"✍️ متن پاسخ پشتیبانی به کاربر {$uid} را ارسال کنید.\nبرای لغو /cancel را بزنید.",
            'reply_markup'=>json_encode(['inline_keyboard'=>[[['text'=>'❌ انصراف','callback_data'=>'ADMIN_CHAT_VIEW_support_'.$uid]]]], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    if (preg_match('/^ADMIN_CHAT_CLOSE_(\d+)_(\d+)$/', (string)$data, $m)) {
        $ticketId = (int)$m[1];
        $uid = (int)$m[2];
        $res = function_exists('gemaccSupportCloseTicket') ? gemaccSupportCloseTicket($pdo, $ticketId, (int)$chatId, $uid) : ['ok'=>false,'error'=>'Ticket service unavailable'];
        answerCbOnceParams(['callback_query_id'=>$cb['id'], 'text'=>!empty($res['ok'])?'✅ تیکت بسته شد':'❌ '.($res['error'] ?? 'خطا'), 'show_alert'=>true]);
        if (!empty($res['ok'])) {
            try { gemaccAdminActionNotify('تیکت پشتیبانی بسته شد', ['تیکت'=>$ticketId, 'کاربر'=>$uid], $chatId); } catch (Throwable $ignored) {}
        }
        exit('OK');
    }

    // ═══════════════════ پشتیبانی ═══════════════════
    
    if ($data === 'SUPPORT') {
        answerCbOnce($cb);
        $supportGroup = trim((string)getSetting('support_group', SUPPORT_GROUP));
        $supportRows = [];
        if ($supportGroup !== '') {
            $supportRows[] = [['text' => '👥 گروه پرسش و پاسخ کاربران', 'url' => $supportGroup]];
        }
        $supportRows[] = [['text' => '✉️ پیام به پشتیبانی', 'callback_data' => 'SEND_SUPPORT_MESSAGE']];
        $supportRows[] = [['text' => '◀️ بازگشت', 'callback_data' => 'BACK_MAIN']];
        $kb = ['inline_keyboard' => $supportRows];

        gemaccEditTextOrSendFromCallback((int)$chatId, $msgId ? (int)$msgId : null, [
            'text'         => "💬 **پشتیبانی**\n\nاز گزینه‌های زیر استفاده کنید:",
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode($kb, JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    
    if ($data === 'SEND_SUPPORT_MESSAGE') {
        $ticketId = 0;
        try { if (function_exists('gemaccSupportOpenTicketForUser')) $ticketId = gemaccSupportOpenTicketForUser($pdo, (int)$chatId, 'پیام پشتیبانی ربات'); } catch (Throwable $e) { error_log('[support_start_ticket] '.$e->getMessage()); }
        setUserMode($chatId, 'SUPPORT_MESSAGE', ['ticket_id'=>$ticketId]);
        $payloadSupportAsk = [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => "✉️ **پیام به پشتیبانی**

پیام خود را ارسال کنید." . ($ticketId ? "

🧾 شماره تیکت: #{$ticketId}" : ''),
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode([
                'inline_keyboard' => [[['text' => '❌ انصراف', 'callback_data' => 'SUPPORT']]],
            ], JSON_UNESCAPED_UNICODE),
        ];
        $editRes = tg('editMessageText', $payloadSupportAsk);
        if (is_array($editRes) && empty($editRes['ok'])) {
            unset($payloadSupportAsk['message_id']);
            tg('sendMessage', $payloadSupportAsk);
        }
        exit('OK');
    }
    
    // ═══════════════════ آموزش ═══════════════════
    
    if ($data === 'TUTORIAL') {
        $tutorialMsg = $tutorials->buildServiceSelectionMessage();
        tg('editMessageText', [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => $tutorialMsg['text'],
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode($tutorialMsg['keyboard'], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }

    if (strpos($data, 'TUTORIAL_SERVICE_') === 0) {
        $serviceType = str_replace('TUTORIAL_SERVICE_', '', $data);
        if ($serviceType === 'openvpn_l2tp' && !gemaccUserHasDeliveredService($pdo, (int)$chatId, 'openvpn_l2tp')) {
            answerCbOnce($cb, 'آموزش و کانفیگ OpenVPN فقط برای خریداران نمایش داده می‌شود.', true);
            exit('OK');
        }
        $tutorialMsg = $tutorials->buildDeviceSelectionMessage($serviceType);
        tg('editMessageText', [
            'chat_id'      => $chatId,
            'message_id'   => $msgId,
            'text'         => $tutorialMsg['text'],
            'parse_mode'   => 'Markdown',
            'reply_markup' => json_encode($tutorialMsg['keyboard'], JSON_UNESCAPED_UNICODE),
        ]);
        exit('OK');
    }
    
    // ═══════════════════ انتخاب نوع کد تخفیف ═══════════════════
    

    if (strpos($data, 'DISCOUNT_SCOPE_') === 0) {
        $mode = getUserMode($chatId);
        if ($mode && $mode['mode'] === 'ADMIN_ADD_DISCOUNT') {
            $payload = $mode['payload'] ?? [];
            $scopeMap = ['DISCOUNT_SCOPE_ALL'=>'all','DISCOUNT_SCOPE_CARD'=>'card','DISCOUNT_SCOPE_CRYPTO'=>'crypto'];
            $payload['payment_scope'] = $scopeMap[$data] ?? 'all';
            $payload['created_by'] = $chatId;
            $discountCode->create($payload);
            clearUserMode($chatId);
            $scopeFa = ['all'=>'همه روش‌ها','card'=>'کارت به کارت','crypto'=>'ارز دیجیتال'][$payload['payment_scope']] ?? 'همه روش‌ها';
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text' => "✅ کد تخفیف `{$payload['code']}` با موفقیت ایجاد شد.
روش پرداخت مجاز: {$scopeFa}",
                'parse_mode' => 'Markdown',
                'reply_markup' => json_encode(kbAdminPanel(), JSON_UNESCAPED_UNICODE),
            ]);
            gemaccAdminActionNotify('کد تخفیف جدید از ربات ایجاد شد', ['کد'=>$payload['code'] ?? '-', 'نوع'=>$payload['type'] ?? '-', 'مقدار'=>$payload['value'] ?? '-', 'روش پرداخت'=>$scopeFa], $chatId);
        }
        exit('OK');
    }

    if ($data === 'DISCOUNT_TYPE_PERCENT' || $data === 'DISCOUNT_TYPE_FIXED') {
        $mode = getUserMode($chatId);
        if ($mode && $mode['mode'] === 'ADMIN_ADD_DISCOUNT') {
            $payload = $mode['payload'] ?? [];
            $payload['type'] = ($data === 'DISCOUNT_TYPE_PERCENT') ? 'PERCENT' : 'FIXED';
            $payload['step'] = 'value';
            setUserMode($chatId, 'ADMIN_ADD_DISCOUNT', $payload);
            
            $label = ($payload['type'] === 'PERCENT') ? 'درصد تخفیف (مثال: 20 برای ۲۰٪)' : 'مبلغ تخفیف به تومان';
            
            tg('sendMessage', [
                'chat_id' => $chatId,
                'text'    => "📝 {$label} را وارد کنید:",
            ]);
        }
        exit('OK');
    }
    
    
    if (strpos($data, 'TUTORIAL_') === 0) {
        // فرمت استاندارد: TUTORIAL_{service_type}_{DEVICE}
        // مثال: TUTORIAL_v2ray_ANDROID یا TUTORIAL_openvpn_l2tp_IPHONE
        $serviceType = 'openvpn_l2tp';
        $device = '';
        if (preg_match('/^TUTORIAL_(openvpn_l2tp|windscribe|expressvpn|v2ray)_(ANDROID|IPHONE|WINDOWS)$/', (string)$data, $tm)) {
            $serviceType = $tm[1];
            $device = $tm[2];
        } else {
            // سازگاری با callbackهای قدیمی
            $device = strtoupper(trim(str_replace('TUTORIAL_', '', (string)$data)));
        }

        if ($serviceType === 'openvpn_l2tp' && !gemaccUserHasDeliveredService($pdo, (int)$chatId, 'openvpn_l2tp')) {
            answerCbOnce($cb, 'آموزش و کانفیگ OpenVPN فقط برای خریداران نمایش داده می‌شود.', true);
            exit('OK');
        }
        $tutorial = $tutorials->getByDevice($device, $serviceType);
        $deviceName = Tutorials::getDeviceName($device);
        $emoji = Tutorials::getDeviceEmoji($device);
        $serviceName = Tutorials::getServiceName($serviceType);

        if (!$tutorial || empty($tutorial['text_content'])) {
            $txt = tgHtmlEscape("{$emoji} آموزش {$serviceName} - {$deviceName} هنوز ثبت نشده است.");
        } else {
            $tutorialBody = str_replace(["\r\n", "\r"], "\n", trim((string)$tutorial['text_content']));
            $txt = $emoji . ' <b>آموزش ' . tgHtmlEscape($serviceName) . ' - ' . tgHtmlEscape($deviceName) . "</b>\n\n" . tgHtmlEscape($tutorialBody);
        }

        $backKb = json_encode([
            'inline_keyboard' => [[['text' => '◀️ بازگشت', 'callback_data' => 'TUTORIAL_SERVICE_'.$serviceType]]],
        ], JSON_UNESCAPED_UNICODE);

        $mediaFileId = $tutorial['media_file_id'] ?? ($tutorial['video_file_id'] ?? null);
        $mediaType = $tutorial['media_type'] ?? (!empty($tutorial['video_file_id']) ? 'video' : null);

        if ($tutorial && $mediaFileId) {
            if ($msgId) {
                tg('deleteMessage', ['chat_id' => $chatId, 'message_id' => $msgId]);
            }

            if ($mediaType === 'photo') {
                tg('sendPhoto', [
                    'chat_id' => $chatId,
                    'photo'   => $mediaFileId,
                    'caption' => "🖼 آموزش {$serviceName} - {$deviceName}",
                ]);
            } elseif ($mediaType === 'document') {
                tg('sendDocument', [
                    'chat_id' => $chatId,
                    'document'=> $mediaFileId,
                    'caption' => "📎 فایل آموزش {$serviceName} - {$deviceName}",
                ]);
            } else {
                tg('sendVideo', [
                    'chat_id' => $chatId,
                    'video'   => $mediaFileId,
                    'caption' => "📹 ویدیو آموزش {$serviceName} - {$deviceName}",
                ]);
            }

            tg('sendMessage', [
                'chat_id'      => $chatId,
                'text'         => $txt,
                'parse_mode'   => 'HTML',
                'disable_web_page_preview' => true,
                'reply_markup' => $backKb,
            ]);
        } else {
            tg('editMessageText', [
                'chat_id'      => $chatId,
                'message_id'   => $msgId,
                'text'         => $txt,
                'parse_mode'   => 'HTML',
                'disable_web_page_preview' => true,
                'reply_markup' => $backKb,
            ]);
        }

        exit('OK');
    }

// ═══════════════════ سرویس‌های من (صفحه‌بندی و جزئیات) ═══════════════════
    if (strpos($data, 'MY_SERVICES_PAGE_') === 0) {
        answerCbOnce($cb);
        $page = (int)str_replace('MY_SERVICES_PAGE_', '', $data);
        gemaccShowMyServices($chatId, $msgId, max(1,$page));
        exit('OK');
    }
    if (strpos($data, 'MY_ORDER_SERVICES_') === 0) {
        answerCbOnce($cb);
        $orderId = (int)str_replace('MY_ORDER_SERVICES_', '', $data);
        gemaccShowOrderServices($chatId, $msgId, $orderId);
        exit('OK');
    }
    if (strpos($data, 'MY_SERVICE_DETAIL_') === 0) {
        answerCbOnce($cb);
        $deliveryId = (int)str_replace('MY_SERVICE_DETAIL_', '', $data);
        gemaccShowServiceDetail($chatId, $msgId, $deliveryId);
        exit('OK');
    }
    // ═══════════════════ پنل ادمین ═══════════════════
    
    if (!isAdmin($chatId)) {
        exit('OK');
    }
    
    // Include admin handlers
    require __DIR__ . '/bot_admin.php';
    
    exit('OK');
}

exit('OK');
