<?php

/* =========================
 * Telegram helpers
 * ========================= */

function phoneLink($phone, $label = null)
{
    $phone = trim((string)$phone);
    if ($phone === '') return '';

    // Keep the number exactly as entered for display/copy, but normalize
    // the href so Telegram opens the phone dialer instead of a web page.
    $dial = preg_replace('/[^0-9+]/', '', $phone);
    if ($dial === '') return escapeHtml($phone);
    if (strpos($dial, '00') === 0) $dial = '+' . substr($dial, 2);
    elseif ($dial[0] === '0') $dial = '+98' . substr($dial, 1);
    elseif ($dial[0] !== '+') $dial = '+' . $dial;

    $visible = $label !== null ? (string)$label : $phone;
    // Telegram does not expose a clipboard API to normal bot messages.
    // Inline <code> text is monospace and gives Telegram's client a native
    // copy affordance without using a Web App or external URL.
    return '<code>' . escapeHtml($visible) . '</code>';
}

function phoneCallKeyboard($phone, $gameId = 0, $backCallback = "rooms:reserve")
{
    // شماره تماس فقط به صورت monospace در متن نمایش داده می‌شود.
    // هیچ دکمه‌ای برای شماره ایجاد نمی‌کنیم؛ فقط دکمه برگشت باقی می‌ماند.
    return inlineKeyboard([
        [["text" => "↩️ برگشت", "callback_data" => $backCallback]]
    ]);
}

function telegramRequest($method, $data = [], $connectTimeout = 5, $timeout = 12)
{
    $ch = curl_init(API . $method);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $data,
        CURLOPT_CONNECTTIMEOUT => (int)$connectTimeout,
        CURLOPT_TIMEOUT => (int)$timeout,
    ]);
    $response = curl_exec($ch);
    curl_close($ch);
    return json_decode($response ?: '', true);
}

function sendMessage($chat_id, $text, $keyboard = null, $extra = [])
{
    $data = array_merge([
        "chat_id" => $chat_id,
        "text" => $text,
        "parse_mode" => "HTML",
        "disable_web_page_preview" => true,
    ], $extra);

    if ($keyboard !== null) {
        $data["reply_markup"] = json_encode($keyboard, JSON_UNESCAPED_UNICODE);
    }

    return telegramRequest("sendMessage", $data);
}

function editMessage($chat_id, $message_id, $text, $keyboard = null)
{
    $data = [
        "chat_id" => $chat_id,
        "message_id" => $message_id,
        "text" => $text,
        "parse_mode" => "HTML",
        "disable_web_page_preview" => true,
    ];

    if ($keyboard !== null) {
        $data["reply_markup"] = json_encode($keyboard, JSON_UNESCAPED_UNICODE);
    }

    return telegramRequest("editMessageText", $data);
}

function editMessageReplyMarkup($chat_id, $message_id, $keyboard = null)
{
    $data = [
        "chat_id" => $chat_id,
        "message_id" => $message_id,
        "reply_markup" => json_encode($keyboard ?? ["inline_keyboard" => []], JSON_UNESCAPED_UNICODE),
    ];
    return telegramRequest("editMessageReplyMarkup", $data);
}

function answerCallback($callback_id, $text = '', $show_alert = false)
{
    return telegramRequest("answerCallbackQuery", [
        "callback_query_id" => $callback_id,
        "text" => $text,
        "show_alert" => $show_alert ? 'true' : 'false',
    ]);
}

function telegramCaption($text)
{
    $text = (string)$text;
    // Telegram photo captions are limited to 1024 characters. If the
    // HTML caption is too long, fall back to plain text before truncating
    // so we never cut an HTML tag in half.
    if (function_exists('mb_strlen') && mb_strlen($text, 'UTF-8') <= 1024) return $text;
    if (!function_exists('mb_strlen') && strlen($text) <= 1024) return $text;
    $plain = trim(strip_tags($text));
    if (function_exists('mb_substr')) return mb_substr($plain, 0, 1024, 'UTF-8');
    return substr($plain, 0, 1024);
}

function sendPhoto($chat_id, $photo, $caption = '', $keyboard = null)
{
    $data = [
        "chat_id" => $chat_id,
        "photo" => $photo,
        "caption" => telegramCaption($caption),
        "parse_mode" => "HTML",
    ];

    if ($keyboard !== null) {
        $data["reply_markup"] = json_encode($keyboard, JSON_UNESCAPED_UNICODE);
    }

    $result = telegramRequest("sendPhoto", $data);
    if (is_array($result) && !empty($result['ok'])) return $result;

    // اگر Telegram به هر دلیل کپشن/پوستر را رد کرد، توضیحات را از دست نده.
    // پیام متنی پشتیبان ارسال می‌شود تا کاربر همیشه توضیحات بازی را ببیند.
    error_log("BOT sendPhoto failed: " . json_encode($result, JSON_UNESCAPED_UNICODE));
    $fallback = $caption !== '' ? $caption : "🎮 اطلاعات بازی";
    return sendMessage($chat_id, $fallback, $keyboard);
}

function sendAudio($chat_id, $file)
{
    $data = [
        "chat_id" => $chat_id,
        "audio" => new CURLFile($file),
    ];
    return telegramRequest("sendAudio", $data);
}

function sendFinalFile($chatId, $fileData, $caption = '', $keyboard = null)
{
    if (!is_array($fileData) || empty($fileData['type']) || empty($fileData['file_id'])) return false;
    $type = $fileData['type'];
    $fileId = $fileData['file_id'];
    $methodMap = [
        'photo' => 'sendPhoto', 'video' => 'sendVideo', 'document' => 'sendDocument',
        'audio' => 'sendAudio', 'voice' => 'sendVoice', 'animation' => 'sendAnimation',
        'sticker' => 'sendSticker'
    ];
    if (!isset($methodMap[$type])) return false;
    $data = ['chat_id' => $chatId, $type => $fileId];
    if ($caption !== '' && $type !== 'sticker') {
        $data['caption'] = telegramCaption($caption);
        $data['parse_mode'] = 'HTML';
    }
    if ($keyboard !== null) $data['reply_markup'] = json_encode($keyboard, JSON_UNESCAPED_UNICODE);
    $result = telegramRequest($methodMap[$type], $data);
    if (!empty($result['ok'])) return true;
    error_log('BOT final file send failed: ' . json_encode($result, JSON_UNESCAPED_UNICODE));
    return false;
}

function sendFinalFiles($chatId, $files)
{
    if (!is_array($files)) return 0;
    $sent = 0;
    foreach ($files as $file) {
        if (sendFinalFile($chatId, $file)) $sent++;
    }
    return $sent;
}

function cleanTelegramText($text)
{
    $text = (string)$text;
    // Remove invisible direction/zero-width characters that can make
    // keyboard button comparisons fail after copy/paste.
    $text = str_replace(["\xE2\x80\x8B", "\xE2\x80\x8C", "\xE2\x80\x8D", "\xEF\xBB\xBF"], '', $text);
    return trim($text);
}

