<?php
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/telegram.php';
require_once __DIR__ . '/includes/screen.php';
require_once __DIR__ . '/includes/settings.php';
require_once __DIR__ . '/includes/state.php';
require_once __DIR__ . '/includes/helpers.php';
require_once __DIR__ . '/includes/backup.php';
require_once __DIR__ . '/includes/restore.php';
require_once __DIR__ . '/handlers/start.php';
require_once __DIR__ . '/handlers/purchase.php';
require_once __DIR__ . '/handlers/invoice.php';
require_once __DIR__ . '/handlers/wallet.php';
require_once __DIR__ . '/handlers/test.php';
require_once __DIR__ . '/handlers/account.php';
require_once __DIR__ . '/handlers/support.php';
require_once __DIR__ . '/handlers/help.php';
require_once __DIR__ . '/handlers/ads.php';
require_once __DIR__ . '/handlers/admin.php';
require_once __DIR__ . '/handlers/link_sub.php';
require_once __DIR__ . '/handlers/my_configs.php';
require_once __DIR__ . '/handlers/discount.php';
require_once __DIR__ . '/handlers/wheel.php';

/**
 * TEMPORARY DEBUG LOGGING — writes into vpnbot/debug.log (next to this
 * file), regardless of the host's own PHP error_log setting/location.
 * Covers two cases:
 *   1) A normal caught exception (see the try/catch below).
 *   2) A FATAL error that is NOT catchable at all — e.g. PHP's own
 *      max_execution_time timeout, or "Allowed memory size exhausted".
 *      Those kill the script instantly, bypassing try/catch entirely,
 *      which is exactly what strands a "⏳ در حال تهیه..." screen
 *      forever with no follow-up message. register_shutdown_function()
 *      still runs even after that kind of fatal, so this is the only
 *      way to see it happened.
 * Once the mystery is solved, this whole register_shutdown_function()
 * block (and the debug_log() calls) can be deleted again.
 */
function debug_log(string $line): void
{
    @file_put_contents(__DIR__ . '/debug.log', '[' . date('Y-m-d H:i:s') . "] $line\n", FILE_APPEND);
}

register_shutdown_function(function () {
    $err = error_get_last();
    if ($err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
        debug_log("FATAL: {$err['message']} in {$err['file']}:{$err['line']}");
    }
});

$update = json_decode(file_get_contents('php://input'), true);
if (!$update) { http_response_code(200); exit; }