function mainMenu($showAdminButton = false)
{
    $keyboard = [
        [
            ["text" => "🎭 اتاق فرار"],
            ["text" => "☕ کافه بازی"]
        ],
        [["text" => "📞 پشتیبانی"]]
    ];

    // Only the real admin sees the admin entry point. All admin tools
    // remain hidden behind this single button.
    if ($showAdminButton) {
        $keyboard[] = [["text" => "👑 ادمین"]];
    }

    return [
        "keyboard" => $keyboard,
        "resize_keyboard" => true,
        "is_persistent" => true
    ];
}

function adminMenu($chatId = null)
{
    if ($chatId !== null && !isOwner($chatId)) {
        $games = getAdminGameIds($chatId);
        $rows = [];
        if (count($games) === 1) {
            $rows[] = [["text" => "🎮 مدیریت بازی من", "callback_data" => "admin:game:" . $games[0]]];
            $rows[] = [["text" => "❓ مدیریت سوالات بازی من", "callback_data" => "admin:qgame:" . $games[0]]];
            $rows[] = [["text" => "📋 نمایش اطلاعات ارسال شده", "callback_data" => "admin:submitted_info:game:" . $games[0]]];
        } else {
            $rows[] = [["text" => "🎮 بازی‌های من", "callback_data" => "admin:my_game"]];
        }
        $rows[] = [["text" => "🏠 منوی کاربری", "callback_data" => "admin:user_menu"]];
        return inlineKeyboard($rows);
    }
    $rows = [
        [["text" => "🎮 مدیریت بازی‌ها", "callback_data" => "admin:games"]],
        [["text" => "❓ مدیریت سوالات", "callback_data" => "admin:question_games"], ["text" => "🎟 مدیریت کد تخفیف", "callback_data" => "admin:discounts"]],
        [["text" => "📋 نمایش اطلاعات ارسال شده", "callback_data" => "admin:submitted_info"], ["text" => "📊 آمار ربات", "callback_data" => "admin:stats"]],
        [["text" => "📢 پیام همگانی", "callback_data" => "admin:broadcast"], ["text" => "👤 پیام به کاربر", "callback_data" => "admin:single_user"]],
        [["text" => "👥 مدیریت ادمین‌ها", "callback_data" => "admin:manage_admins"]],
    ];
    if (isFounder($chatId)) {
        $rows[] = [["text" => "👑 مدیریت Ownerها", "callback_data" => "admin:manage_owners"]];
        $rows[] = [["text" => "🛠 کنترل سیستم", "callback_data" => "founder:system"]];
    }
    $rows[] = [["text" => "🏠 منوی کاربری", "callback_data" => "admin:user_menu"]];
    return inlineKeyboard($rows);
}

function cancelButton()
{
    return [
        "keyboard" => [[["text" => "❌ منصرف شدن"]]],
        "resize_keyboard" => true
    ];
}

function yesNoKeyboard()
{
    return [
        "keyboard" => [
            [["text" => "بله"], ["text" => "خیر"]],
            [["text" => "❌ منصرف شدن"]]
        ],
        "resize_keyboard" => true
    ];
}

function genreKeyboard()
{
    return [
        "keyboard" => [
            [["text" => "معمایی هیجانی"], ["text" => "معمایی ترسناک"]],
            [["text" => "معمایی کلاسیک"], ["text" => "معمایی کمدی"]],
            [["text" => "معمایی فانتزی"]],
            [["text" => "❌ منصرف شدن"]]
        ],
        "resize_keyboard" => true
    ];
}

function inlineKeyboard($rows)
{
    return ["inline_keyboard" => $rows];
}

/* =========================
 * JSON database
 * ========================= */

function dbPath($name)
{
    return __DIR__ . "/data/" . $name . ".json";
}

function dbRead($name, $default = [])
{
    $file = dbPath($name);
    if (!file_exists($file)) return $default;
    $data = json_decode(file_get_contents($file), true);
    return is_array($data) ? $data : $default;
}

function dbWrite($name, $data)
{
    $file = dbPath($name);
    $tmp = $file . ".tmp";
    file_put_contents($tmp, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX);
    @rename($tmp, $file);
}

function dbNextId($items)
{
    $max = 0;
    foreach ($items as $item) {
        $max = max($max, (int)($item["id"] ?? 0));
    }
    return $max + 1;
}

function isMaintenanceMode()
{
    $file = defined('MAINTENANCE_FILE') ? MAINTENANCE_FILE : (__DIR__ . '/data/maintenance.json');
    if (!file_exists($file)) return false;
    $data = json_decode((string)@file_get_contents($file), true);
    return is_array($data) && !empty($data['enabled']);
}

function setMaintenanceMode($enabled, $by = null)
{
    $file = defined('MAINTENANCE_FILE') ? MAINTENANCE_FILE : (__DIR__ . '/data/maintenance.json');
    $data = [
        'enabled' => (bool)$enabled,
        'updated_at' => date('Y-m-d H:i:s'),
        'updated_by' => $by !== null ? (int)$by : null,
    ];
    return @file_put_contents($file, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX) !== false;
}

function webhookUrl()
{
    if (defined('WEBHOOK_URL') && trim((string)WEBHOOK_URL) !== '') {
        return rtrim(trim((string)WEBHOOK_URL), '/');
    }

    $https = (!empty($_SERVER['HTTPS']) && strtolower((string)$_SERVER['HTTPS']) !== 'off')
        || ((int)($_SERVER['SERVER_PORT'] ?? 0) === 443)
        || strtolower((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https';
    $scheme = $https ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'] ?? '';
    $path = $_SERVER['SCRIPT_NAME'] ?? '/webhook.php';
    if ($host !== '') return $scheme . '://' . $host . $path;
    return '';
}

function webhookSystemStatus()
{
    $me = telegramRequest('getMe', [], 5, 10);
    $info = telegramRequest('getWebhookInfo', [], 5, 10);

    $ok = !empty($me['ok']) && !empty($info['ok']);
    $url = (string)($info['result']['url'] ?? '');
    $pending = (int)($info['result']['pending_update_count'] ?? 0);
    $lastError = trim((string)($info['result']['last_error_message'] ?? ''));
    $botName = (string)($me['result']['username'] ?? '');

    return [
        'ok' => $ok,
        'bot' => $botName !== '' ? '@' . $botName : 'نامشخص',
        'webhook_url' => $url,
        'expected_url' => webhookUrl(),
        'pending' => $pending,
        'last_error' => $lastError,
        'maintenance' => isMaintenanceMode(),
    ];
}

function refreshWebhookRegistration()
{
    $url = webhookUrl();
    if ($url === '' || strpos($url, 'https://') !== 0) {
        return ['ok' => false, 'message' => 'آدرس HTTPS وبهوک مشخص نیست. WEBHOOK_URL را در config.php تنظیم کن.'];
    }

    $data = ['url' => $url, 'allowed_updates' => json_encode([
        'message', 'callback_query', 'inline_query', 'chat_member'
    ], JSON_UNESCAPED_UNICODE)];
    if (defined('WEBHOOK_SECRET') && trim((string)WEBHOOK_SECRET) !== '' && WEBHOOK_SECRET !== 'CHANGE_THIS_TO_A_RANDOM_SECRET') {
        $data['secret_token'] = (string)WEBHOOK_SECRET;
    }
    $result = telegramRequest('setWebhook', $data, 5, 15);
    return [
        'ok' => !empty($result['ok']),
        'message' => (string)($result['description'] ?? ($result['ok'] ? 'Webhook registered.' : 'Telegram rejected the webhook.')),
    ];
}

function founderSystemMenu($chatId)
{
    if (!isFounder($chatId)) return adminMenu($chatId);
    $maintenance = isMaintenanceMode();
    return inlineKeyboard([
        [["text" => "🔄 راه‌اندازی مجدد Webhook", "callback_data" => "founder:restart"]],
        [["text" => ($maintenance ? "🟢 روشن کردن ربات" : "🔴 حالت خاموشی"), "callback_data" => $maintenance ? "founder:maintenance_off" : "founder:maintenance_on"]],
        [["text" => "📡 وضعیت ربات و Webhook", "callback_data" => "founder:status"]],
        [["text" => "↩️ پنل ادمین", "callback_data" => "admin:back"]],
    ]);
}

function getFounderId()
{
    global $founder_id;
    return (int)($founder_id ?? 1822165779);
}

function isFounder($chatId)
{
    return (int)$chatId === getFounderId();
}

function getConfiguredOwnerIds()
{
    global $owner_ids, $owner_id;
    $ids = isset($owner_ids) && is_array($owner_ids) ? $owner_ids : [$owner_id];
    $ids = array_values(array_unique(array_filter(array_map('intval', $ids), fn($id) => $id > 0)));
    $stored = dbRead('owners', []);
    if (is_array($stored)) {
        foreach ($stored as $id) {
            $id = (int)$id;
            if ($id > 0) $ids[] = $id;
        }
    }
    $founder = getFounderId();
    if ($founder > 0) $ids[] = $founder;
    return array_values(array_unique(array_filter(array_map('intval', $ids), fn($id) => $id > 0)));
}

function isConfiguredOwner($chatId)
{
    return in_array((int)$chatId, getConfiguredOwnerIds(), true);
}

function addOwner($chatId)
{
    $id = (int)$chatId;
    if ($id <= 0) return false;
    $owners = dbRead('owners', []);
    if (!is_array($owners)) $owners = [];
    $owners[] = $id;
    dbWrite('owners', array_values(array_unique(array_map('intval', $owners))));
    return true;
}

function removeOwner($chatId)
{
    $id = (int)$chatId;
    if ($id <= 0 || isFounder($id)) return false;
    $owners = dbRead('owners', []);
    if (!is_array($owners)) $owners = [];
    $owners = array_values(array_filter(array_map('intval', $owners), fn($v) => $v !== $id));
    dbWrite('owners', $owners);
    return true;
}

function ensureAdminStore()
{
    $file = dbPath("admins");
    if (!file_exists($file)) {
        global $unfinished_admins, $owner_ids, $owner_id;
        $records = [];
        $ids = array_values(array_unique(array_map('intval', $unfinished_admins)));
        $ids = array_values(array_unique(array_merge($ids, getConfiguredOwnerIds())));
        foreach (array_values(array_unique($ids)) as $id) {
            $records[] = [
                "id" => (int)$id,
                "role" => (isConfiguredOwner($id) ? "owner" : "admin"),
                "game_ids" => [],
            ];
        }
        dbWrite("admins", $records);
        return;
    }

    // Migrate the old format [123,456] to scoped admin records.
    $raw = dbRead("admins", []);
    if ($raw && isset($raw[0]) && !is_array($raw[0])) {
        global $owner_id;
        $records = [];
        foreach (array_values(array_unique(array_map('intval', $raw))) as $id) {
            $records[] = [
                "id" => (int)$id,
                "role" => (isConfiguredOwner($id) ? "owner" : "admin"),
                "game_ids" => [],
            ];
        }
        foreach (getConfiguredOwnerIds() as $oid) {
            if (!in_array($oid, array_column($records, 'id'), true)) {
                $records[] = ["id" => $oid, "role" => "owner", "game_ids" => []];
            }
        }
        dbWrite("admins", $records);
    }
}

function getAdminRecords()
{
    global $owner_id;
    ensureAdminStore();
    $records = dbRead("admins", []);
    $out = [];
    foreach ($records as $r) {
        if (!is_array($r)) continue;
        $id = (int)($r['id'] ?? 0);
        if ($id <= 0) continue;
        $games = array_values(array_unique(array_map('intval', $r['game_ids'] ?? [])));
        $out[] = [
            'id' => $id,
            'role' => (isConfiguredOwner($id) ? 'owner' : (($r['role'] ?? 'admin') === 'owner' ? 'owner' : 'admin')),
            'game_ids' => $games,
        ];
    }
    $existingIds = array_map(fn($r) => (int)$r['id'], $out);
    foreach (getConfiguredOwnerIds() as $oid) {
        if (!in_array($oid, $existingIds, true)) $out[] = ['id' => $oid, 'role' => 'owner', 'game_ids' => []];
    }
    return $out;
}

function saveAdminRecords($records)
{
    global $owner_id;
    $out = [];
    foreach ($records as $r) {
        $id = (int)($r['id'] ?? 0);
        if ($id <= 0) continue;
        $out[$id] = [
            'id' => $id,
            'role' => (isConfiguredOwner($id) ? 'owner' : 'admin'),
            'game_ids' => array_values(array_unique(array_map('intval', $r['game_ids'] ?? []))),
        ];
    }
    foreach (getConfiguredOwnerIds() as $oid) $out[$oid] = ['id' => $oid, 'role' => 'owner', 'game_ids' => []];
    dbWrite('admins', array_values($out));
}

function getAdminRecord($chatId)
{
    $id = (int)$chatId;
    foreach (getAdminRecords() as $r) if ((int)$r['id'] === $id) return $r;
    return null;
}

function getAdminGameIds($chatId)
{
    if (isOwner($chatId)) return array_map(fn($g) => (int)$g['id'], getGames());
    $r = getAdminRecord($chatId);
    return $r ? array_values(array_unique(array_map('intval', $r['game_ids'] ?? []))) : [];
}

function canManageGame($chatId, $gameId)
{
    if (isOwner($chatId)) return true;
    $r = getAdminRecord($chatId);
    return $r && $r['role'] === 'admin' && in_array((int)$gameId, array_map('intval', $r['game_ids'] ?? []), true);
}

function assignAdminToGame($adminId, $gameId)
{
    $adminId = (int)$adminId;
    $gameId = (int)$gameId;
    if ($adminId <= 0 || isConfiguredOwner($adminId) || !getGame($gameId)) return false;
    $records = getAdminRecords();
    $found = false;
    foreach ($records as &$r) {
        if ((int)$r['id'] === $adminId) {
            $r['role'] = 'admin';
            // An admin can belong to one game, while each game can have many admins.
            $r['game_ids'] = [$gameId];
            $found = true;
            break;
        }
    }
    unset($r);
    if (!$found) $records[] = ['id' => $adminId, 'role' => 'admin', 'game_ids' => [$gameId]];
    saveAdminRecords($records);
    return true;
}

function getGameAdmins($gameId)
{
    $gameId = (int)$gameId;
    $out = [];
    foreach (getAdminRecords() as $r) {
        if (($r['role'] ?? '') === 'admin' && in_array($gameId, array_map('intval', $r['game_ids'] ?? []), true)) $out[] = $r;
    }
    return $out;
}

function unassignAdminFromGame($adminId, $gameId)
{
    $records = getAdminRecords();
    foreach ($records as &$r) {
        if ((int)$r['id'] === (int)$adminId) {
            $r['game_ids'] = array_values(array_filter($r['game_ids'] ?? [], fn($id) => (int)$id !== (int)$gameId));
        }
    }
    unset($r);
    saveAdminRecords($records);
}

function getGameAdmin($gameId)
{
    $admins = getGameAdmins($gameId);
    return $admins[0] ?? null;
}

function getAdmins()
{
    return array_values(array_unique(array_map(fn($r) => (int)$r['id'], getAdminRecords())));
}

function saveAdmins($admins)
{
    global $owner_id;
    $old = getAdminRecords();
    $records = [];
    foreach (array_values(array_unique(array_map('intval', $admins))) as $id) {
        $existing = null;
        foreach ($old as $r) if ((int)$r['id'] === $id) { $existing = $r; break; }
        $records[] = $existing ?: ['id' => $id, 'role' => isConfiguredOwner($id) ? 'owner' : 'admin', 'game_ids' => []];
    }
    saveAdminRecords($records);
}

function isOwner($chatId)
{
    global $owner_id;
    return isConfiguredOwner($chatId);
}

function isAdmin($chatId)
{
    return getAdminRecord($chatId) !== null;
}

function addAdmin($chatId)
{
    $records = getAdminRecords();
    $id = (int)$chatId;
    foreach ($records as $r) if ((int)$r['id'] === $id) return $id;
    $records[] = ['id' => $id, 'role' => 'admin', 'game_ids' => []];
    saveAdminRecords($records);
    return $id;
}

function removeAdmin($chatId)
{
    global $owner_id;
    $id = (int)$chatId;
    if (isConfiguredOwner($id)) return false;
    $records = array_values(array_filter(getAdminRecords(), fn($r) => (int)$r['id'] !== $id));
    saveAdminRecords($records);
    return true;
}

function channelConfigured()
{
    return trim((string)CHANNEL_ID) !== '' || trim((string)CHANNEL_USERNAME) !== '';
}

function channelChatId()
{
    return trim((string)CHANNEL_ID) !== '' ? trim((string)CHANNEL_ID) : trim((string)CHANNEL_USERNAME);
}

function isChannelMember($chatId)
{
    if (!channelConfigured()) return true;

    // Always ask Telegram for the current membership state. There is NO
    // time-based membership cache: if the user leaves, the very next
    // interaction is checked against Telegram again.
    $result = telegramRequest('getChatMember', [
        'chat_id' => channelChatId(),
        'user_id' => (int)$chatId,
    ], 1, 2);

    if (!is_array($result) || empty($result['ok'])) {
        error_log('BOT getChatMember failed: ' . json_encode($result, JSON_UNESCAPED_UNICODE));
        // Never trust an old verified flag when the current status could not
        // be confirmed. This prevents a user who has left from retaining
        // access indefinitely. The next interaction will retry the check.
        saveUser($chatId, ['channel_verified' => false]);
        return false;
    }

    $status = $result['result']['status'] ?? '';
    return in_array($status, ['creator', 'administrator', 'member'], true)
        || ($status === 'restricted' && !empty($result['result']['is_member']));
}

function channelJoinKeyboard()
{
    $url = trim((string)CHANNEL_JOIN_URL);
    if ($url === '' && trim((string)CHANNEL_USERNAME) !== '') {
        $url = 'https://t.me/' . ltrim(trim((string)CHANNEL_USERNAME), '@');
    }
    $rows = [];
    if ($url !== '') $rows[] = [["text" => "📢 عضویت در کانال", "url" => $url]];
    $rows[] = [["text" => "✅ بررسی عضویت", "callback_data" => "channel:check"]];
    return inlineKeyboard($rows);
}

function sendChannelGate($chatId)
{
    $text = "📢 <b>قبل از استفاده از ربات باید عضو کانال بازیرو شوی.</b>\n\n" .
        "1️⃣ عضو کانال شو\n2️⃣ بعد روی «✅ بررسی عضویت» بزن.";
    sendMessage($chatId, $text, channelJoinKeyboard());
}

function requireChannelMembership($chatId)
{
    if (isAdmin($chatId) || isOwner($chatId) || !channelConfigured()) return true;
    return isChannelMember($chatId);
}

function channelMembershipGuard($chatId)
{
    if (isAdmin($chatId) || isOwner($chatId) || !channelConfigured()) return true;

    // Verify the CURRENT Telegram membership on every user interaction.
    // chat_member updates are still processed for fast state changes, but the
    // database flag is only an informational cache and is never trusted for
    // access control.
    if (isChannelMember($chatId)) {
        saveUser($chatId, ["channel_verified" => true]);
        return true;
    }
    sendChannelGate($chatId);
    return false;
}

function handleChannelMemberUpdate($chatMember)
{
    if (!channelConfigured()) return;
    $chat = $chatMember['chat'] ?? [];
    $chatId = (string)($chat['id'] ?? '');
    $configured = (string)channelChatId();
    $username = ltrim((string)($chat['username'] ?? ''), '@');
    if ($chatId !== $configured && ($username === '' || '@' . $username !== $configured)) return;

    $userId = (int)($chatMember['new_chat_member']['user']['id'] ?? 0);
    if ($userId <= 0) return;
    $status = (string)($chatMember['new_chat_member']['status'] ?? '');
    $isMember = in_array($status, ['creator', 'administrator', 'member'], true)
        || ($status === 'restricted' && !empty($chatMember['new_chat_member']['is_member']));
    saveUser($userId, ["channel_verified" => $isMember]);
}

function adminRankText($chatId)
{
    return isOwner($chatId) ? '👑 Owner' : '🛡 Admin';
}

function saveUser($id, $data)
{
    $users = dbRead("users", []);
    $old = $users[(string)$id] ?? [];
    $users[(string)$id] = array_merge($old, $data);
    dbWrite("users", $users);
}

function getUser($id)
{
    $users = dbRead("users", []);
    return $users[(string)$id] ?? [];
}

function deleteUser($id)
{
    $users = dbRead("users", []);
    unset($users[(string)$id]);
    dbWrite("users", $users);
}

function saveRequest($id, $data)
{
    $requests = dbRead("requests", []);
    $requests[(string)$id] = $data;
    dbWrite("requests", $requests);
}

function hasRequest($id)
{
    $requests = dbRead("requests", []);
    return isset($requests[(string)$id]);
}

function getRequests()
{
    return dbRead("requests", []);
}

/* =========================
 * Games
 * ========================= */

function getGames()
{
    return dbRead("games", []);
}

function saveGames($games)
{
    dbWrite("games", array_values($games));
}

function getGame($id)
{
    foreach (getGames() as $game) {
        if ((int)$game["id"] === (int)$id) return $game;
    }
    return null;
}

function addGame($data)
{
    $games = getGames();
    $data["id"] = dbNextId($games);
    $data["slug"] = $data["slug"] ?? ("game_" . $data["id"]);
    $data["created_at"] = date('Y-m-d H:i:s');
    $data["active"] = $data["active"] ?? true;
    $data["latest"] = $data["latest"] ?? false;
    $data["going"] = $data["going"] ?? false;
    $data["suggested_games"] = array_values(array_unique(array_map('intval', $data["suggested_games"] ?? [])));
    $games[] = $data;
    saveGames($games);
    return $data;
}

function updateGame($id, $changes)
{
    $games = getGames();
    foreach ($games as &$game) {
        if ((int)$game["id"] === (int)$id) {
            $game = array_merge($game, $changes);
            saveGames($games);
            return $game;
        }
    }
    return null;
}

function deleteGame($id)
{
    $games = getGames();
    $new = [];
    foreach ($games as $game) {
        if ((int)$game["id"] !== (int)$id) $new[] = $game;
    }
    saveGames($new);

    $questions = getQuestions();
    $questions = array_values(array_filter($questions, function($q) use ($id) {
        return (int)($q["game_id"] ?? 0) !== (int)$id;
    }));
    saveQuestions($questions);

    $discounts = getDiscounts();
    foreach ($discounts as &$discount) {
        $discount["game_ids"] = array_values(array_filter($discount["game_ids"] ?? [], function($gameId) use ($id) {
            return (int)$gameId !== (int)$id;
        }));
    }
    saveDiscounts($discounts);
}

function seedGames()
{
    $games = getGames();
    if (!empty($games)) return;

    $game = addGame([
        "slug" => "natamam",
        "name" => "ناتمام",
        "description" => "توضیحات بازی ناتمام را از پنل ادمین خودت اینجا قرار بده.\nاین متن کاملاً قابل ویرایش است.",
        "poster" => "",
        "category" => "all",
        "active" => true,
        "latest" => false,
        "going" => false,
        "reservation_enabled" => true,
    ]);

    $oldQuestions = [
        ["text" => "تعداد نفرات گروهتون؟", "type" => "text"],
        ["text" => "میانگین تجربه گروهتون؟", "type" => "text"],
        ["text" => "۳ تا از بهترین بازیایی که رفتین؟", "type" => "text"],
        ["text" => "آیا توی گروهتون تجربه اولی یا دومی دارید؟", "type" => "yesno"],
        ["text" => "ژانر مورد علاقه اتاق فرارتون؟", "type" => "genre"],
        ["text" => "ارسال سایز تمامی اعضای گروه در قالب یک پیام:", "type" => "text"],
        ["text" => "تاریخ و ساعتی که رزرو دارید:", "type" => "text"],
    ];

    foreach ($oldQuestions as $i => $q) {
        addQuestion($game["id"], $q["text"], $q["type"], $i + 1);
    }
}

function seedAll()
{
    ensureAdminStore();
    seedGames();
    if (!file_exists(dbPath("discounts"))) dbWrite("discounts", []);
    if (!file_exists(dbPath("bookings"))) dbWrite("bookings", []);
    if (!file_exists(dbPath("questions"))) dbWrite("questions", []);
    if (!file_exists(dbPath("admin_states"))) dbWrite("admin_states", []);
    if (!file_exists(dbPath("discount_usages"))) dbWrite("discount_usages", []);
    if (!file_exists(dbPath("owners"))) dbWrite("owners", []);

    // مهاجرت اطلاعات فرم قدیمی «ناتمام» به ساختار جدید رزروها
    $oldRequestsFile = __DIR__ . "/data/requests.json";
    $bookings = getBookings();
    if (empty($bookings) && file_exists($oldRequestsFile)) {
        $oldRequests = json_decode(file_get_contents($oldRequestsFile), true);
        if (is_array($oldRequests) && $oldRequests) {
            $game = null;
            foreach (getGames() as $g) {
                if (($g["slug"] ?? '') === 'natamam') { $game = $g; break; }
            }
            if ($game) {
                foreach ($oldRequests as $chatId => $request) {
                    $answers = [];
                    $map = [
                        'people' => 'تعداد نفرات گروهتون؟',
                        'experience' => 'میانگین تجربه گروهتون؟',
                        'games' => '۳ تا از بهترین بازیایی که رفتین؟',
                        'new' => 'آیا توی گروهتون تجربه اولی یا دومی دارید؟',
                        'genre' => 'ژانر مورد علاقه اتاق فرارتون؟',
                        'sizes' => 'ارسال سایز تمامی اعضای گروه در قالب یک پیام:',
                        'reserve_time' => 'تاریخ و ساعتی که رزرو دارید:',
                    ];
                    foreach ($map as $field => $question) {
                        if (isset($request[$field])) $answers[] = ['question' => $question, 'answer' => $request[$field]];
                    }
                    $bookings[] = [
                        'id' => count($bookings) + 1,
                        'chat_id' => (string)$chatId,
                        'username' => $request['username'] ?? 'ندارد',
                        'first_name' => $request['first_name'] ?? 'ندارد',
                        'game_id' => (int)$game['id'],
                        'game_name' => $game['name'],
                        'answers' => $answers,
                        'discount_id' => null,
                        'status' => 'old_import',
                        'created_at' => date('Y-m-d H:i:s'),
                    ];
                }
                dbWrite('bookings', $bookings);
            }
        }
    }
}

/* =========================
 * Questions
 * ========================= */

function getQuestions($gameId = null)
{
    $questions = dbRead("questions", []);
    if ($gameId === null) return $questions;
    return array_values(array_filter($questions, function($q) use ($gameId) {
        return (int)($q["game_id"] ?? 0) === (int)$gameId;
    }));
}

function saveQuestions($questions)
{
    usort($questions, function($a, $b) { return ($a["order"] ?? 0) <=> ($b["order"] ?? 0); });
    dbWrite("questions", array_values($questions));
}

function addQuestion($gameId, $text, $type = "text", $order = null)
{
    $questions = getQuestions();
    if ($order === null) {
        $order = 1;
        foreach ($questions as $q) {
            if ((int)($q["game_id"] ?? 0) === (int)$gameId) {
                $order = max($order, (int)($q["order"] ?? 0) + 1);
            }
        }
    }
    $q = [
        "id" => dbNextId($questions),
        "game_id" => (int)$gameId,
        "text" => $text,
        "type" => $type,
        "order" => (int)$order,
        "active" => true,
    ];
    $questions[] = $q;
    saveQuestions($questions);
    return $q;
}

function getQuestion($id)
{
    foreach (getQuestions() as $q) {
        if ((int)$q["id"] === (int)$id) return $q;
    }
    return null;
}

function updateQuestion($id, $changes)
{
    $questions = getQuestions();
    foreach ($questions as &$q) {
        if ((int)$q["id"] === (int)$id) {
            $q = array_merge($q, $changes);
            saveQuestions($questions);
            return $q;
        }
    }
    return null;
}

function deleteQuestion($id)
{
    $questions = getQuestions();
    $questions = array_values(array_filter($questions, function($q) use ($id) {
        return (int)$q["id"] !== (int)$id;
    }));
    saveQuestions($questions);
}

/* =========================
 * Discounts
 * ========================= */

function getDiscounts()
{
    $items = dbRead("discounts", []);
    $usages = dbRead("discount_usages", []);
    $changed = false;

    // Never reuse a deleted discount's numeric ID. Older versions used
    // dbNextId($items), so deleting a code and creating a new one could
    // recycle the old ID and make the new code look already-used.
    foreach ($items as &$discount) {
        $createdAt = strtotime($discount["created_at"] ?? "");
        if (!$createdAt) continue;
        $oldUsage = false;
        $currentUsage = false;
        foreach ($usages as $usage) {
            if ((int)($usage["discount_id"] ?? 0) !== (int)($discount["id"] ?? 0)) continue;
            $usedAt = strtotime($usage["used_at"] ?? "");
            if ($usedAt && $usedAt < $createdAt) $oldUsage = true;
            if ($usedAt && $usedAt >= $createdAt) $currentUsage = true;
        }
        if ($oldUsage && !$currentUsage) {
            $maxId = 0;
            foreach ($items as $d) $maxId = max($maxId, (int)($d["id"] ?? 0));
            foreach ($usages as $u) $maxId = max($maxId, (int)($u["discount_id"] ?? 0));
            $discount["id"] = $maxId + 1;
            $changed = true;
        }
    }
    unset($discount);
    if ($changed) dbWrite("discounts", array_values($items));
    return $items;
}

function saveDiscounts($items)
{
    dbWrite("discounts", array_values($items));
}

function getDiscount($id)
{
    foreach (getDiscounts() as $d) {
        if ((int)$d["id"] === (int)$id) return $d;
    }
    return null;
}

function createDiscount($data)
{
    $items = getDiscounts();
    $usages = dbRead("discount_usages", []);
    $maxId = 0;
    foreach ($items as $d) $maxId = max($maxId, (int)($d["id"] ?? 0));
    foreach ($usages as $u) $maxId = max($maxId, (int)($u["discount_id"] ?? 0));
    $data["id"] = $maxId + 1;
    $data["code"] = strtoupper(trim($data["code"]));
    $data["used_count"] = 0;
    $data["active"] = true;
    $data["created_at"] = date('Y-m-d H:i:s');
    $items[] = $data;
    saveDiscounts($items);
    return $data;
}

function updateDiscount($id, $changes)
{
    $items = getDiscounts();
    foreach ($items as &$d) {
        if ((int)$d["id"] === (int)$id) {
            $d = array_merge($d, $changes);
            saveDiscounts($items);
            return $d;
        }
    }
    return null;
}

function findDiscountByCode($code)
{
    $code = strtoupper(trim($code));
    foreach (getDiscounts() as $d) {
        if (strtoupper($d["code"] ?? '') === $code) return $d;
    }
    return null;
}

function discountIsValid($discount)
{
    if (!$discount || empty($discount["active"])) return false;
    $max = (int)($discount["max_uses"] ?? 0);
    if ($max > 0 && (int)($discount["used_count"] ?? 0) >= $max) return false;
    if (!empty($discount["starts_at"]) && time() < strtotime($discount["starts_at"])) return false;
    if (!empty($discount["ends_at"]) && time() > strtotime($discount["ends_at"])) return false;
    return true;
}

function userUsedDiscount($chatId, $discountId)
{
    foreach (dbRead("discount_usages", []) as $usage) {
        if ((string)$usage["chat_id"] === (string)$chatId && (int)$usage["discount_id"] === (int)$discountId) return true;
    }
    return false;
}

function useDiscount($chatId, $discountId, $gameId, $extra = [])
{
    if (userUsedDiscount($chatId, $discountId)) return false;
    $discounts = getDiscounts();
    foreach ($discounts as &$d) {
        if ((int)$d["id"] !== (int)$discountId) continue;
        if (!discountIsValid($d)) return false;

        $d["used_count"] = (int)($d["used_count"] ?? 0) + 1;
        $usages = dbRead("discount_usages", []);
        $usage = [
            "id" => dbNextId($usages),
            "chat_id" => (string)$chatId,
            "discount_id" => (int)$discountId,
            "discount_code" => (string)($d["code"] ?? ""),
            "game_id" => (int)$gameId,
            "used_at" => date('Y-m-d H:i:s'),
        ];
        foreach ($extra as $k => $v) $usage[$k] = $v;
        $usages[] = $usage;

        // Save both records only after all validation has succeeded.
        saveDiscounts($discounts);
        dbWrite("discount_usages", $usages);
        return true;
    }
    return false;
}

function rollbackDiscountUsage($chatId, $discountId)
{
    $usages = dbRead("discount_usages", []);
    $removed = false;
    $kept = [];
    foreach ($usages as $usage) {
        if (!$removed && (string)($usage['chat_id'] ?? '') === (string)$chatId && (int)($usage['discount_id'] ?? 0) === (int)$discountId) {
            $removed = true;
            continue;
        }
        $kept[] = $usage;
    }
    if (!$removed) return false;
    dbWrite('discount_usages', $kept);

    $discounts = getDiscounts();
    foreach ($discounts as &$d) {
        if ((int)$d['id'] === (int)$discountId) {
            $d['used_count'] = max(0, (int)($d['used_count'] ?? 0) - 1);
            break;
        }
    }
    unset($d);
    saveDiscounts($discounts);
    return true;
}

function discountGames($discount)
{
    $ids = array_map('intval', $discount["game_ids"] ?? []);
    return array_values(array_filter(getGames(), function($game) use ($ids) {
        return in_array((int)$game["id"], $ids, true) && !empty($game["active"]);
    }));
}

/* =========================
 * Bookings
 * ========================= */

function createBooking($data)
{
    $bookings = dbRead("bookings", []);
    $data["id"] = dbNextId($bookings);
    $data["created_at"] = date('Y-m-d H:i:s');
    $bookings[] = $data;
    dbWrite("bookings", $bookings);
    return $data;
}

function getBookings()
{
    return dbRead("bookings", []);
}

/* =========================
 * Admin state
 * ========================= */

function setAdminState($chatId, $state)
{
    $states = dbRead("admin_states", []);
    $states[(string)$chatId] = $state;
    dbWrite("admin_states", $states);
}

function getAdminState($chatId)
{
    $states = dbRead("admin_states", []);
    return $states[(string)$chatId] ?? [];
}

function clearAdminState($chatId)
{
    $states = dbRead("admin_states", []);
    unset($states[(string)$chatId]);
    dbWrite("admin_states", $states);
}

/* =========================
 * Menus / rendering
 * ========================= */

function textLower($text)
{
    return function_exists('mb_strtolower') ? mb_strtolower((string)$text, 'UTF-8') : strtolower((string)$text);
}

function textSubstr($text, $start, $length = null)
{
    if (function_exists('mb_substr')) return $length === null ? mb_substr((string)$text, $start, null, 'UTF-8') : mb_substr((string)$text, $start, $length, 'UTF-8');
    return $length === null ? substr((string)$text, $start) : substr((string)$text, $start, $length);
}

function textPos($haystack, $needle)
{
    if (function_exists('mb_strpos')) return mb_strpos((string)$haystack, (string)$needle, 0, 'UTF-8');
    return strpos((string)$haystack, (string)$needle);
}

function escapeHtml($text)
{
    return htmlspecialchars((string)$text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function escapeInlineQuery($text)
{
    return trim(preg_replace('/\s+/', ' ', (string)$text));
}

function roomMenu()
{
    return inlineKeyboard([
        [["text" => "🎮 لیست همه بازی‌ها", "callback_data" => "rooms:all"]],
        [["text" => "🆕 جدیدترین بازی‌ها", "callback_data" => "rooms:latest"], ["text" => "🔥 بازی‌هایی که دارن میرن", "callback_data" => "rooms:going"]],
        [["text" => "📝 رزرو کردم، اومدم برای ثبت اطلاعات", "callback_data" => "rooms:reserve"]],
        [["text" => "🎟 اعمال کد تخفیف", "callback_data" => "discount:enter"]],
    ]);
}

function gameCardKeyboard($game)
{
    $rows = [];
    if (!empty($game["reservation_enabled"])) {
        $rows[] = [["text" => "🎟 رزرو بازی", "callback_data" => "reserve:" . $game["id"]]];
    }
    $rows[] = [["text" => "💡 پیشنهاد شده توسط " . ($game["name"] ?? "این بازی"), "callback_data" => "suggested:list:" . $game["id"]]];
    $rows[] = [["text" => "↩️ برگشت به اتاق فرار", "callback_data" => "rooms:menu"]];
    return inlineKeyboard($rows);
}

function getSuggestingGames($targetGameId)
{
    $targetGameId = (int)$targetGameId;
    if ($targetGameId <= 0) return [];

    $out = [];
    foreach (getGames() as $source) {
        if (empty($source['active'])) continue;
        $ids = array_values(array_unique(array_map('intval', $source['suggested_games'] ?? [])));
        if (in_array($targetGameId, $ids, true)) {
            $out[] = $source;
        }
    }
    return $out;
}

function sendGameCard($chatId, $game, $backCallback = "rooms:all", $suggestedBy = null)
{
    // Always calculate all games that recommend this game. The optional
    // $suggestedBy parameter is kept for backward compatibility with older callbacks.
    $suggesters = getSuggestingGames((int)($game['id'] ?? 0));
    if (!$suggesters && $suggestedBy) $suggesters = [$suggestedBy];

    $prefix = '';
    if ($suggesters) {
        $names = [];
        foreach ($suggesters as $s) {
            $name = trim((string)($s['name'] ?? ''));
            if ($name !== '' && !in_array($name, $names, true)) $names[] = $name;
        }
        if ($names) {
            $prefix = "💡 <b>پیشنهاد شده توسط " . escapeHtml(implode('، ', $names)) . "</b>\n\n";
        }
    }

    $caption = $prefix . "🎮 <b>" . escapeHtml($game["name"]) . "</b>\n\n" . escapeHtml($game["description"] ?? "");
    $keyboard = inlineKeyboard([
        !empty($game["reservation_enabled"]) ? [["text" => "🎟 رزرو بازی", "callback_data" => "reserve:" . $game["id"]]] : [],
        [["text" => "💡 پیشنهاد شده توسط " . ($game["name"] ?? "این بازی"), "callback_data" => "suggested:list:" . $game["id"]]],
        [["text" => "↩️ برگشت", "callback_data" => $backCallback]],
    ]);
    $keyboard["inline_keyboard"] = array_values(array_filter($keyboard["inline_keyboard"]));

    if (!empty($game["poster"])) {
        sendPhoto($chatId, $game["poster"], $caption, $keyboard);
    } else {
        sendMessage($chatId, $caption, $keyboard);
    }
}

function suggestedGames($sourceGameId)
{
    $source = getGame($sourceGameId);
    if (!$source) return [];
    $ids = array_values(array_unique(array_map('intval', $source['suggested_games'] ?? [])));
    $out = [];
    foreach ($ids as $id) { $g = getGame($id); if ($g && !empty($g['active'])) $out[] = $g; }
    return $out;
}

function setSuggestedGames($sourceGameId, $gameIds)
{
    $ids = array_values(array_unique(array_filter(array_map('intval', (array)$gameIds), fn($id) => $id > 0 && $id !== (int)$sourceGameId)));
    return updateGame($sourceGameId, ['suggested_games' => $ids]);
}

function listGamesText($games, $title)
{
    if (!$games) return $title . "\n\nفعلاً چیزی اینجا نیست.";
    $text = $title . "\n\n";
    foreach ($games as $i => $game) {
        $text .= ($i + 1) . ". <b>" . escapeHtml($game["name"]) . "</b>\n";
        $text .= escapeHtml($game["description"] ?? '') . "\n\n";
    }
    return $text;
}

function sendToAdmins($text, $gameId = null)
{
    $recipients = [];
    if ($gameId === null) {
        $recipients[] = (int)(getAdminRecords()[0]['id'] ?? 0);
        foreach (getAdminRecords() as $r) $recipients[] = (int)$r['id'];
    } else {
        foreach (getConfiguredOwnerIds() as $oid) $recipients[] = $oid;
        foreach (getGameAdmins($gameId) as $admin) $recipients[] = (int)$admin['id'];
    }
    foreach (array_values(array_unique(array_filter(array_map('intval', $recipients)))) as $admin) {
        $result = sendMessage($admin, $text);
        if (!is_array($result) || empty($result['ok'])) {
            error_log("BOT admin notification failed for chat " . $admin . ": " . json_encode($result, JSON_UNESCAPED_UNICODE));
        }
    }
}

function sendToAllUsers($text)
{
    $users = dbRead("users", []);
    foreach ($users as $chatId => $user) {
        @sendMessage($chatId, $text);
        usleep(300000);
    }
}

function adminGamesMenu()
{
    return inlineKeyboard([
        [["text" => "➕ افزودن بازی", "callback_data" => "admin:add_game"], ["text" => "📋 لیست / ویرایش بازی‌ها", "callback_data" => "admin:games"]],
        [["text" => "🆕 مدیریت جدیدترین‌ها", "callback_data" => "admin:latest"], ["text" => "🔥 مدیریت بازی‌های در حال رفتن", "callback_data" => "admin:going"]],
        [["text" => "↩️ برگشت به پنل ادمین", "callback_data" => "admin:back"]]
    ]);
}

function adminQuestionsMenu()
{
    return inlineKeyboard([
        [["text" => "➕ افزودن سوال برای بازی", "callback_data" => "admin:add_question"]],
        [["text" => "📋 مدیریت سوالات بازی‌ها", "callback_data" => "admin:question_games"]],
    ]);
}

function adminDiscountMenu()
{
    return inlineKeyboard([
        [["text" => "➕ ساخت کد تخفیف", "callback_data" => "admin:add_discount"]],
        [["text" => "📋 لیست کدها", "callback_data" => "admin:discounts"]],
    ]);
}

function adminStatsText()
{
    $users = dbRead("users", []);
    $games = getGames();
    $bookings = getBookings();
    $discounts = getDiscounts();
    return "📊 <b>آمار ربات</b>\n\n" .
        "👥 کاربران: " . count($users) . "\n" .
        "🎮 بازی‌ها: " . count($games) . "\n" .
        "📝 رزروها/ثبت اطلاعات: " . count($bookings) . "\n" .
        "🎟 کدهای تخفیف: " . count($discounts) . "\n";
}

/* =========================
 * Inline mode (مثل تصویر ارسالی)
 * ========================= */

function answerInlineQuery($inlineQueryId, $results, $cacheTime = 0)
{
    $result = telegramRequest("answerInlineQuery", [
        "inline_query_id" => $inlineQueryId,
        "results" => json_encode(array_values($results), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
        "cache_time" => $cacheTime,
        "is_personal" => true,
    ]);
    if (!is_array($result) || empty($result['ok'])) {
        error_log("BOT answerInlineQuery failed: " . json_encode($result, JSON_UNESCAPED_UNICODE));
    }
    return $result;
}

/** Validate a Telegram photo file_id before using InlineQueryResultCachedPhoto. */
function isValidTelegramFileId($fileId)
{
    static $cache = [];
    $fileId = trim((string)$fileId);
    if ($fileId === '') return false;
    if (array_key_exists($fileId, $cache)) return $cache[$fileId];

    $result = telegramRequest("getFile", ["file_id" => $fileId]);
    $cache[$fileId] = is_array($result) && !empty($result['ok']) && !empty($result['result']['file_id']);
    if (!$cache[$fileId]) {
        error_log("BOT invalid poster file_id: " . $fileId . " | " . json_encode($result, JSON_UNESCAPED_UNICODE));
    }
    return $cache[$fileId];
}

function inlineGameArticleResult($game)
{
    $id = 'game_' . $game["id"];
    $caption = "🎮 <b>" . escapeHtml($game["name"] ?? '') . "</b>\n\n" . escapeHtml($game["description"] ?? '');
    $buttons = [];
    if (!empty($game["reservation_enabled"])) {
        $buttons[] = [["text" => "🎟 رزرو بازی", "callback_data" => "reserve:" . $game["id"]]];
    }
    return [
        "type" => "article",
        "id" => $id,
        "title" => $game["name"] ?? 'بازی',
        "description" => textSubstr($game["description"] ?? '', 0, 150),
        "input_message_content" => [
            "message_text" => $caption,
            "parse_mode" => "HTML",
        ],
        "reply_markup" => inlineKeyboard($buttons),
    ];
}

function inlineGameResults($query = '')
{
    $query = textLower(trim($query));
    $games = array_values(array_filter(getGames(), function($g) use ($query) {
        if (empty($g["active"])) return false;
        if ($query === '' || $query === 'بازی' || $query === 'games') return true;
        $hay = textLower(($g["name"] ?? '') . ' ' . ($g["description"] ?? ''));
        return textPos($hay, $query) !== false;
    }));

    if (!$games) {
        return [[
            "type" => "article",
            "id" => "no_games",
            "title" => "فعلاً بازی‌ای پیدا نشد",
            "description" => "برای جست‌وجوی دیگری نام بازی را وارد کن.",
            "input_message_content" => [
                "message_text" => "فعلاً بازی‌ای با این جست‌وجو پیدا نشد.",
            ],
        ]];
    }

    $results = [];
    foreach ($games as $game) {
        $buttons = [];
        if (!empty($game["reservation_enabled"])) {
            $buttons[] = [["text" => "🎟 رزرو بازی", "callback_data" => "reserve:" . $game["id"]]];
        }
        $buttons[] = [["text" => "💡 پیشنهاد شده توسط " . ($game["name"] ?? "این بازی"), "callback_data" => "suggested:list:" . $game["id"]]];
        $caption = "🎮 <b>" . escapeHtml($game["name"] ?? '') . "</b>\n\n" . escapeHtml($game["description"] ?? '');
        $poster = trim((string)($game["poster"] ?? ''));

        // A single invalid cached photo can make Telegram reject the entire
        // answerInlineQuery payload. Validate each poster individually so
        // valid posters remain real photo results and only broken ones fall back.
        if ($poster !== '' && isValidTelegramFileId($poster)) {
            $results[] = [
                "type" => "photo",
                "id" => 'game_photo_' . $game["id"],
                "photo_file_id" => $poster,
                "title" => $game["name"] ?? 'بازی',
                "description" => textSubstr($game["description"] ?? '', 0, 150),
                "caption" => telegramCaption($caption),
                "parse_mode" => "HTML",
                "show_caption_above_media" => true,
                "reply_markup" => inlineKeyboard($buttons),
            ];
        } else {
            $results[] = [
                "type" => "article",
                "id" => 'game_text_' . $game["id"],
                "title" => $game["name"] ?? 'بازی',
                "description" => textSubstr($game["description"] ?? '', 0, 150),
                "input_message_content" => [
                    "message_text" => $caption,
                    "parse_mode" => "HTML",
                ],
                "reply_markup" => inlineKeyboard($buttons),
            ];
        }
    }
    return $results;
}

seedAll();