try {
    if (isset($update['message'])) {
        handle_message($update['message']);
    } elseif (isset($update['callback_query'])) {
        handle_callback($update['callback_query']);
    }
} catch (Throwable $e) {
    debug_log('EXCEPTION: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
    if (DEBUG_MODE) {
        error_log($e->getMessage() . "\n" . $e->getTraceAsString());
    }
    // Admins are told in the chat — otherwise a failing handler is just a button that does nothing.
    try {
        $whoId = $update['message']['from']['id'] ?? ($update['callback_query']['from']['id'] ?? null);
        if ($whoId !== null && is_admin((int)$whoId)) {
            tg_send((int)$whoId, "⚠️ خطای داخلی ربات:\n<code>" .
                htmlspecialchars(text_cut($e->getMessage(), 300), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . "</code>\n" .
                basename($e->getFile()) . ':' . $e->getLine());
        }
    } catch (Throwable $e2) {
        // never let the error report itself break the webhook
    }
}

http_response_code(200);

/* =====================================================================
 *  MESSAGE HANDLER
 * ===================================================================== */
function handle_message(array $message): void
{
    $chatId = $message['chat']['id'];
    $from   = $message['from'];
    $tgId   = $from['id'];

    // Clean-chat: remove the user's own message once we've read it, so it
    // doesn't linger next to the bot's single screen message. Applies to
    // typed replies (a name, a duplicate-name error, an admin form answer)
    // AND to reply-keyboard button taps (خرید اشتراک، پنل مدیریت، ...),
    // which Telegram sends as a normal text message from the user. This is
    // best-effort — Telegram only allows deleting messages sent within the
    // last 48h — failures are silently ignored, nothing depends on it.
    // EXCEPTION: a payment receipt — the photo of a wallet charge or of a card purchase (invoice step),
    // or a receipt FILE on the purchase invoice — stays in the chat (it is the user's proof of payment).
    // It is never deleted — it just scrolls up as the bot's replies land below it.
    $keepMessage = false;
    if (isset($message['photo'])) {
        $st = get_state($tgId);
        $keepMessage = $st && in_array($st['step'], ['charge_receipt', 'buy_confirm'], true);
    } elseif (isset($message['document'])) {
        $st = get_state($tgId);
        // a_restore_wait_file: the owner's backup file must survive until it is downloaded (admin_restore_receive_file deletes it itself)
        $keepMessage = $st && in_array($st['step'], ['buy_confirm', 'a_restore_wait_file'], true);
    }
    if (isset($message['message_id']) && !$keepMessage) {
        tg_delete($chatId, (int)$message['message_id']);
    }

    // Contact shared (phone number)
    if (isset($message['contact'])) {
        $user = get_or_create_user($from);
        db()->prepare('UPDATE users SET phone = ? WHERE id = ?')
            ->execute([$message['contact']['phone_number'], $user['id']]);
        send_main_menu($chatId, $tgId, "✅ شماره تماس شما ثبت شد.");
        return;
    }

    $text = trim($message['text'] ?? '');

    if (str_starts_with($text, '/start')) {
        handle_start($message);
        return;
    }

    $user = get_or_create_user($from);

    if (!user_passed_force_join($tgId)) {
        send_force_join_prompt($chatId);
        return;
    }

    // Photo received (receipt for wallet charge, or admin uploading the QR background)
    if (isset($message['photo'])) {
        $state = get_state($tgId);
        $fileId = end($message['photo'])['file_id'];
        if ($state && $state['step'] === 'charge_receipt') {
            charge_receive_receipt($chatId, $tgId, $user['id'], $fileId, (int)$state['data']['amount']);
        } elseif ($state && $state['step'] === 'buy_confirm') {
            buy_receive_receipt($chatId, $tgId, $user, $state['data'], $fileId, false);
        } elseif ($state && $state['step'] === 'a_qrbg_wait_photo' && is_admin($tgId)) {
            admin_qrbg_save($chatId, $tgId, $fileId);
        }
        return;
    }

    // Receipt sent as a FILE (PDF, or an image sent "as file") on the purchase invoice
    if (isset($message['document'])) {
        $state = get_state($tgId);
        if ($state && $state['step'] === 'buy_confirm') {
            $doc  = $message['document'];
            $mime = (string)($doc['mime_type'] ?? '');
            if (str_starts_with($mime, 'image/') || $mime === 'application/pdf') {
                buy_receive_receipt($chatId, $tgId, $user, $state['data'], (string)$doc['file_id'], true);
            } else {
                tg_send_notice($chatId, '⚠️ فقط عکس یا فایل PDF به‌عنوان رسید قابل قبول است.');
            }
        } elseif ($state && $state['step'] === 'a_restore_wait_file' && is_admin($tgId)) {
            // «♻️ بازیابی دیتابیس» — the backup .sql (owner-only, re-checked inside)
            admin_restore_receive_file($chatId, $tgId, $message);
        }
        return;
    }

    // Is user mid-conversation (state machine)?
    $state = get_state($tgId);

    // A tap on a reply-keyboard button (a main-menu label or «پنل مدیریت») is ALWAYS a menu tap. A half-finished text
    // form must not swallow it as its "answer" — that is how «👨‍💼 پنل مدیریت» could get saved as the NAME of a customer
    // button (the customers then saw a button called "پنل مدیریت"). So drop the pending form and handle the tap.
    if ($state && $text !== '' && ($text === ADMIN_PANEL_LABEL || button_label_to_key($text) !== null)) {
        clear_state($tgId);
        $state = null;
    }

    if ($state) {
        route_text_by_state($chatId, $tgId, $user, $state, $text);
        return;
    }

    // Otherwise: main-menu reply-keyboard button pressed
    route_main_menu_text($chatId, $tgId, $user, $text);
}

function route_text_by_state(int $chatId, int $tgId, array $user, array $state, string $text): void
{
    $step = $state['step'];
    $data = $state['data'];

    switch ($step) {
        case 'buy_name_input':
            buy_receive_new_name($chatId, $tgId, $user['id'], $text);
            break;
        case 'charge_amount':
            charge_receive_amount($chatId, $tgId, $text);
            break;
        case 'link_sub_input':
            link_sub_receive($chatId, $tgId, $user['id'], $text);
            break;
        case 'buy_discount_input': // 🎟 «افزودن کد تخفیف» on the purchase invoice
            disc_receive_code($chatId, $tgId, $user, $data, $text);
            break;
        default:
            // any admin text-input form
            if (str_starts_with($step, 'a_hlp_')) {
                hlp_admin_text_step($chatId, $tgId, $step, $data, $text);
            } elseif (str_starts_with($step, 'a_ads_')) {
                ads_admin_text_step($chatId, $tgId, $step, $data, $text);
            } elseif (str_starts_with($step, 'a_disc_')) {
                admin_disc_text_step($chatId, $tgId, $step, $data, $text);
            } elseif (str_starts_with($step, 'a_wheel_')) {
                admin_wheel_text_step($chatId, $tgId, $step, $data, $text);
            } elseif (str_starts_with($step, 'a_')) {
                admin_handle_text_step($chatId, $tgId, $step, $data, $text);
            } else {
                // unrecognised state - fall back to main menu button handling
                route_main_menu_text($chatId, $tgId, $user, $text);
            }
    }
}

function route_main_menu_text(int $chatId, int $tgId, array $user, string $text): void
{
    if ($text === '👨‍💼 پنل مدیریت' && is_admin($tgId)) {
        tg_send($chatId, '👨‍💼 پنل مدیریت', remove_kb());
        admin_main_menu($chatId, $tgId);
        return;
    }

    $key = button_label_to_key($text);
    switch ($key) {
        case 'buy':
            buy_start($chatId, $tgId, $user['id']);
            break;
        case 'plans_info':
            show_plans_info($chatId);
            break;
        case 'free_test':
            free_test($chatId, $tgId, $user);
            break;
        case 'account':
            show_account($chatId, $user);
            break;
        case 'charge':
            wallet_show($chatId, $tgId); // 💰 کیف پول: موجودی + ➕ افزایش موجودی
            break;
        case 'referral':
            show_referral($chatId, $user);
            break;
        case 'reseller':
            screen($chatId, "این قابلیت به‌زودی فعال می‌شود.", kb_back());
            break;
        case 'support':
            show_support($chatId);
            break;
        case 'tariffs':
            screen($chatId, setting('tariffs_text', 'تعرفه‌ها هنوز تنظیم نشده‌اند.'), kb_back());
            break;
        case 'about':
            show_about_developer($chatId, 'back');
            break;
        case 'link_sub':
            link_sub_start($chatId, $tgId);
            break;
        case 'wheel':
            wheel_menu($chatId, $tgId, $user);
            break;
        default:
            send_main_menu($chatId, $tgId);
    }
}

/* =====================================================================
 *  CALLBACK QUERY HANDLER (inline button presses)
 * ===================================================================== */
function handle_callback(array $cb): void
{
    $chatId = $cb['message']['chat']['id'];
    $tgId   = $cb['from']['id'];
    $data   = $cb['data'];
    $cbId   = $cb['id'];

    // Telegram accepts only ONE answer per callback query, and the first one wins. check_join needs
    // to answer with a message (alert) on failure, so it must not be pre-answered here — otherwise
    // its alert was silently dropped and the button looked like it did nothing.
    if ($data !== 'check_join') tg_answer_callback($cbId);

    if ($data === 'noop') return;

    if ($data === 'check_join') {
        $joinError = null;
        if (user_passed_force_join($tgId, $joinError)) {
            tg_answer_callback($cbId);
            send_main_menu($chatId, $tgId, "✅ عضویت شما تایید شد.");
        } else {
            $msg = "هنوز در همه کانال‌ها عضو نشده‌اید.";
            // Only admins get the technical reason; regular users just see the normal message.
            if ($joinError !== null && is_admin($tgId)) {
                $msg = "⚠️ بررسی عضویت انجام نشد:\n$joinError\n\nمعمولاً یعنی ربات ادمین این کانال نیست.";
            }
            tg_answer_callback($cbId, text_cut($msg, 200), true); // alert text is capped at 200 chars
        }
        return;
    }

    $user = get_or_create_user($cb['from']);

    if ($data === 'back') {
        handle_back($chatId, $tgId, $user);
        return;
    }

    [$action, $rest] = array_pad(explode(':', $data, 2), 2, '');

    // ---- purchase flow ----
    if ($action === 'byname') {
        if ($rest === 'new') {
            buy_ask_new_name($chatId, $tgId);
        } else {
            $stmt = db()->prepare('SELECT name FROM service_names WHERE id = ? AND user_id = ?');
            $stmt->execute([(int)$rest, $user['id']]);
            $row = $stmt->fetch();
            if ($row) buy_pick_category($chatId, $tgId, $row['name']);
        }
        return;
    }
    if ($action === 'bycat') {
        [$catId, $nameB64] = explode(':', $rest);
        $cat = fetch_one('categories', (int)$catId);
        if ($cat) buy_pick_duration($chatId, $tgId, base64_decode($nameB64), (int)$catId, $cat['title']);
        return;
    }
    if ($action === 'bydur') {
        $state = get_state($tgId);
        buy_pick_plan($chatId, $tgId, $state['data'] ?? [], (int)$rest);
        return;
    }
    if ($action === 'byplan') {
        $state = get_state($tgId);
        buy_confirm($chatId, $tgId, $state['data'] ?? [], (int)$rest);
        return;
    }
    if ($action === 'bypay') {
        $state = get_state($tgId);
        buy_pay($chatId, $tgId, $user['id'], $state['data'] ?? [], (int)$rest);
        return;
    }
    if ($action === 'bycard') { // «پرداخت کارت به کارت» → card numbers + copy buttons + receipt
        $state = get_state($tgId);
        buy_show_card($chatId, $tgId, $state['data'] ?? [], (int)$rest);
        return;
    }
    if ($action === 'bydisc') { // 🎟 «افزودن کد تخفیف» → asks for the code
        $state = get_state($tgId);
        disc_ask_code($chatId, $tgId, $state['data'] ?? [], (int)$rest);
        return;
    }
    if ($action === 'bydiscdel') { // 🗑 «حذف کد تخفیف»
        $state = get_state($tgId);
        disc_remove_code($chatId, $tgId, $state['data'] ?? [], (int)$rest);
        return;
    }
    if ($action === 'bycardback') { // «بازگشت» of the card step → payment method choice
        $state = get_state($tgId);
        buy_card_back($chatId, $tgId, $state['data'] ?? [], (int)$rest);
        return;
    }

    // ---- wallet charge ----
    if ($data === 'gocharge') { charge_start($chatId, $tgId); return; }
    if ($data === 'gohome' || $data === 'chargecancel') { clear_state($tgId); send_main_menu($chatId, $tgId); return; }
    if ($action === 'chargeunit') { // 💵 flips the amount between تومان and ریال on the card screen
        $state = get_state($tgId);
        if ($state && $state['step'] === 'charge_receipt') {
            charge_render_card($chatId, (int)$state['data']['amount'], $rest === 'rial' ? 'rial' : 'toman');
        } else {
            wallet_show($chatId, $tgId);
        }
        return;
    }
    if ($data === 'chargecard') {
        $state = get_state($tgId);
        charge_show_card($chatId, $tgId, (int)($state['data']['amount'] ?? 0));
        return;
    }
    if ($action === 'chargeok') {
        if (!can_review($tgId)) return;
        charge_approve($chatId, $tgId, (int)$rest);
        return;
    }
    if ($action === 'chargeno') {
        if (!can_review($tgId)) return;
        charge_reject_ask_reason($chatId, $tgId, (int)$rest);
        return;
    }

    // ---- card-to-card purchase receipts (admin ✅ / ❌) ----
    if ($action === 'orderok') {
        if (!can_review($tgId)) return;
        order_approve($chatId, $tgId, (int)$rest);
        return;
    }
    if ($action === 'orderno') {
        if (!can_review($tgId)) return;
        order_reject_ask_reason($chatId, $tgId, (int)$rest);
        return;
    }

    // ---- support ----
    if ($data === 'support_direct') {
        screen($chatId, bot_text('support_direct_text', 'پیام خود را همینجا بنویسید، به‌زودی کارشناس پشتیبانی پاسخ می‌دهد.'), kb_back('support_menu'));
        return;
    }
    if ($data === 'support_menu') { show_support($chatId); return; }
    if ($data === 'support_guide') { hlp_user_root($chatId); return; }     // 📘 راهنمای استفاده (آموزش‌ها + دانلود)
    if ($data === 'wheel_spin') { wheel_spin($chatId, $tgId, $user); return; }  // 🎡 چرخ‌گردون شانس
    if ($action === 'hgr') { hlp_user_list($chatId, hlp_rootname($rest), 0); return; }
    if ($action === 'hg')  { hlp_user_open($chatId, (int)$rest); return; }

    // ---- account / my configs ----
    if ($data === 'account_view') { show_account($chatId, $user); return; }
    if ($data === 'myconfigs') { my_configs_list($chatId, $user); return; }
    if ($action === 'myconfig') { my_config_detail($chatId, $user, $rest); return; }
    if ($action === 'myconfig_qr') { my_config_send_qr($chatId, $user, $rest); return; }
    if ($action === 'myconfig_code') { my_config_send_code($chatId, $user, $rest); return; }
    if ($action === 'myconfig_sub') { my_config_send_sub($chatId, $user, $rest); return; }
    if ($action === 'myconfig_cfgs') { my_config_send_configs($chatId, $user, $rest); return; }
    if ($action === 'myconfig_chsrv') { my_config_change_server_menu($chatId, $user, $rest); return; }
    if ($action === 'myconfig_chsrv_do') {
        [$svcName, $newServerId] = array_pad(explode(':', $rest, 2), 2, '');
        my_config_change_server_apply($chatId, $user, $svcName, (int)$newServerId);
        return;
    }
    if ($action === 'myconfig_del') { my_config_delete_confirm($chatId, $rest); return; }
    if ($action === 'myconfig_del_go') { my_config_delete_apply($chatId, $user, $rest); return; }

    // ---- admin panel ----
    if (str_starts_with($data, 'a_')) {
        if (!is_admin($tgId)) return;
        route_admin_callback($chatId, $tgId, $data);
        return;
    }
}

/** Re-render the previous screen in a multi-step flow. */
function handle_back(int $chatId, int $tgId, array $user): void
{
    $prev = pop_state($tgId);
    if (!$prev) {
        send_main_menu($chatId, $tgId);
        return;
    }

    $step = $prev['step'];
    $data = $prev['data'];
    unset($data['__history']);

    switch ($step) {
        case 'buy_pick_name':
            buy_start($chatId, $tgId, $user['id'], false);
            break;
        case 'buy_pick_category':
            buy_pick_category($chatId, $tgId, $data['service_name'], false);
            break;
        case 'buy_pick_duration':
            $cat = fetch_one('categories', (int)$data['category_id']);
            buy_pick_duration($chatId, $tgId, $data['service_name'], (int)$data['category_id'], $cat['title'] ?? '', false);
            break;
        case 'buy_pick_plan':
            buy_pick_plan($chatId, $tgId, $data, (int)$data['duration_id'], false);
            break;
        case 'buy_confirm':
            buy_confirm($chatId, $tgId, $data, (int)$data['plan_id'], false);
            break;
        case 'charge_amount':
            charge_start($chatId, $tgId, false);
            break;
        case 'charge_pay_method':
            screen($chatId, "مبلغ انتخابی: " . fmt_money((int)$data['amount']) . "\n\nروش پرداخت را انتخاب کنید:",
                kb_inline([[['text' => '💳 کارت به کارت', 'callback_data' => 'chargecard']], [['text' => '◂ بازگشت', 'callback_data' => 'back', 'style' => 'danger']]]));
            break;
        default:
            send_main_menu($chatId, $tgId);
    }
}

function fetch_one(string $table, int $id): ?array
{
    $stmt = db()->prepare("SELECT * FROM `$table` WHERE id = ?");
    $stmt->execute([$id]);
    $row = $stmt->fetch();
    return $row ?: null;
}

/* =====================================================================
 *  ADMIN PANEL CALLBACK ROUTER
 * ===================================================================== */
function route_admin_callback(int $chatId, int $tgId, string $data): void
{
    [$action, $rest] = array_pad(explode(':', $data, 2), 2, '');

    switch (true) {
        case $data === 'a_home':
            clear_state($tgId); // «◂ لغو» of a text prompt leads here — the pending form must really be dropped
            admin_main_menu($chatId, $tgId); break;
        case $action === 'a_cancel': // «◂ لغو» of a text prompt: drop the pending form, then open the parent screen
            clear_state($tgId);
            route_admin_callback($chatId, $tgId, ($rest !== '' && !str_starts_with($rest, 'a_cancel')) ? $rest : 'a_home');
            break;
        case $data === 'a_user_area':
            clear_state($tgId); // a half-finished admin form must not swallow the first tap in the user area
            send_main_menu($chatId, $tgId); break;
        case $data === 'a_stats':
            admin_stats($chatId); break;

        case $data === 'a_catalog':
            admin_catalog_categories($chatId); break;
        case $action === 'a_cat':
            admin_catalog_category_detail($chatId, (int)$rest); break;
        case $data === 'a_cat_add':
            admin_ask_text($chatId, $tgId, 'a_cat_add_title', [], "نام سرویس جدید را وارد کنید (مثل «مولتی لوکیشن»):"); break;
        case $action === 'a_cat_toggle':
            db()->prepare('UPDATE categories SET enabled = 1-enabled WHERE id=?')->execute([(int)$rest]);
            $c = fetch_one('categories', (int)$rest);
            if ($c) log_admin_action($tgId, $c['enabled'] ? 'فعال‌سازی سرویس' : 'غیرفعال‌سازی سرویس', "«{$c['title']}»");
            admin_catalog_category_detail($chatId, (int)$rest); break;
        case $action === 'a_cat_del':
            $c = fetch_one('categories', (int)$rest);
            db()->prepare('DELETE FROM categories WHERE id=?')->execute([(int)$rest]);
            log_admin_action($tgId, 'حذف سرویس', $c ? "«{$c['title']}»" : "سرویس #$rest");
            admin_catalog_categories($chatId); break;
        case $action === 'a_cat_srv':
            admin_cat_server_picker($chatId, (int)$rest); break;
        case $action === 'a_cat_srv_set':
            [$catId, $serverId] = array_pad(explode(':', $rest, 2), 2, '0');
            admin_cat_server_set($chatId, $tgId, (int)$catId, (int)$serverId); break;

        case $action === 'a_dur':
            admin_catalog_duration_detail($chatId, (int)$rest); break;
        case $action === 'a_dur_add':
            admin_ask_text($chatId, $tgId, 'a_dur_add_title', ['category_id' => (int)$rest], "عنوان مدت جدید را وارد کنید (مثل «1 ماهه»):"); break;
        case $action === 'a_dur_toggle':
            db()->prepare('UPDATE durations SET enabled = 1-enabled WHERE id=?')->execute([(int)$rest]);
            $d = fetch_one('durations', (int)$rest);
            if ($d) log_admin_action($tgId, $d['enabled'] ? 'فعال‌سازی مدت' : 'غیرفعال‌سازی مدت', log_duration_label((int)$rest));
            admin_catalog_duration_detail($chatId, (int)$rest); break;
        case $action === 'a_dur_del':
            $d = fetch_one('durations', (int)$rest);
            $durLabel = log_duration_label((int)$rest);
            db()->prepare('DELETE FROM durations WHERE id=?')->execute([(int)$rest]);
            log_admin_action($tgId, 'حذف مدت', $durLabel);
            if ($d) admin_catalog_category_detail($chatId, (int)$d['category_id']);
            break;

        case $action === 'a_plan':
            admin_plan_detail($chatId, (int)$rest); break;
        case $action === 'a_plan_add':
            admin_ask_text($chatId, $tgId, 'a_plan_add_gb', ['duration_id' => (int)$rest], "حجم پلن را به گیگابایت وارد کنید (برای نامحدود عدد 0 بفرستید):"); break;
        case $action === 'a_plan_editprice':
            admin_ask_text($chatId, $tgId, 'a_plan_editprice', ['plan_id' => (int)$rest], "قیمت جدید را به تومان وارد کنید:"); break;
        case $action === 'a_plan_editgb':
            admin_ask_text($chatId, $tgId, 'a_plan_editgb', ['plan_id' => (int)$rest], "حجم جدید را به گیگابایت وارد کنید (0 = نامحدود):"); break;
        case $action === 'a_plan_toggle':
            db()->prepare('UPDATE plans SET enabled = 1-enabled WHERE id=?')->execute([(int)$rest]);
            $p = fetch_one('plans', (int)$rest);
            if ($p) log_admin_action($tgId, $p['enabled'] ? 'فعال‌سازی پلن' : 'غیرفعال‌سازی پلن', log_plan_label((int)$rest));
            admin_plan_detail($chatId, (int)$rest); break;
        case $action === 'a_plan_del':
        case $action === 'a_plan_del_direct':
            $p = fetch_one('plans', (int)$rest);
            $planLabel = log_plan_label((int)$rest);
            db()->prepare('DELETE FROM plans WHERE id=?')->execute([(int)$rest]);
            log_admin_action($tgId, 'حذف پلن', $planLabel);
            if ($p) admin_catalog_duration_detail($chatId, (int)$p['duration_id']);
            break;

        case $data === 'a_buttons':
            admin_texts_buttons_hub($chatId); break;
        case $data === 'a_buttons_list':
            admin_buttons_list($chatId); break;
        case $data === 'a_texts_list':
            admin_texts_list($chatId); break;
        case $action === 'a_texts_cat':
            $txtCatNames = array_keys(editable_texts_categories());
            admin_texts_category_list($chatId, $txtCatNames[(int)$rest] ?? ''); break;
        case $action === 'a_text_edit':
            admin_text_edit_start($chatId, $tgId, $rest); break;
        case $action === 'a_btn_edit':
            admin_button_edit_start($chatId, $tgId, (int)$rest); break;
        case $action === 'a_btn_toggle':
            db()->prepare('UPDATE menu_buttons SET enabled = 1-enabled WHERE id=?')->execute([(int)$rest]);
            $b = fetch_one('menu_buttons', (int)$rest);
            if ($b) log_admin_action($tgId, 'تغییر دکمه منو', "«{$b['label']}» → " . ($b['enabled'] ? 'روشن' : 'خاموش'));
            admin_buttons_list($chatId); break;
        case $data === 'a_reseller_toggle':
            $cur = setting('reseller_enabled', '0');
            set_setting('reseller_enabled', $cur === '1' ? '0' : '1');
            log_admin_action($tgId, 'تغییر وضعیت نمایندگی', $cur === '1' ? 'خاموش شد' : 'روشن شد');
            admin_buttons_list($chatId); break;

        case $data === 'a_channels':
            admin_channels_list($chatId); break;
        case $data === 'a_channel_add':
            admin_ask_text($chatId, $tgId, 'a_channel_add_id', [], "آیدی کانال (مثل @channel) یا chat_id عددی (-100...) را ارسال کنید:"); break;
        case $action === 'a_channel':
            admin_channel_detail($chatId, (int)$rest); break;
        case $action === 'a_channel_toggle':
            db()->prepare('UPDATE force_join_channels SET enabled=1-enabled WHERE id=?')->execute([(int)$rest]);
            $ch = fetch_one('force_join_channels', (int)$rest);
            if ($ch) log_admin_action($tgId, 'تغییر کانال اسپانسر', "{$ch['chat_id']} → " . ($ch['enabled'] ? 'فعال' : 'غیرفعال'));
            admin_channel_detail($chatId, (int)$rest); break;
        case $action === 'a_channel_del':
            $ch = fetch_one('force_join_channels', (int)$rest);
            db()->prepare('DELETE FROM force_join_channels WHERE id=?')->execute([(int)$rest]);
            log_admin_action($tgId, 'حذف کانال اسپانسر', $ch ? (string)$ch['chat_id'] : "کانال #$rest");
            admin_channels_list($chatId); break;
        case $data === 'a_force_toggle':
            $cur = setting('force_join_enabled', '0');
            set_setting('force_join_enabled', $cur === '1' ? '0' : '1');
            log_admin_action($tgId, 'تغییر عضویت اجباری', $cur === '1' ? 'خاموش شد' : 'روشن شد');
            admin_channels_list($chatId); break;

        case $data === 'a_admins':
            if (!can_manage_admins($tgId)) return;
            admin_admins_list($chatId); break;
        case $data === 'a_admin_add':
            if (!can_manage_admins($tgId)) return;
            admin_ask_text($chatId, $tgId, 'a_addadmin_id', [], "آیدی عددی ادمین جدید را ارسال کنید (از @userinfobot بگیرید):"); break;
        case $action === 'a_admin':
            if (!can_manage_admins($tgId)) return;
            admin_admin_detail($chatId, (int)$rest); break;
        case $action === 'a_admin_role':
            if (!can_manage_admins($tgId)) return;
            [$id, $role] = explode(':', $rest);
            db()->prepare('UPDATE admins SET role=? WHERE id=?')->execute([$role, (int)$id]);
            $adm = fetch_one('admins', (int)$id);
            if ($adm) log_admin_action($tgId, 'تغییر سطح ادمین', "آیدی {$adm['tg_id']} → $role");
            admin_admin_detail($chatId, (int)$id); break;
        case $action === 'a_admin_del':
            if (!can_manage_admins($tgId)) return;
            $adm = fetch_one('admins', (int)$rest);
            db()->prepare('DELETE FROM admins WHERE id=? AND role != "owner"')->execute([(int)$rest]);
            if ($adm && $adm['role'] !== 'owner') log_admin_action($tgId, 'حذف ادمین', "آیدی {$adm['tg_id']} (سطح: {$adm['role']})");
            admin_admins_list($chatId); break;

        case $data === 'a_settings':
            admin_settings_menu($chatId); break;
        case $data === 'a_hlp' || str_starts_with($data, 'a_hlp_'):
            hlp_admin_route($chatId, $tgId, $data); break;
        case $data === 'a_resettest':
            admin_reset_test_start($chatId, $tgId); break;

        case $data === 'a_cards':
            admin_cards_menu($chatId); break;
        case $data === 'a_card_add':
            admin_card_add_start($chatId, $tgId); break;
        case $action === 'a_card_toggle':
            db()->prepare('UPDATE payment_cards SET enabled = 1-enabled WHERE id=?')->execute([(int)$rest]);
            $card = fetch_one('payment_cards', (int)$rest);
            if ($card) log_admin_action($tgId, 'تغییر وضعیت کارت', "{$card['card_number']} → " . ($card['enabled'] ? 'فعال' : 'غیرفعال'));
            admin_cards_menu($chatId); break;
        case $action === 'a_card_del':
            $card = fetch_one('payment_cards', (int)$rest);
            db()->prepare('DELETE FROM payment_cards WHERE id=?')->execute([(int)$rest]);
            log_admin_action($tgId, 'حذف کارت', $card ? "{$card['card_number']} — {$card['card_holder']}" : "کارت #$rest");
            admin_cards_menu($chatId); break;

        case $data === 'a_qrbg':
            admin_qrbg_start($chatId, $tgId); break;
        case $data === 'a_qrbg_clear':
            @unlink(qr_bg_path());
            clear_state($tgId);
            screen($chatId, "✅ پس‌زمینه حذف شد.");
            admin_settings_menu($chatId);
            break;

        case $data === 'a_freetest':
            admin_freetest_menu($chatId); break;

        case $action === 'a_set':
            $labels = [
                'min_charge_amount' => 'حداقل مبلغ شارژ را به تومان وارد کنید:',
                'free_test_mb' => 'حجم تست رایگان را به مگابایت وارد کنید (مثلاً 1024 برای ۱ گیگ):',
                'free_test_hours' => 'مدت تست رایگان را به ساعت وارد کنید:',
                'free_test_max_count' => 'هر کاربر چند بار حق استفاده از تست رایگان را داشته باشد؟ (عدد صحیح):',
                'welcome_text' => 'متن خوش‌آمدگویی جدید را ارسال کنید:',
                'monthly_cost' => 'هزینه ثابت ماهانه را به تومان وارد کنید (برای محاسبه سود و زیان):',
            ];
            if (isset($labels[$rest])) {
                admin_ask_text($chatId, $tgId, 'a_setting_text', ['key' => $rest], $labels[$rest]);
            }
            break;

        case $data === 'a_broadcast':
            admin_broadcast_start($chatId, $tgId); break;
        case $data === 'a_bc_send':
            admin_broadcast_send($chatId, $tgId); break;
        case $data === 'a_bc_edit':
            admin_broadcast_edit($chatId, $tgId); break;
        case $data === 'a_bc_cancel':
            admin_broadcast_cancel($chatId, $tgId); break;

        case $data === 'a_payments':
            clear_state($tgId); // «انصراف» of a reject-reason prompt lands here — drop that half-finished step
            admin_payments_list($chatId); break;
        case $action === 'a_pay':
            admin_payment_detail($chatId, (int)$rest); break;
        case $action === 'a_opay':
            admin_order_detail($chatId, (int)$rest); break;

        // ---- sections defined but not yet built (later batches) ----
        case $data === 'a_users':
            admin_users_menu($chatId); break;
        case $action === 'a_ulist':
            [$uf, $up] = array_pad(explode(':', $rest, 2), 2, '1');
            admin_users_list($chatId, $uf, (int)$up); break;
        case $action === 'a_uview':
            [$uid, $uf, $up] = array_pad(explode(':', $rest, 3), 3, '');
            admin_user_profile($chatId, (int)$uid, $uf !== '' ? $uf : 'all', max(1, (int)$up)); break;
        case $data === 'a_tariffs':
            admin_tariffs_menu($chatId); break;
        case $data === 'a_tariffs_edit':
            admin_ask_text($chatId, $tgId, 'a_tariffs_text', [], "متن جدید تعرفه‌ها را ارسال کنید:"); break;
        case $data === 'a_servers':
            admin_servers_menu($chatId); break;
        case $action === 'a_srv_type':
            admin_servers_type_list($chatId, $rest); break;
        case $action === 'a_srv_add':
            admin_server_add_start($chatId, $tgId, $rest); break;
        case $action === 'a_srv_auth':
            $st = get_state($tgId);
            $pending = $st['data'] ?? [];
            if ($rest === 'token') {
                admin_ask_text($chatId, $tgId, 'a_srv_add_token', $pending, "توکن/کد API پنل را وارد کنید:");
            } else {
                admin_ask_text($chatId, $tgId, 'a_srv_add_user', $pending, "یوزرنیم ادمین پنل را وارد کنید:");
            }
            break;
        case $action === 'a_srv_del':
            admin_server_delete($chatId, $tgId, (int)$rest); break;
        case $action === 'a_srv_editsub':
            admin_server_edit_subdomain_start($chatId, $tgId, (int)$rest); break;
        case $data === 'a_resellers':
            admin_coming_soon($chatId, 'نمایندگان'); break;
        case $data === 'a_bulk_time':
            admin_bulk_time_start($chatId, $tgId); break;
        case $data === 'a_bulk_gb':
            admin_bulk_gb_start($chatId, $tgId); break;
        case $action === 'a_bulk_go':
            [$kind, $amount] = array_pad(explode(':', $rest, 2), 2, '');
            admin_bulk_run($chatId, $tgId, $kind, (int)$amount); break;
        case $data === 'a_financial':
            admin_financial_menu($chatId); break;
        case $data === 'a_fin_dash':
            admin_financial_dashboard($chatId); break;
        case $data === 'a_fin_forecast':
            admin_financial_forecast($chatId); break;
        case $action === 'a_fin_renew':
            admin_financial_renewals($chatId, max(1, (int)$rest)); break;
        case $action === 'a_fin_remind':
            admin_financial_remind_all($chatId, $tgId, max(1, (int)$rest)); break;
        case $data === 'a_fin_pnl':
            admin_financial_pnl($chatId); break;
        case $action === 'a_activity':
            [$pg, $filterAdmin] = array_pad(explode(':', $rest, 2), 2, '0');
            admin_activity_show($chatId, $tgId, max(1, (int)$pg), (int)$filterAdmin); break;
        case $data === 'a_actfilter':
            admin_activity_filter($chatId, $tgId); break;
        case $data === 'a_backup':
            admin_backup_menu($chatId); break;
        case $data === 'a_backup_now':
            admin_backup_now($chatId, $tgId); break;
        case $data === 'a_ads' || str_starts_with($data, 'a_ads_'):
            ads_admin_route($chatId, $tgId, $data); break;
        case $data === 'a_disc' || str_starts_with($data, 'a_disc_'):
            admin_disc_route($chatId, $tgId, $data); break;
        case $data === 'a_wheel' || str_starts_with($data, 'a_wheel_'):
            admin_wheel_route($chatId, $tgId, $data); break;
        case $data === 'a_backup_auto_toggle':
            admin_backup_auto_toggle($chatId, $tgId); break;
        case $data === 'a_restore':
            admin_restore_prompt($chatId, $tgId); break;
        case $data === 'a_restore_go':
            admin_restore_run($chatId, $tgId); break;
        case $data === 'a_restore_cancel':
            admin_restore_cancel($chatId, $tgId); break;
        case $data === 'a_about':
            show_about_developer($chatId); break;
        case $data === 'a_referrals':
            admin_referrals_menu($chatId); break;
        case $data === 'a_ref_tiers':
            $current = implode(',', referral_tiers());
            admin_ask_text($chatId, $tgId, 'a_setting_text', ['key' => 'referral_tier_percents'],
                "درصدهای هر پله رو با کاما جدا وارد کنید (خرید اول، دوم، سوم و…).\nمقدار فعلی: <code>$current</code>\nمثلاً: <code>15,10,5</code>\n\n(از آخرین عدد به بعد، همون درصد برای همه‌ی خریدهای بعدی هم اعمال می‌شه)"); break;
        case $data === 'a_ref_bonus':
            admin_ask_text($chatId, $tgId, 'a_setting_text', ['key' => 'first_purchase_bonus_gb'],
                "حجم هدیه‌ی خرید اول (فقط برای کاربرانی که از لینک دعوت اومدن) رو به گیگابایت وارد کنید (برای غیرفعال‌کردنش، 0 بفرستید):"); break;
        case $data === 'a_ref_texts':
            admin_texts_category_list($chatId, 'زیرمجموعه‌گیری'); break;
    }
}
