<?php
require_once __DIR__ . '/config.php';
if (!function_exists('rx_kb_debug_log')) {
    function rx_kb_debug_log($label, $data = null) {
        return;
    }
}
function telegram($method, $datas = [], $token = null)
{
    global $APIKEY, $telegramCurlTimeout;

    $token = $token === null ? $APIKEY : $token;
    $url = "https://api.telegram.org/bot" . $token . "/" . $method;

    if (!is_array($datas)) {
        $datas = [];
    }

    $ayPremiumFallbackAttempt = !empty($datas['_ay_premium_fallback']);
    unset($datas['_ay_premium_fallback']);
    // Keep a pristine non-premium payload. If Telegram rejects custom emoji for
    // this bot/account, the same message is retried once with ordinary emoji.
    $ayPremiumPlainPayload = $datas;
    $ayPremiumWasInjected = false;

    if (isset($datas['message_thread_id']) && intval($datas['message_thread_id']) <= 0) {
        unset($datas['message_thread_id']);
    }









    $rxAlreadyTransformed = !empty($datas['_rx_already_transformed']);
    unset($datas['_rx_already_transformed']);

    $rxAutoTransformMethods = ['sendmessage', 'sendMessage', 'editmessagetext', 'editMessageText', 'sendphoto', 'sendPhoto', 'sendvideo', 'sendVideo', 'sendDocument', 'senddocument', 'editmessagecaption', 'editMessageCaption'];
    if (!$rxAlreadyTransformed && in_array(strtolower($method), array_map('strtolower', $rxAutoTransformMethods), true)) {

        $rxTextField = null;
        if (isset($datas['text']))         { $rxTextField = 'text'; }
        elseif (isset($datas['caption']))  { $rxTextField = 'caption'; }
        if ($rxTextField !== null && is_string($datas[$rxTextField]) && function_exists('applyPremiumEmojiTransform')) {
            $rxParseMode = $datas['parse_mode'] ?? null;
            $ayBeforePremiumText = $datas[$rxTextField];
            $datas[$rxTextField] = applyPremiumEmojiTransform($datas[$rxTextField], $rxParseMode);
            if ($datas[$rxTextField] !== $ayBeforePremiumText) $ayPremiumWasInjected = true;
            // Plain-text Telegram messages can carry custom emoji through explicit
            // MessageEntity objects. This makes Premium Emoji work even at call-sites
            // that don't set parse_mode=HTML.
            if (($rxParseMode === null || $rxParseMode === '') && function_exists('aycore_apply_premium_entities_to_payload')) {
                if (aycore_apply_premium_entities_to_payload($datas, $rxTextField)) {
                    $ayPremiumWasInjected = true;
                }
            }
        }

        if (isset($datas['reply_markup']) && $datas['reply_markup'] !== '' && $datas['reply_markup'] !== null) {
            if (function_exists('processKeyboardStyles')) {
                $datas['reply_markup'] = processKeyboardStyles($datas['reply_markup']);
                if (isset($ayPremiumPlainPayload['reply_markup'])) {
                    $ayPremiumPlainPayload['reply_markup'] = processKeyboardStyles($ayPremiumPlainPayload['reply_markup']);
                }
            }
            if (function_exists('applyPremiumEmojiToKeyboard')) {
                $ayBeforePremiumKeyboard = $datas['reply_markup'];
                $datas['reply_markup'] = applyPremiumEmojiToKeyboard($datas['reply_markup']);
                if ($datas['reply_markup'] !== $ayBeforePremiumKeyboard) $ayPremiumWasInjected = true;
            }
        }
    }

    // V6.2 — Premium Emoji keyboard final pass.
    // Some glass/colored keyboards are sent through methods such as
    // editMessageReplyMarkup (or arrive already transformed), so the older
    // sendMessage/editMessageText-only transformation path never touched them.
    // Telegram Bot API 9.4 supports icon_custom_emoji_id together with button style;
    // apply it to every outgoing reply_markup, regardless of Telegram method.
    // applyPremiumEmojiToKeyboard() is idempotent: existing icon ids are preserved.
    if (isset($datas['reply_markup']) && $datas['reply_markup'] !== '' && $datas['reply_markup'] !== null
        && function_exists('applyPremiumEmojiToKeyboard')) {
        $ayBeforeFinalPremiumKeyboard = $datas['reply_markup'];
        $datas['reply_markup'] = applyPremiumEmojiToKeyboard($datas['reply_markup']);
        if ($datas['reply_markup'] !== $ayBeforeFinalPremiumKeyboard) {
            $ayPremiumWasInjected = true;
        }
    }

    // Final safety net: strip any button with null/empty/non-string text from the
    // outgoing keyboard so a single corrupt DB row (e.g. a marzban_panel row with
    // name_panel = NULL feeding a dynamically-built list keyboard) can never make
    // Telegram reject the whole message with "can't parse keyboard button: Field
    // \"text\" must be of type String". Runs for ALL methods (even pre-transformed
    // sends) and is premium-safe (keeps the ' ' icon-only buttons).
    if (isset($datas['reply_markup']) && is_string($datas['reply_markup']) && $datas['reply_markup'] !== ''
        && function_exists('rx_sanitizeKeyboardButtons')) {
        $datas['reply_markup'] = rx_sanitizeKeyboardButtons($datas['reply_markup']);
    }

    $preparedPayload = prepareTelegramRequestPayload($datas);

    // Outbound to api.telegram.org has intermittent 2-5s connect timeouts
    // on some hosting providers (MatHost.eu in particular). When a single
    // sendmessage() fails, the calling handler usually has ALREADY advanced
    // `step()` past this state — leaving the admin in a "stuck" state with
    // no UI to recover. A short network-level retry inside telegram() makes
    // the rest of the codebase resilient to those blips without touching
    // ~50+ call-sites.
    //
    // Only retry on transient cURL errors (DNS, connect, timeout, recv).
    // Application errors (HTTP 4xx/5xx, "message to edit not found", etc.)
    // are NOT retried — they're real responses, not network failures.
    $retryableCurlErrnos = [6, 7, 28, 35, 56];
    $maxAttempts = 2; // 1 retry. Total worst case: ~10s (acceptable for a 30s webhook).
    $rawResponse = false;
    $curlErrorNumber = 0;
    $curlError = '';
    $httpCode = 0;
    $duration = 0.0;
    $attemptedTimes = 0;

    while ($attemptedTimes < $maxAttempts) {
        $attemptedTimes++;

        $ch = curl_init($url);
        if ($ch === false) {
            error_log('Unable to initialise cURL for Telegram request.');
            return [
                'ok' => false,
                'description' => 'Unable to initialise cURL for Telegram request.'
            ];
        }

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_ENCODING, '');
        $timeout = isset($telegramCurlTimeout) && is_numeric($telegramCurlTimeout) ? (int)$telegramCurlTimeout : 10;
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
        curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
        curl_setopt($ch, CURLOPT_TCP_KEEPALIVE, 1);
        curl_setopt($ch, CURLOPT_TCP_KEEPIDLE, 60);
        curl_setopt($ch, CURLOPT_TCP_KEEPINTVL, 30);
        if (function_exists('aycore_apply_curl_proxy')) {
            aycore_apply_curl_proxy($ch, 'telegram');
        }
        if (!empty($preparedPayload['headers'])) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $preparedPayload['headers']);
        }
        curl_setopt($ch, CURLOPT_POSTFIELDS, $preparedPayload['body']);

        $requestStartedAt = microtime(true);
        $rawResponse = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $duration = microtime(true) - $requestStartedAt;

        if ($rawResponse !== false) {
            curl_close($ch);
            break; // network success — proceed to response parsing below
        }

        $curlErrorNumber = curl_errno($ch);
        $curlError = curl_error($ch);
        curl_close($ch);

        // Stop if the error isn't network-transient, or if we just used the
        // last attempt — fall through to the error-return block.
        if (!in_array($curlErrorNumber, $retryableCurlErrnos, true) || $attemptedTimes >= $maxAttempts) {
            break;
        }

        usleep(250000); // 250 ms backoff before retry
    }

    if ($rawResponse === false) {
        if (function_exists('itz_metric')) itz_metric('telegram.api.transport_error', 1, ['method'=>(string)$method,'errno'=>(int)$curlErrorNumber]);
        if (function_exists('itz_event')) itz_event('telegram.transport_error', ['method'=>(string)$method,'errno'=>(int)$curlErrorNumber,'duration'=>(float)$duration]);
        $logError = $curlError !== '' ? $curlError : 'Unknown cURL error';
        error_log(sprintf('Telegram request failed (errno: %d, url: %s, attempts: %d): %s',
            $curlErrorNumber, $url, $attemptedTimes, $logError));

        return [
            'ok' => false,
            'description' => ($curlError !== '' ? $curlError : 'Telegram request failed.') . ' اتصال به تلگرام در مهلت مقرر برقرار نشد؛ فایروال یا پراکسی خروجی را بررسی کنید.'
        ];
    }

    if ($duration >= 5.0) {
        error_log(sprintf('Slow Telegram response detected (method: %s, http_code: %d, duration: %.3fs)', $method, $httpCode, $duration));
    }

    $decodedResponse = json_decode($rawResponse, true);
    if (!is_array($decodedResponse)) {
        $logSnippet = substr($rawResponse, 0, 200);
        error_log(sprintf('Invalid response from Telegram API (HTTP %d): %s', $httpCode, $logSnippet));

        return [
            'ok' => false,
            'error_code' => $httpCode,
            'description' => 'Invalid response received from Telegram.'
        ];
    }

    if (isset($decodedResponse['ok']) && !$decodedResponse['ok']) {
        $ayPremiumDescription = strtolower((string)($decodedResponse['description'] ?? ''));
        $ayPremiumReject = $ayPremiumWasInjected && (
            strpos($ayPremiumDescription, 'custom emoji') !== false ||
            strpos($ayPremiumDescription, 'custom_emoji') !== false ||
            strpos($ayPremiumDescription, "can't parse entities") !== false ||
            strpos($ayPremiumDescription, 'failed to parse') !== false ||
            strpos($ayPremiumDescription, 'icon_custom_emoji_id') !== false ||
            strpos($ayPremiumDescription, 'not allowed to use custom emoji') !== false ||
            strpos($ayPremiumDescription, 'custom emoji is not allowed') !== false
        );
        if ($ayPremiumReject && !$ayPremiumFallbackAttempt) {
            // Telegram limits custom-emoji rendering for some bots. Delivery is
            // more important than decoration, so retry exactly once in plain mode.
            $ayPremiumPlainPayload['_rx_already_transformed'] = true;
            $ayPremiumPlainPayload['_ay_premium_fallback'] = true;
            error_log('[premium emoji] Telegram rejected custom emoji; retrying with plain emoji: ' . ($decodedResponse['description'] ?? 'unknown'));
            return telegram($method, $ayPremiumPlainPayload, $token);
        }

        $rxBenignDescriptions = [
            'message to delete not found',
            'message to edit not found',
            'message is not modified',
            "message can't be deleted",
            'message can\'t be deleted for everyone',
            'query is too old',
            'MESSAGE_ID_INVALID',
            'replied message not found',
            'bot was blocked by the user',
            'user is deactivated',
            'chat not found',
        ];
        $rxDesc = (string) ($decodedResponse['description'] ?? '');
        $rxIsBenign = false;
        foreach ($rxBenignDescriptions as $rxNeedle) {
            if (stripos($rxDesc, $rxNeedle) !== false) {
                $rxIsBenign = true;
                break;
            }
        }
        if (!$rxIsBenign) {
            $rxTgKey = 'tg|' . (string)($decodedResponse['error_code'] ?? 0) . '|' . substr($rxDesc, 0, 60);
            if (function_exists('aycore_dedup_error_log')) {
                aycore_dedup_error_log($rxTgKey, json_encode($decodedResponse), 21600);
            } else {
                error_log(json_encode($decodedResponse));
            }
        }
    }

    if (function_exists('itz_metric')) {
        itz_metric('telegram.api.duration_ms', max(0.0, (float)$duration * 1000.0), ['method'=>(string)$method,'http'=>(int)$httpCode,'ok'=>!empty($decodedResponse['ok'])?1:0]);
        itz_metric(!empty($decodedResponse['ok']) ? 'telegram.api.success' : 'telegram.api.error', 1, ['method'=>(string)$method,'http'=>(int)$httpCode]);
    }
    return $decodedResponse;
}
function prepareTelegramRequestPayload(array $datas)
{
    $normalised = [];
    foreach ($datas as $key => $value) {
        if ($value === null) {
            continue;
        }

        $normalised[$key] = normaliseTelegramValue($value);
    }

    $containsFile = false;
    foreach ($normalised as $value) {
        if ($value instanceof CURLFile) {
            $containsFile = true;
            break;
        }
    }

    if ($containsFile) {
        return [
            'body' => $normalised,
            'headers' => ['Expect:']
        ];
    }

    $stringBody = http_build_query($normalised, '', '&', PHP_QUERY_RFC3986);

    return [
        'body' => $stringBody,
        'headers' => ['Expect:', 'Content-Type: application/x-www-form-urlencoded']
    ];
}

function normaliseTelegramValue($value)
{
    if ($value instanceof CURLFile) {
        return $value;
    }

    if (is_bool($value)) {
        return $value ? 'true' : 'false';
    }

    if (is_array($value) || is_object($value)) {
        $encoded = json_encode($value, JSON_UNESCAPED_UNICODE);
        if ($encoded === false) {
            $encoded = json_encode($value);
        }

        return $encoded === false ? '' : $encoded;
    }

    return $value;
}



if (!function_exists('aycore_validate_custom_emoji_id')) {
    function aycore_validate_custom_emoji_id(string $customEmojiId, $token = null): array {
        global $APIKEY;
        $customEmojiId = trim($customEmojiId);
        if ($customEmojiId === '') return ['ok'=>false,'error'=>'Custom Emoji ID خالی است.'];
        $token = $token === null ? ($APIKEY ?? '') : $token;
        if ($token === '') return ['ok'=>false,'error'=>'Bot Token برای اعتبارسنجی در دسترس نیست.'];
        $ch = curl_init('https://api.telegram.org/bot' . $token . '/getCustomEmojiStickers');
        if ($ch === false) return ['ok'=>false,'error'=>'cURL init failed'];
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER=>true,
            CURLOPT_POST=>true,
            CURLOPT_POSTFIELDS=>http_build_query(['custom_emoji_ids'=>json_encode([$customEmojiId])], '', '&', PHP_QUERY_RFC3986),
            CURLOPT_CONNECTTIMEOUT=>5,
            CURLOPT_TIMEOUT=>10,
            CURLOPT_IPRESOLVE=>CURL_IPRESOLVE_V4,
        ]);
        if (function_exists('aycore_apply_curl_proxy')) aycore_apply_curl_proxy($ch, 'telegram');
        $raw=curl_exec($ch); $errno=curl_errno($ch); $error=curl_error($ch); curl_close($ch);
        if ($raw===false || $errno) return ['ok'=>false,'error'=>$error!==''?$error:'Telegram lookup failed'];
        $j=json_decode((string)$raw,true);
        if (!is_array($j) || empty($j['ok'])) return ['ok'=>false,'error'=>(string)($j['description']??'Telegram rejected custom emoji ID')];
        $sticker=$j['result'][0]??null;
        if (!is_array($sticker) || (string)($sticker['custom_emoji_id']??'') !== $customEmojiId) {
            return ['ok'=>false,'error'=>'این Custom Emoji ID توسط Telegram پیدا نشد.'];
        }
        return ['ok'=>true,'sticker'=>$sticker,'emoji'=>(string)($sticker['emoji']??'')];
    }
}


if (!function_exists('aycore_test_custom_emoji_rendering')) {
    /**
     * Sends one strict custom-emoji message without AY's automatic fallback.
     * Useful to distinguish an invalid ID from Telegram bot eligibility limits.
     */
    function aycore_test_custom_emoji_rendering($chatId, string $baseEmoji, string $customEmojiId, $token = null): array {
        global $APIKEY;
        $customEmojiId = trim($customEmojiId);
        $baseEmoji = trim($baseEmoji);
        $validation = aycore_validate_custom_emoji_id($customEmojiId, $token);
        if (empty($validation['ok'])) {
            return ['ok'=>false,'stage'=>'validate','error'=>(string)($validation['error'] ?? 'Custom Emoji ID invalid')];
        }
        $fallback = trim((string)($validation['emoji'] ?? ''));
        if ($fallback === '') $fallback = $baseEmoji !== '' ? $baseEmoji : '⭐';
        $token = $token === null ? ($APIKEY ?? '') : $token;
        if ($token === '') return ['ok'=>false,'stage'=>'token','error'=>'Bot Token در دسترس نیست.'];
        $tag = '<tg-emoji emoji-id="' . htmlspecialchars($customEmojiId, ENT_QUOTES, 'UTF-8') . '">' . htmlspecialchars($fallback, ENT_QUOTES, 'UTF-8') . '</tg-emoji>';
        $payload = [
            'chat_id' => (string)$chatId,
            'text' => $tag . ' <b>AY Premium Emoji Test</b>',
            'parse_mode' => 'HTML',
            'disable_notification' => true,
        ];
        $ch = curl_init('https://api.telegram.org/bot' . $token . '/sendMessage');
        if ($ch === false) return ['ok'=>false,'stage'=>'transport','error'=>'cURL init failed'];
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER=>true,
            CURLOPT_POST=>true,
            CURLOPT_POSTFIELDS=>http_build_query($payload, '', '&', PHP_QUERY_RFC3986),
            CURLOPT_CONNECTTIMEOUT=>5,
            CURLOPT_TIMEOUT=>12,
            CURLOPT_IPRESOLVE=>CURL_IPRESOLVE_V4,
        ]);
        if (function_exists('aycore_apply_curl_proxy')) aycore_apply_curl_proxy($ch, 'telegram');
        $raw=curl_exec($ch); $errno=curl_errno($ch); $error=curl_error($ch); $http=(int)curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
        if ($raw===false || $errno) return ['ok'=>false,'stage'=>'transport','error'=>$error!==''?$error:'Telegram transport error','curl_errno'=>$errno,'http'=>$http];
        $j=json_decode((string)$raw,true);
        if (!is_array($j)) return ['ok'=>false,'stage'=>'response','error'=>'Invalid Telegram response','http'=>$http];
        if (empty($j['ok'])) {
            $desc = (string)($j['description'] ?? 'Telegram rejected custom emoji');
            $dl = strtolower($desc);
            $eligibility = (strpos($dl, 'custom emoji') !== false || strpos($dl, 'custom_emoji') !== false)
                && (strpos($dl, 'not allowed') !== false || strpos($dl, 'can only') !== false || strpos($dl, 'premium') !== false || strpos($dl, 'fragment') !== false);
            return [
                'ok'=>false,
                'stage'=>$eligibility ? 'eligibility' : 'render',
                'error'=>$desc,
                'error_code'=>$j['error_code']??$http,
                'http'=>$http,
                'hint'=>$eligibility
                    ? 'مالک واقعی Bot در BotFather باید Telegram Premium فعال داشته باشد؛ یا Bot باید شرایط Fragment برای Custom Emoji را داشته باشد.'
                    : 'Custom Emoji ID معتبر است اما Telegram این ارسال را رد کرد.'
            ];
        }
        return ['ok'=>true,'stage'=>'render','message_id'=>$j['result']['message_id']??null,'fallback_emoji'=>$fallback,'http'=>$http];
    }
}

if (!function_exists('aycore_strip_premium_keyboard_icons')) {
    function aycore_strip_premium_keyboard_icons($keyboard) {
        $wasString=is_string($keyboard);
        if ($wasString) { $kb=json_decode($keyboard,true); if(!is_array($kb)) return $keyboard; }
        elseif(is_array($keyboard)) $kb=$keyboard; else return $keyboard;
        foreach (['inline_keyboard','keyboard'] as $section) {
            if (empty($kb[$section]) || !is_array($kb[$section])) continue;
            foreach ($kb[$section] as &$row) if(is_array($row)) foreach($row as &$btn) if(is_array($btn)) unset($btn['icon_custom_emoji_id']);
            unset($row,$btn);
        }
        return $wasString?json_encode($kb,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES):$kb;
    }
}

function getPremiumEmojiMap($forceReload = false) {
    static $cache = null;
    if ($forceReload) { $cache = null; }
    if ($cache !== null) { return $cache; }
    $cache = [];
    global $pdo;
    if (!isset($pdo) || !($pdo instanceof PDO)) { return $cache; }
    try {
        $stmt = $pdo->query("SELECT premium_emoji_status FROM setting LIMIT 1");
        $row = $stmt ? $stmt->fetch(PDO::FETCH_ASSOC) : null;
        $isOn = (is_array($row) && (string)($row['premium_emoji_status'] ?? '0') === '1');
        if (!$isOn) { return $cache; }
    } catch (\Throwable $e) { return $cache; }
    try {
        $stmt = $pdo->query("SELECT emoji, custom_emoji_id FROM premium_emojis WHERE custom_emoji_id IS NOT NULL AND custom_emoji_id <> '' ORDER BY updated_at ASC, id ASC");
        if ($stmt) {
            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $emoji = (string)($row['emoji'] ?? '');
                $cid   = (string)($row['custom_emoji_id'] ?? '');
                if ($emoji !== '' && $cid !== '') { $cache[$emoji] = $cid; }
            }
        }
    } catch (\Throwable $e) {  }


    uksort($cache, function($a, $b) { return strlen($b) - strlen($a); });
    return $cache;
}
if (!function_exists('aycore_enable_premium_emoji')) {
    function aycore_enable_premium_emoji(): bool {
        global $pdo, $setting;
        try {
            if (isset($pdo) && $pdo instanceof PDO) {
                $pdo->exec("UPDATE setting SET premium_emoji_status='1'");
            }
            if (is_array($setting ?? null)) {
                $setting['premium_emoji_status'] = '1';
            }
            getPremiumEmojiMap(true);
            return true;
        } catch (Throwable $e) {
            error_log('[premium emoji] failed to enable engine: ' . $e->getMessage());
            return false;
        }
    }
}

if (!function_exists('aycore_utf16_length')) {
    function aycore_utf16_length(string $text): int {
        if ($text === '') return 0;
        if (function_exists('mb_convert_encoding')) {
            $utf16 = mb_convert_encoding($text, 'UTF-16LE', 'UTF-8');
            return (int)(strlen($utf16) / 2);
        }
        if (function_exists('iconv')) {
            $utf16 = @iconv('UTF-8', 'UTF-16LE//IGNORE', $text);
            if (is_string($utf16)) return (int)(strlen($utf16) / 2);
        }
        // Last-resort UTF-8 fallback: 4-byte Unicode code points consume two
        // UTF-16 code units; all other valid code points consume one.
        if (preg_match_all('/./us', $text, $m)) {
            $units = 0;
            foreach ($m[0] as $ch) $units += (strlen($ch) === 4 ? 2 : 1);
            return $units;
        }
        return strlen($text);
    }
}

if (!function_exists('aycore_apply_premium_entities_to_payload')) {
    /**
     * Add Telegram custom_emoji MessageEntity objects to plain-text payloads.
     * This covers messages/captions that don't use parse_mode=HTML.
     * Existing explicit entity arrays are left untouched to avoid overlaps.
     */
    function aycore_apply_premium_entities_to_payload(array &$datas, string $field): bool {
        if (!isset($datas[$field]) || !is_string($datas[$field]) || $datas[$field] === '') return false;
        if (!empty($datas['parse_mode'])) return false;
        $entityField = $field === 'caption' ? 'caption_entities' : 'entities';
        if (!empty($datas[$entityField])) return false;
        $map = getPremiumEmojiMap();
        if (empty($map)) return false;

        $text = $datas[$field];
        $hits = [];
        $occupied = [];
        foreach ($map as $emoji => $cid) {
            if ($emoji === '' || $cid === '' || strpos($text, $emoji) === false) continue;
            $from = 0;
            $elen = strlen($emoji);
            while (($pos = strpos($text, $emoji, $from)) !== false) {
                $end = $pos + $elen;
                $overlap = false;
                foreach ($occupied as $r) {
                    if ($pos < $r[1] && $end > $r[0]) { $overlap = true; break; }
                }
                if (!$overlap) {
                    $occupied[] = [$pos, $end];
                    $hits[] = [
                        '_byte' => $pos,
                        'type' => 'custom_emoji',
                        'offset' => aycore_utf16_length(substr($text, 0, $pos)),
                        'length' => aycore_utf16_length($emoji),
                        'custom_emoji_id' => (string)$cid,
                    ];
                }
                $from = $end;
            }
        }
        if (empty($hits)) return false;
        usort($hits, static fn($a, $b) => $a['_byte'] <=> $b['_byte']);
        foreach ($hits as &$hit) unset($hit['_byte']);
        unset($hit);
        $datas[$entityField] = $hits;
        return true;
    }
}

function applyPremiumEmojiTransform($text, $parseMode) {
    if (!is_string($text) || $text === '') { return $text; }
    $pm = strtolower((string)$parseMode);

    if ($pm !== 'html') { return $text; }
    $map = getPremiumEmojiMap();
    if (empty($map)) { return $text; }

    if (stripos($text, '<tg-emoji') !== false) { return $text; }
    foreach ($map as $emoji => $cid) {
        if ($emoji === '' || $cid === '') { continue; }
        if (strpos($text, $emoji) === false) { continue; }
        $cidEscaped = htmlspecialchars($cid, ENT_QUOTES);
        $tagged = '<tg-emoji emoji-id="' . $cidEscaped . '">' . $emoji . '</tg-emoji>';
        $text = str_replace($emoji, $tagged, $text);
    }
    return $text;
}


if (!defined('REPLY_STYLE_EMOJI_MAP')) {
    define('REPLY_STYLE_EMOJI_MAP', serialize([
        'success' => '🟢',
        'danger'  => '🔴',
        'primary' => '🔵',
    ]));
}


function stripReplyStyleEmoji(string $text): string {
    if ($text === '') return $text;
    foreach (unserialize(REPLY_STYLE_EMOJI_MAP) as $dot) {
        $prefix = $dot . ' ';
        $len    = mb_strlen($prefix, 'UTF-8');
        if (mb_substr($text, 0, $len, 'UTF-8') === $prefix) {
            return mb_substr($text, $len, null, 'UTF-8');
        }
    }
    return $text;
}




if (!function_exists('rx_adminPanelCallbackMapFile')) {
    function rx_adminPanelCallbackMapFile(): string {
        $base = defined('REFACTORED_LEGACY_ROOT') ? REFACTORED_LEGACY_ROOT : __DIR__;
        return rtrim($base, '/') . '/rx_admin_panel_callback_map.json';
    }
}
if (!function_exists('rx_makeAdminPanelCallback')) {
    function rx_makeAdminPanelCallback(string $text): string {
        $short = 'apn:' . $text;
        if (strlen($short) <= 64) return $short;
        $code = 'apnh:' . substr(hash('sha256', $text), 0, 32);
        $file = rx_adminPanelCallbackMapFile();
        $map = [];
        if (is_file($file)) {
            $dec = json_decode((string) @file_get_contents($file), true);
            if (is_array($dec)) $map = $dec;
        }
        $map[$code] = $text;
        @file_put_contents($file, json_encode($map, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), LOCK_EX);
        return $code;
    }
}
if (!function_exists('rx_resolveAdminPanelCallback')) {
    function rx_resolveAdminPanelCallback(string $callbackData): ?string {
        if (strpos($callbackData, 'apn:') === 0) {
            return substr($callbackData, 4);
        }
        if (strpos($callbackData, 'apnh:') === 0) {
            $file = rx_adminPanelCallbackMapFile();
            if (!is_file($file)) return null;
            $map = json_decode((string) @file_get_contents($file), true);
            return (is_array($map) && isset($map[$callbackData])) ? (string)$map[$callbackData] : null;
        }
        return null;
    }
}
if (!function_exists('rx_finalizeInlineAdminKb')) {
    function rx_finalizeInlineAdminKb(string $json): string {
        $kb = json_decode($json, true);
        if (!is_array($kb)) return $json;
        $rows = null;
        if (isset($kb['keyboard']) && is_array($kb['keyboard'])) {
            $rows = $kb['keyboard'];
        } elseif (isset($kb['inline_keyboard']) && is_array($kb['inline_keyboard'])) {
            $rows = $kb['inline_keyboard'];
        }
        if ($rows === null) return $json;




        $rxGlassMode = isset($GLOBALS['setting']['inlinebtnmain']) && $GLOBALS['setting']['inlinebtnmain'] === 'oninline';
        if (!$rxGlassMode && isset($kb['keyboard'])) {
            return $json;
        }

        $hasContactRequest = false;
        foreach ($rows as $row) {
            if (!is_array($row)) continue;
            foreach ($row as $btn) {
                if (is_array($btn) && (isset($btn['request_contact']) || isset($btn['request_location']) || isset($btn['request_poll']) || isset($btn['request_users']) || isset($btn['request_chat']))) {
                    $hasContactRequest = true;
                    break 2;
                }
            }
        }
        if ($hasContactRequest) {

            return $json;
        }

        $newRows = [];
        foreach ($rows as $row) {
            if (!is_array($row)) continue;
            $newRow = [];
            foreach ($row as $btn) {
                if (!is_array($btn) || !isset($btn['text'])) continue;
                if (!isset($btn['callback_data']) && !isset($btn['url'])
                    && !isset($btn['login_url']) && !isset($btn['switch_inline_query'])
                    && !isset($btn['switch_inline_query_current_chat']) && !isset($btn['web_app'])) {
                    $btn['callback_data'] = rx_makeAdminPanelCallback((string)$btn['text']);
                }
                $newRow[] = $btn;
            }
            if (!empty($newRow)) $newRows[] = $newRow;
        }
        return json_encode(['inline_keyboard' => $newRows], JSON_UNESCAPED_UNICODE);
    }
}

if (!function_exists('rx_sanitizeKeyboardButtons')) {
    /**
     * Guarantee every keyboard button carries a non-empty string `text` so Telegram
     * never rejects the markup with: Bad Request: can't parse keyboard button:
     * Field "text" must be of type String.
     * Button text coming from the DB/lang can be null/empty when a deployment's DB
     * is broken; such buttons are dropped here (and emptied rows removed). Works for
     * both reply (`keyboard`) and inline (`inline_keyboard`) markups and preserves
     * the wrapper key plus any top-level flags (resize_keyboard, etc.).
     */
    function rx_sanitizeKeyboardButtons($json) {
        if (!is_string($json) || $json === '') return $json;
        $kb = json_decode($json, true);
        if (!is_array($kb)) return $json;
        $wrapperKey = null;
        if (isset($kb['keyboard']) && is_array($kb['keyboard'])) {
            $wrapperKey = 'keyboard';
        } elseif (isset($kb['inline_keyboard']) && is_array($kb['inline_keyboard'])) {
            $wrapperKey = 'inline_keyboard';
        }
        if ($wrapperKey === null) return $json;
        $newRows = [];
        foreach ($kb[$wrapperKey] as $row) {
            if (!is_array($row)) continue;
            $newRow = [];
            foreach ($row as $btn) {
                if (!is_array($btn)) continue;
                // Drop only buttons Telegram would reject: missing / null / non-scalar /
                // empty-string text. Keep a single space ' ' (used by premium icon-only
                // buttons) and any other non-empty text as-is. Do NOT trim — a leading/
                // trailing space can be intentional and is a valid string for Telegram.
                if (!array_key_exists('text', $btn) || $btn['text'] === null) continue;
                $t = $btn['text'];
                if (!is_string($t)) {
                    if (is_scalar($t)) { $t = (string)$t; } else { continue; }
                }
                if ($t === '') continue;
                $btn['text'] = $t;
                $newRow[] = $btn;
            }
            if (!empty($newRow)) $newRows[] = array_values($newRow);
        }
        $kb[$wrapperKey] = array_values($newRows);
        return json_encode($kb, JSON_UNESCAPED_UNICODE);
    }
}

if (!function_exists('rx_collectKeyboardLabels')) {
    /**
     * Collect every non-empty button label from a keyboard JSON string into $out.
     * Used by the admin nav-guard to enumerate menu buttons so that pressing one
     * mid-step resets the step instead of being swallowed as step input.
     */
    function rx_collectKeyboardLabels($json, array &$out) {
        if (!is_string($json) || $json === '') return;
        $kb = json_decode($json, true);
        if (!is_array($kb)) return;
        $rows = null;
        if (isset($kb['keyboard']) && is_array($kb['keyboard'])) {
            $rows = $kb['keyboard'];
        } elseif (isset($kb['inline_keyboard']) && is_array($kb['inline_keyboard'])) {
            $rows = $kb['inline_keyboard'];
        }
        if ($rows === null) return;
        foreach ($rows as $row) {
            if (!is_array($row)) continue;
            foreach ($row as $btn) {
                if (!is_array($btn) || !isset($btn['text'])) continue;
                $label = trim((string)$btn['text']);
                if ($label !== '') $out[] = $label;
            }
        }
    }
}


function processKeyboardStyles($keyboard) {
    $styleMap = unserialize(REPLY_STYLE_EMOJI_MAP);
    $wasString = is_string($keyboard);
    if ($wasString) {
        if ($keyboard === '') return $keyboard;
        $kb = json_decode($keyboard, true);
        if (!is_array($kb)) return $keyboard;
    } elseif (is_array($keyboard)) {
        $kb = $keyboard;
    } else {
        return $keyboard;
    }


    
    
    
    
    
    static $_rx_kb_list_styles = null;
    if ($_rx_kb_list_styles === null) {
        $_rx_kb_list_styles = ['lists' => [], 'misc' => [], 'features' => []];
        try {
            global $pdo;
            if (isset($pdo) && $pdo instanceof PDO) {
                $stmt = $pdo->query("SELECT keyboard_styles_all FROM setting LIMIT 1");
                $row = $stmt ? $stmt->fetch(PDO::FETCH_ASSOC) : null;
                if (is_array($row) && !empty($row['keyboard_styles_all'])) {
                    $all = json_decode($row['keyboard_styles_all'], true);
                    if (is_array($all)) {
                        if (!empty($all['admin_lists']))  $_rx_kb_list_styles['lists'] = $all['admin_lists'];
                        if (!empty($all['user_dynamic_lists']) && is_array($all['user_dynamic_lists'])) {

                            $_rx_kb_list_styles['lists'] = $_rx_kb_list_styles['lists'] + $all['user_dynamic_lists'];
                        }
                        if (!empty($all['cron_notifications']) && is_array($all['cron_notifications'])) {

                            $_rx_kb_list_styles['lists'] = $_rx_kb_list_styles['lists'] + $all['cron_notifications'];
                        }
                        if (!empty($all['misc_buttons'])) $_rx_kb_list_styles['misc']  = $all['misc_buttons'];
                        
                        $featMenus = [
                            'features_nav','features_bot','features_users','features_shop',
                            'features_lottery','features_crons','features_antispam',
                            'admin_pagination','admin_stats','admin_search','admin_user_lists',
                            'admin_filters','admin_node','admin_gateway_extra',
                            'admin_cart_advanced','admin_crypto','admin_iplogin','admin_infocard',
                            'admin_discount_settings','admin_misc_actions','admin_premium_stock',
                            'user_subscription','recheckcrypto_buttons','recheckcrypto_admin_buttons',


                            'admin_main','admin_settings','admin_shop','admin_roles','admin_gateways',
                            'admin_features','admin_channel','admin_help','admin_category','admin_products',


                            'service','account','payment','user_nav','pay_receipt','invoice_copy_buttons',
                        ];
                        foreach ($featMenus as $fm) {
                            if (!empty($all[$fm]) && is_array($all[$fm])) {
                                $_rx_kb_list_styles['features'] = array_merge(
                                    $_rx_kb_list_styles['features'], $all[$fm]
                                );
                            }
                        }
                    }
                }
            }
        } catch (\Throwable $e) {}




        if (function_exists('rx_getKeyboardDefaultStyles') && (!function_exists('rx_kb_use_defaults') || rx_kb_use_defaults())) {
            $rx_kb_all_defaults = rx_getKeyboardDefaultStyles();
            if (is_array($rx_kb_all_defaults)) {
                foreach ($rx_kb_all_defaults as $rx_kb_sec => $rx_kb_def_pairs) {
                    if (!is_array($rx_kb_def_pairs)) { continue; }


                    if (strpos($rx_kb_sec, 'admin_panel_') === 0) { continue; }

                    if ($rx_kb_sec === 'misc_buttons') {

                        $_rx_kb_list_styles['misc'] = $_rx_kb_list_styles['misc'] + $rx_kb_def_pairs;
                    } elseif ($rx_kb_sec === 'admin_lists' || $rx_kb_sec === 'user_dynamic_lists' || $rx_kb_sec === 'cron_notifications') {

                        $_rx_kb_list_styles['lists'] = $_rx_kb_list_styles['lists'] + $rx_kb_def_pairs;
                    } else {

                        $_rx_kb_list_styles['features'] = $_rx_kb_list_styles['features'] + $rx_kb_def_pairs;
                    }
                }
            }
        }
    }
    $listPrefixMap = [
        
        'editpanel_'             => 'panel_list',
        'locationedit_'          => 'panel_list',
        'location_'              => 'paneluser_list',
        'locationnotuser_'       => 'paneluser_list',
        'locationom_'            => 'paneluser_list',
        'locationtest_'          => 'usertest_list',
        'testpanel_'             => 'usertest_list',
        'changeloc_'             => 'changeloc_list',
        'changelocselectlo-'     => 'changeloc_list',
        'changeloclimitbyuser_'  => 'changeloc_list',
        'confirmchangeloccha_'   => 'changeloc_list',
        
        'productedit_'           => 'product_list',
        'productdel_'            => 'product_list',
        'product_'               => 'product_list',
        
        'quickview_'             => 'user_services_list',
        'categorynames_'         => 'category_list',
        'category_'              => 'category_list',
        'discountedit_'          => 'discount_list',
        'discountdel_'           => 'discount_list',
        'discount_'              => 'discount_list',
        'inboundedit_'           => 'inbound_list',
        'inbound_'               => 'inbound_list',
        
        'helpedit_'              => 'help_list',
        'helpdel_'               => 'help_list',
        'helpctgory_'            => 'help_list',
        'channeldel_'            => 'channel_list',
        'confirmchannel-'        => 'channel_list',
        'carduserhide-'          => 'card_list',
        'cardremove_'            => 'card_list',
        
        'banuserlist_'           => 'user_list',
        'blockuserfake_'         => 'user_list',
        'addbalanceuser_'        => 'user_list',
        'addbalamceuser_'        => 'user_list',
        'agenttypshowlist_'      => 'agent_list',
        'addagent_'              => 'agent_list',
        'addagentrequest_'       => 'agent_list',
        'setagenttype_'          => 'agent_list',
        'expireset_'             => 'agent_list',
        'maxbuyagent_'           => 'agent_list',
        'setvolumesrc_'          => 'agent_list',
        'settimepricesrc_'       => 'agent_list',
        
        'hidepanel_'             => 'panel_list',
        'removehide_'            => 'panel_list',
        
        'deliplogin_'            => 'admin_iplogin_dyn',
        'confirmcryptomanual_'   => 'crypto_manual_actions',
        'rejectcryptomanual_'    => 'crypto_manual_actions',
        'cmauto_'                => 'crypto_manual_actions',
        'cmmanual_'              => 'crypto_manual_actions',
        'cmback_'                => 'crypto_manual_actions',
        'rcc_pick_'              => 'crypto_manual_actions',
        
        'editstsuts-'            => 'feature_toggle',
        
        
        'Response_'              => 'support_response',
        'Responsesupport_'       => 'support_response',
        'Responsesusera_'        => 'support_response',
        'Responseuser'           => 'support_response',
        
        'Extra_time_'            => 'extra_purchase',
        'Extra_volume_'          => 'extra_purchase',
        'Extra_volumes_'         => 'extra_purchase',
        'Percentlow_'            => 'extra_purchase',
        
        'btntypemessage-'        => 'btnmsg_settings',
        
        'confirmaccountdisable_'    => 'user_confirms',
        'confirmaccountdisableadmin_'=> 'user_confirms',
        'confirmaextra-'         => 'user_confirms',
        'confirmaextras_'        => 'user_confirms',
        'confirmaextratime-'     => 'user_confirms',
        'confirmchange_'         => 'user_confirms',
        'confirmdisorders-'      => 'user_confirms',
        'confirmnumber_'         => 'user_confirms',
        'confirmremovefulls-'    => 'user_confirms',
        'confirmremoveservices-' => 'user_confirms',
        'confirmserivceadmin-'   => 'user_confirms',
        'confirmserivces-'       => 'user_confirms',
        
        'removeadmin_'           => 'admin_removes',
        'removeaffiliate-'       => 'admin_removes',
        'removeaffiliateuser-'   => 'admin_removes',
        'removeagent_'           => 'admin_removes',
        'removeauto-'            => 'admin_removes',
        'removebotsell_'         => 'admin_removes',
        'rejectrequesta_'        => 'admin_removes',
        
        'crypto_pay_'            => 'crypto_actions',
        'crypto_submit_'         => 'crypto_actions',
        'cryptowallet_'          => 'crypto_actions',
        
        'config_'                => 'service_actions',
        'configget_'             => 'service_actions',
        'activeconfig-'          => 'service_actions',
        'changelink_'            => 'service_actions',
        'changenote_'            => 'service_actions',
        'changestatus_'          => 'service_actions',
        'changestatusadmin_'     => 'service_actions',
        
        'previous_page'          => 'pagination_btns',
        'next_page'              => 'pagination_btns',
        
        'broadcast_'             => 'broadcast_actions',
        'sendmsg_'               => 'broadcast_actions',
        'accept_broadcast_'      => 'broadcast_actions',
        
        'affiliates-'            => 'affiliate_actions',
        
        'editshops-'             => 'shop_edit_actions',
        'changeipnode'           => 'node_actions',
        'changenamenode'         => 'node_actions',
        'changecoefficient'      => 'node_actions',
        'bakcnode'               => 'node_actions',
        'actionnode'             => 'node_actions',
        'deletelist-'            => 'admin_removes',


        'prodcutservice_'        => 'product_buy',
        'prodcutservices_'       => 'product_buy',
        'prodcutserviceom_'      => 'product_buy',
        'prodcutservicesom_'     => 'product_buy',
        'serviceextendselects-'  => 'product_buy',
        'serviceextendselect-'   => 'product_buy',
        'serviceextend-'         => 'product_buy',
        'producttime_'           => 'time_buy',
        'productvolume_'         => 'volume_buy',
        'categorynames_'         => 'category_buy',
        'helpsection_'           => 'helpsection',
        'paneluserbuy_'          => 'panel_buy',
        'locationbuy_'           => 'panel_buy',


        'apn:'                   => 'auto_inline_btn',
        'apnh:'                  => 'auto_inline_btn',


        'extend_'                => 'cron_extend',
        'manageuser_'            => 'cron_manage_user',
        'managepanel_'           => 'cron_manage_panel',
        'cronnotify_'            => 'cron_action_btn',
    ];
    $miscMap = [
        'adm_backmenu'      => 'adm_backmenu',
        'adm_backadmin'     => 'adm_backadmin',
        'hide_mini_app_instruction' => 'hide_mini_app',
        'backproductadmin'  => 'backproductadmin',
        'backadmin'         => 'backadmin',
        'backlistuser'      => 'backlistuser',
        'buyback'           => 'buyback',
        'cancel_gift'       => 'cancel_gift',
        'cancel_hash_input' => 'cancel_hash_input',
        'cancel_sendmessage'=> 'cancel_sendmessage',
        'close_listusers'   => 'close_listusers',
        'close_stat'        => 'close_stat',
        'cronjobs_back_settings'=> 'cronjobs_back_settings',
        'resetbot_cancel'   => 'resetbot_cancel',
        'resetbot_confirm'  => 'resetbot_confirm',
        'broadcast_status_refresh' => 'broadcast_status_refresh',
        'reject'            => 'reject',
        'accept'            => 'accept',
        'acceptrule'        => 'acceptrule',
        'confirmpaid'       => 'confirmpaid',
        'confirmandgetservice' => 'confirmandgetservice',
        'confirmandgetserviceDiscount' => 'confirmandgetserviceDiscount',
        'confirmchannel'    => 'confirmchannel',


        'backuser'          => 'backuser',
        'nav_back'          => 'nav_back',
        'backorder'         => 'backorder',
        'colselist'         => 'colselist',
        'supportbtns'       => 'supportbtns',
        'helpbtns'          => 'helpbtns',
        'usertestbtn'       => 'usertestbtn',
        'none'              => 'inert_label',
        'confirmserdiscount'=> 'confirmserdiscount',
        'confirmserivce'    => 'confirmserivce',
    ];











    $rxGlassOn = isset($GLOBALS['setting']['inlinebtnmain']) && $GLOBALS['setting']['inlinebtnmain'] === 'oninline';

    if ($rxGlassOn && isset($kb['keyboard']) && is_array($kb['keyboard'])) {
        $rxHasReplyFeature = false;
        foreach ($kb['keyboard'] as $rxRChk) {
            if (!is_array($rxRChk)) continue;
            foreach ($rxRChk as $rxBChk) {
                if (is_array($rxBChk) && (
                    isset($rxBChk['request_contact']) ||
                    isset($rxBChk['request_location']) ||
                    isset($rxBChk['request_poll']) ||
                    isset($rxBChk['request_users']) ||
                    isset($rxBChk['request_chat'])
                )) {
                    $rxHasReplyFeature = true;
                    break 2;
                }
            }
        }

        if (!$rxHasReplyFeature && function_exists('rx_makeAdminPanelCallback')) {
            $rxInlineRows = [];
            foreach ($kb['keyboard'] as $rxR) {
                if (!is_array($rxR)) continue;
                $rxNewRow = [];
                foreach ($rxR as $rxB) {
                    if (!is_array($rxB) || !isset($rxB['text'])) continue;
                    if (!isset($rxB['callback_data']) && !isset($rxB['url'])
                        && !isset($rxB['login_url']) && !isset($rxB['switch_inline_query'])
                        && !isset($rxB['switch_inline_query_current_chat'])) {
                        $rxB['callback_data'] = rx_makeAdminPanelCallback((string)$rxB['text']);
                    }
                    $rxNewRow[] = $rxB;
                }
                if (!empty($rxNewRow)) $rxInlineRows[] = $rxNewRow;
            }
            unset(
                $kb['keyboard'], $kb['resize_keyboard'], $kb['input_field_placeholder'],
                $kb['one_time_keyboard'], $kb['selective'], $kb['is_persistent']
            );
            $kb['inline_keyboard'] = $rxInlineRows;
        }
    }

    if (isset($kb['keyboard']) && is_array($kb['keyboard']) && function_exists('rx_makeAdminPanelCallback')) {
        foreach ($kb['keyboard'] as &$rxReplyRow) {
            if (!is_array($rxReplyRow)) continue;
            foreach ($rxReplyRow as &$rxReplyBtn) {
                if (!is_array($rxReplyBtn) || !isset($rxReplyBtn['text'])) continue;
                if (isset($rxReplyBtn['request_contact']) || isset($rxReplyBtn['request_location'])
                    || isset($rxReplyBtn['request_poll']) || isset($rxReplyBtn['request_users'])
                    || isset($rxReplyBtn['request_chat'])) {
                    continue;
                }
                if (!isset($rxReplyBtn['callback_data'])) {
                    $rxReplyBtn['callback_data'] = rx_makeAdminPanelCallback((string)$rxReplyBtn['text']);
                }
            }
            unset($rxReplyBtn);
        }
        unset($rxReplyRow);
    }




    $rxStyleTargetKey = null;
    if (isset($kb['inline_keyboard']) && is_array($kb['inline_keyboard'])) {
        $rxStyleTargetKey = 'inline_keyboard';
    } elseif (isset($kb['keyboard']) && is_array($kb['keyboard'])) {
        $rxStyleTargetKey = 'keyboard';
    }

    if ($rxStyleTargetKey !== null) {
        foreach ($kb[$rxStyleTargetKey] as &$row) {
            if (!is_array($row)) continue;
            foreach ($row as &$btn) {
                if (!is_array($btn) || !isset($btn['callback_data'])) continue;
                if (isset($btn['style'])) continue;
                $cb = (string)$btn['callback_data'];
                
                if (isset($miscMap[$cb])) {
                    $st = $_rx_kb_list_styles['misc'][$miscMap[$cb]] ?? null;
                    if (!empty($st) && $st !== 'default') { $btn['style'] = $st; continue; }
                }

                if (isset($_rx_kb_list_styles['features'][$cb])) {
                    $st = $_rx_kb_list_styles['features'][$cb];
                    if (!empty($st) && $st !== 'default') { $btn['style'] = $st; continue; }
                }

                $matched = false;
                foreach ($listPrefixMap as $prefix => $listKey) {
                    if (strpos($cb, $prefix) === 0) {
                        $st = $_rx_kb_list_styles['lists'][$listKey] ?? null;
                        if (!empty($st) && $st !== 'default') {
                            $btn['style'] = $st;
                        }
                        $matched = true;
                        break;
                    }
                }

                if (!$matched && !isset($btn['style'])) {
                    $fb = $_rx_kb_list_styles['lists']['fallback_inline'] ?? null;
                    if (!empty($fb) && $fb !== 'default') {
                        $btn['style'] = $fb;
                    }
                }


                if (!isset($btn['style']) && isset($btn['text'])
                    && function_exists('rx_kb_guess_style_from_text')
                    && (!function_exists('rx_kb_use_defaults') || rx_kb_use_defaults())) {
                    $rxGuessed = rx_kb_guess_style_from_text((string)$btn['text']);
                    if ($rxGuessed !== null) {
                        $btn['style'] = $rxGuessed;
                    }
                }
            }
            unset($btn);
        }
        unset($row);
    }
    

    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    if (isset($kb['keyboard']) && is_array($kb['keyboard'])) {

        foreach ($kb['keyboard'] as &$row) {
            if (!is_array($row)) continue;
            foreach ($row as &$btn) {
                if (!is_array($btn)) continue;

                unset($btn['url']);
                unset($btn['switch_inline_query']);
                unset($btn['switch_inline_query_current_chat']);
                unset($btn['login_url']);
                unset($btn['callback_game']);
                unset($btn['pay']);
                unset($btn['copy_text']);
            }
            unset($btn);
        }
        unset($row);
    }


    return $wasString ? json_encode($kb, JSON_UNESCAPED_UNICODE) : $kb;
}

function applyPremiumEmojiToKeyboard($keyboard) {

    $wasString = is_string($keyboard);
    if ($wasString) {
        if ($keyboard === '') { return $keyboard; }
        $kb = json_decode($keyboard, true);
        if (!is_array($kb)) { return $keyboard; }
    } elseif (is_array($keyboard)) {
        $kb = $keyboard;
    } else {
        return $keyboard;
    }
    $map = getPremiumEmojiMap();
    if (empty($map)) {

        return $keyboard;
    }


    $sortedEmojis = array_keys($map);
    $stripPrefixIfMatch = function ($text) use ($map, $sortedEmojis) {

        if (!is_string($text) || $text === '') { return null; }
        foreach ($sortedEmojis as $emoji) {
            $emojiByteLen = strlen($emoji);
            if ($emojiByteLen === 0) { continue; }
            if (strncmp($text, $emoji, $emojiByteLen) === 0) {
                $rest = ltrim((string)substr($text, $emojiByteLen));
                return [$rest, $map[$emoji]];
            }
        }
        return null;
    };

    if (isset($kb['inline_keyboard']) && is_array($kb['inline_keyboard'])) {
        foreach ($kb['inline_keyboard'] as &$row) {
            if (!is_array($row)) { continue; }
            foreach ($row as &$btn) {
                if (!is_array($btn)) { continue; }
                if (isset($btn['icon_custom_emoji_id'])) { continue; }
                if (!isset($btn['text']) || !is_string($btn['text'])) { continue; }
                $hit = $stripPrefixIfMatch($btn['text']);
                if ($hit !== null) {
                    $btn['text'] = $hit[0] !== '' ? $hit[0] : ' ';
                    $btn['icon_custom_emoji_id'] = (string)$hit[1];
                }
            }
            unset($btn);
        }
        unset($row);
    }

    
    
    
    
    if (isset($kb['keyboard']) && is_array($kb['keyboard'])) {
        $rxReplyEntries = [];
        foreach ($kb['keyboard'] as &$row) {
            if (!is_array($row)) { continue; }
            foreach ($row as &$btn) {
                if (!is_array($btn)) { continue; }
                if (isset($btn['icon_custom_emoji_id'])) { continue; }
                if (!isset($btn['text']) || !is_string($btn['text'])) { continue; }
                $hit = $stripPrefixIfMatch($btn['text']);
                if ($hit !== null) {
                    $origText    = $btn['text'];
                    $strippedTxt = $hit[0] !== '' ? $hit[0] : ' ';
                    
                    $rxReplyEntries[$strippedTxt] = $origText;
                    $btn['text']              = $strippedTxt;
                    $btn['icon_custom_emoji_id'] = (string)$hit[1];
                }
            }
            unset($btn);
        }
        unset($row);
        
        if (!empty($rxReplyEntries)) {
            $rxRbmFile = (defined('REFACTORED_LEGACY_ROOT') ? REFACTORED_LEGACY_ROOT : __DIR__)
                       . '/rx_reply_button_map.json';
            $rxRbmExisting = [];
            if (is_file($rxRbmFile)) {
                $rxRbmRaw = json_decode((string)@file_get_contents($rxRbmFile), true);
                if (is_array($rxRbmRaw)) { $rxRbmExisting = $rxRbmRaw; }
            }
            $rxRbmMerged = array_merge($rxRbmExisting, $rxReplyEntries);
            @file_put_contents(
                $rxRbmFile,
                json_encode($rxRbmMerged, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
                LOCK_EX
            );
        }
    }

    return $wasString ? json_encode($kb, JSON_UNESCAPED_UNICODE) : $kb;
}


function rx_restorePremiumReplyText(string $text): string {
    if ($text === '') { return $text; }
    $rxRbmFile = (defined('REFACTORED_LEGACY_ROOT') ? REFACTORED_LEGACY_ROOT : __DIR__)
               . '/rx_reply_button_map.json';
    if (!is_file($rxRbmFile)) { return $text; }
    $rxRbmMap = json_decode((string)@file_get_contents($rxRbmFile), true);
    if (!is_array($rxRbmMap)) { return $text; }
    return isset($rxRbmMap[$text]) ? (string)$rxRbmMap[$text] : $text;
}

if (!function_exists('rx_getKeyboardDefaultStyles')) {





    function rx_getKeyboardDefaultStyles($section = null) {
        static $cache = null;
        if ($cache === null) {
            $cache = [
                'admin_main' => [
                    'admin_status'      => 'primary',
                    'admin_managepanel' => 'success',
                    'admin_addpanel'    => 'success',
                    'admin_timeprice'   => 'success',
                    'admin_volprice'    => 'success',
                    'admin_users'       => 'primary',
                    'admin_shop'        => 'success',
                    'admin_finance'     => 'success',
                    'admin_support'     => 'primary',
                    'admin_help'        => 'primary',
                    'admin_features'    => 'success',
                    'admin_settings'    => 'success',
                    'admin_invoices'    => 'success',
                    'admin_back'        => 'danger',
                ],
                'admin_settings' => [
                    'set_features'   => 'success',
                    'set_reports'    => 'primary',
                    'set_channel'    => 'primary',
                    'set_webpanel'   => 'success',
                    'set_optimize'   => 'success',
                    'set_text'       => 'primary',
                    'set_adminmgr'   => 'primary',
                    'set_testlimit'  => 'success',
                    'set_agentprice' => 'success',
                    'set_qrbg'       => 'primary',
                    'set_webhook'    => 'primary',
                    'set_backadmin'  => 'danger',
                    'set_backmenu'   => 'danger',
                ],
                'admin_shop' => [
                    'shop_status'      => 'success',
                    'shop_category'    => 'primary',
                    'shop_products'    => 'primary',
                    'shop_giftadd'     => 'success',
                    'shop_giftdel'     => 'danger',
                    'shop_discountadd' => 'success',
                    'shop_discountdel' => 'danger',
                    'shop_minbulk'     => 'primary',
                    'shop_renewcb'     => 'primary',
                    'shop_backadmin'   => 'danger',
                    'shop_backmenu'    => 'danger',
                ],
                'admin_roles' => [
                    'seller_status'  => 'primary',
                    'seller_users'   => 'success',
                    'seller_back'    => 'danger',
                    'support_users'  => 'success',
                    'support_search' => 'primary',
                    'support_back'   => 'danger',
                ],
                'admin_gateways' => [

                    'cart_title'       => 'primary',
                    'cart_setnum'      => 'success',
                    'cart_delnum'      => 'danger',
                    'cart_support'     => 'primary',
                    'cart_pvmode'      => 'primary',
                    'cart_autoconfirm' => 'success',
                    'cart_cashback'    => 'success',
                    'cart_firstpay'    => 'primary',
                    'cart_min'         => 'primary',
                    'cart_max'         => 'primary',
                    'cart_edu'         => 'primary',
                    'cart_back'        => 'danger',

                    'trnado_name'      => 'primary',
                    'trnado_apikey'    => 'success',
                    'trnado_wallet'    => 'success',
                    'trnado_apiurl'    => 'success',
                    'trnado_cashback'  => 'success',
                    'trnado_min'       => 'primary',
                    'trnado_max'       => 'primary',
                    'trnado_edu'       => 'primary',
                    'trnado_back'      => 'danger',

                    'zpal_name'        => 'primary',
                    'zpal_merchant'    => 'success',
                    'zpal_cashback'    => 'success',
                    'zpal_min'         => 'primary',
                    'zpal_max'         => 'primary',
                    'zpal_edu'         => 'primary',
                    'zpal_back'        => 'danger',

                    'zpey_name'        => 'primary',
                    'zpey_token'       => 'success',
                    'zpey_cashback'    => 'success',
                    'zpey_tutorial'    => 'primary',
                    'zpey_min'         => 'primary',
                    'zpey_max'         => 'primary',
                    'zpey_edu'         => 'primary',
                    'zpey_back'        => 'danger',

                    'aqaye_name'       => 'primary',
                    'aqaye_merchant'   => 'success',
                    'aqaye_cashback'   => 'success',
                    'aqaye_min'        => 'primary',
                    'aqaye_max'        => 'primary',
                    'aqaye_edu'        => 'primary',
                    'aqaye_back'       => 'danger',

                    'plisio_name'      => 'primary',
                    'plisio_api'       => 'success',
                    'plisio_cashback'  => 'success',
                    'plisio_min'       => 'primary',
                    'plisio_max'       => 'primary',
                    'plisio_edu'       => 'primary',
                    'plisio_back'      => 'danger',
                ],
                'admin_features' => [
                    'feat_info'     => 'success',
                    'feat_test'     => 'success',
                    'feat_help'     => 'success',
                    'feat_back'     => 'danger',
                    'feat_backmenu' => 'danger',
                ],
                'admin_channel' => [
                    'ch_add'      => 'success',
                    'ch_del'      => 'danger',
                    'ch_back'     => 'danger',
                    'ch_backmenu' => 'danger',
                ],
                'admin_help' => [
                    'help_add'      => 'success',
                    'help_del'      => 'danger',
                    'help_edit'     => 'primary',
                    'help_back'     => 'danger',
                    'help_backmenu' => 'danger',
                ],
                'admin_category' => [
                    'cat_add'  => 'success',
                    'cat_del'  => 'danger',
                    'cat_edit' => 'primary',
                    'cat_back' => 'danger',
                ],
                'admin_products' => [
                    'shopitem_add'      => 'success',
                    'shopitem_del'      => 'danger',
                    'shopitem_edit'     => 'primary',
                    'shopitem_priceinc' => 'success',
                    'shopitem_pricedec' => 'danger',
                    'shopitem_back'     => 'danger',
                ],
                'admin_product_edit' => [
                    'قیمت'                  => 'success',
                    'حجم'                   => 'success',
                    'زمان'                  => 'success',
                    'عنوان پلن'             => 'primary',
                    'نوع کاربری'            => 'primary',
                    'نوع ریست حجم'          => 'primary',
                    'یادداشت'               => 'primary',
                    'موقعیت محصول'          => 'primary',
                    'دسته‌بندی'             => 'primary',
                    '🎛 مدیریت Inbound'      => 'success',
                    'نمایش فقط برای خرید اول'   => 'success',
                    'مخفی کردن پنل'         => 'primary',
                    'پاک‌سازی محدودیت نمایش پنل‌ها'  => 'danger',
                    'backadmin'             => 'danger',
                    'backmenu'              => 'danger',
                ],
                'admin_stats' => [
                    'today_stat'         => 'primary',
                    'yesterday_stat'     => 'primary',
                    'hoursago_stat'      => 'primary',
                    'view_stat_time'     => 'primary',
                    'month_current_stat' => 'success',
                    'month_old_stat'     => 'success',
                    'stat_all_bot'       => 'success',
                    'status_var'         => 'primary',
                    'showprice'          => 'success',
                ],
                'admin_cart_advanced' => [
                    'cart_autocheck'   => 'success',
                    'cart_autotime'    => 'primary',
                    'cart_except_user' => 'primary',
                    'cart_export_num'  => 'primary',
                    'cart_show_num'    => 'success',
                    'cart_hide_num'    => 'danger',
                    'cart_group_num'   => 'primary',
                    'checkpay'         => 'success',
                    'paydirect'        => 'success',
                ],
                'admin_search' => [
                    'searchorder'   => 'primary',
                    'searchservice' => 'primary',
                    'searchuser'    => 'primary',
                    'selectname'    => 'success',
                ],
                'admin_user_lists' => [
                    'alllistusers'    => 'primary',
                    'agentlistusers'  => 'primary',
                    'balanceuserlist' => 'success',
                    'cartuserlist'    => 'success',
                    'adminlist'       => 'primary',
                    'listrefral'      => 'primary',
                    'zerobalance'     => 'danger',
                    'balanceaddall'   => 'success',
                ],
                'features_bot' => [
                    'subject'             => 'primary',
                    'subjectde'           => 'primary',
                    'statusbot'           => 'success',
                    'stautsrolee'         => 'primary',
                    'Authenticationphone' => 'success',
                    'Authenticationiran'  => 'success',
                    'verify'              => 'success',
                    'verifybyuser'        => 'success',
                    'inlinebtnmain'       => 'success',
                ],
                'features_users' => [
                    'usernamebtn'       => 'success',
                    'statusnewuser'     => 'success',
                    'statussupportpv'   => 'success',
                    'statusnamecustom'  => 'success',
                    'statusnamecustomf' => 'success',
                ],
                'features_shop' => [
                    'bulkbuy'             => 'success',
                    'btn_status_category' => 'success',
                    'keyconfig'           => 'success',
                    'copycart'            => 'success',
                    'Debtsettlement'      => 'success',
                    'changeloc'           => 'success',
                    'changeloclimit'      => 'primary',
                    'infocard_status'     => 'success',
                    'infocard_color_menu' => 'primary',
                    'linkappstatus'       => 'success',
                    'linkappsetting'      => 'primary',
                ],
                'features_lottery' => [
                    'wheel_luck'         => 'success',
                    'gradonhshans'       => 'primary',
                    'wheelagentfirst'    => 'success',
                    'wheelagent'         => 'success',
                    'score'              => 'success',
                    'scoresetting'       => 'primary',
                    'Lotteryagent'       => 'success',
                    'affiliatesstatus'   => 'success',
                    'settingaffiliatesf' => 'primary',
                    'Dice'               => 'success',
                ],
                'features_crons' => [
                    'cronday'                 => 'success',
                    'settimecornday'          => 'primary',
                    'on_hold'                 => 'success',
                    'setting_on_holdcron'     => 'primary',
                    'cronvolume'              => 'success',
                    'settimecornvolume'       => 'primary',
                    'notifremove'             => 'danger',
                    'settimecornremove'       => 'primary',
                    'notifremove_volume'      => 'danger',
                    'settimecornremovevolume' => 'primary',
                    'cronjobs_settings'       => 'success',
                ],
                'features_antispam' => [
                    'antispam_toggle'      => 'success',
                    'antispam_set_count'   => 'primary',
                    'antispam_set_seconds' => 'primary',
                    'antispam_set_mute'    => 'primary',
                ],
                'features_nav' => [
                    'featcat_bot'            => 'success',
                    'featcat_users'          => 'success',
                    'featcat_shop'           => 'success',
                    'featcat_lottery'        => 'success',
                    'featcat_crons'          => 'success',
                    'featcat_antispam'       => 'success',
                    'premium_emoji_settings' => 'primary',
                    'featcat_main'           => 'danger',
                    'close_stat'             => 'danger',
                ],
                'admin_pagination' => [
                    'next_page'                => 'primary',
                    'previous_page'            => 'primary',
                    'next_page_extends'        => 'primary',
                    'previous_page_extends'    => 'primary',
                    'next_pageuser'            => 'primary',
                    'previous_pageuser'        => 'primary',
                    'next_pageuserbalance'     => 'primary',
                    'previous_pageuserbalance' => 'primary',
                    'next_pageusercart'        => 'primary',
                    'previous_pageusercart'    => 'primary',
                    'next_pageuserrefral'      => 'primary',
                    'previous_pageuserrefral'  => 'primary',
                    'next_pageuserzero'        => 'primary',
                    'previous_pageuserzero'    => 'primary',
                ],
                'admin_filters' => [
                    'typecustomer_all'         => 'primary',
                    'typecustomer_customer'    => 'success',
                    'typecustomer_notcustomer' => 'danger',
                    'typebalanceall_all'       => 'primary',
                    'typebalanceall_f'         => 'success',
                    'typebalanceall_n2'        => 'success',
                    'typebalanceall_nl'        => 'success',
                    'typeaddprice_percent'     => 'primary',
                    'typeaddprice_static'      => 'primary',
                    'typeagenteditproduct_f'   => 'success',
                    'typeagenteditproduct_n'   => 'success',
                    'typeagenteditproduct_n2'  => 'success',
                    'discounttype_all'         => 'primary',
                    'discounttype_buy'         => 'success',
                    'discounttype_extend'      => 'success',
                    'discountlimitbuy_0'       => 'primary',
                    'discountlimitbuy_1'       => 'success',
                    'typegift_day'             => 'success',
                    'typegift_volume'          => 'success',
                    'voloume_or_day_all'       => 'primary',
                    'agenttypshowlist_all'     => 'primary',
                    'agenttypshowlist_n'       => 'success',
                    'agenttypshowlist_n2'      => 'success',
                ],
                'admin_node' => [
                    'namenode'       => 'primary',
                    'changenamenode' => 'primary',
                    'changeipnode'   => 'primary',
                    'addiplogin'     => 'success',
                    'iploginset'     => 'primary',
                    'removenode'     => 'danger',
                    'reconnectnode'  => 'success',
                    'bakcnode'       => 'danger',
                    'actionnode'     => 'success',
                ],
                'admin_gateway_extra' => [
                    'cartsetting'            => 'primary',
                    'carttocart'             => 'success',
                    'aqayepardakhtsetting'   => 'primary',
                    'zarinpalsetting'        => 'primary',
                    'zarinpeysetting'        => 'primary',
                    'plisiosetting'          => 'primary',
                    'nowpaymentsetting'      => 'primary',
                    'iranpay1setting'        => 'primary',
                    'iranpay2setting'        => 'primary',
                    'iranpay3setting'        => 'primary',
                    'affilnecurrency'        => 'success',
                    'affilnecurrencysetting' => 'primary',
                    'arzireyali1'            => 'success',
                    'arzireyali2'            => 'success',
                    'oniranpay3'             => 'success',
                ],
                'admin_crypto' => [
                    'cryptowallet_TON'        => 'primary',
                    'cryptowallet_TRX'        => 'success',
                    'cryptowallet_USDT_TON'   => 'primary',
                    'cryptowallet_USDT_TRC20' => 'success',
                    'walletaddress'           => 'success',
                    'cryptomemo_yes_TON'      => 'success',
                    'cryptomemo_no_TON'       => 'danger',
                    'cryptomemo_yes_USDT_TON' => 'success',
                    'cryptomemo_no_USDT_TON'  => 'danger',
                ],
                'admin_iplogin' => [
                    'addiplogin'       => 'success',
                    'iploginunlim_on'  => 'success',
                    'iploginunlim_off' => 'danger',
                    'iploginset'       => 'primary',
                ],
                'admin_infocard' => [
                    'infocard_setcolor_red'    => 'danger',
                    'infocard_setcolor_green'  => 'success',
                    'infocard_setcolor_blue'   => 'primary',
                    'infocard_setcolor_orange' => 'primary',
                    'infocard_setcolor_purple' => 'primary',
                    'infocard_setcolor_yellow' => 'primary',
                ],
                'admin_discount_settings' => [
                    'discountextend'     => 'success',
                    'startgift'          => 'success',
                    'get_gift_start'     => 'success',
                    'statuscategorytime' => 'primary',
                    'statustimeextra'    => 'primary',
                ],
                'admin_misc_actions' => [
                    'addnewadmin'             => 'success',
                    'customsellvolume'        => 'primary',
                    'changecoefficient'       => 'primary',
                    'changgestatus'           => 'primary',
                    'categroygenral'          => 'primary',
                    'changenote'              => 'primary',
                    'optimizebot'             => 'success',
                    'removeresid'             => 'danger',
                    'productcheckdata'        => 'primary',
                    'mainbalanceaccount'      => 'success',
                    'maxbalanceaccount'       => 'primary',
                    'kharidanbuh'             => 'success',
                    'systemsms'               => 'primary',
                    'linkappdownlod'          => 'primary',
                    'fqQuestions'             => 'primary',
                    'disorderss'              => 'primary',
                    'reasetchangeloc'         => 'danger',
                    'serviceextendselect_pre' => 'primary',
                    'removeservicebackbtn'    => 'danger',
                    'startelegram'            => 'success',
                ],
                'admin_premium_stock' => [
                    'premium_emoji_add'  => 'success',
                    'premium_emoji_noop' => 'primary',
                    'nm_del_all_stock'   => 'danger',
                    'nm_del_one_stock'   => 'danger',
                    'antispam_noop'      => 'primary',
                ],
                'recheckcrypto_admin_buttons' => [
                    'confirmcryptomanual' => 'success',
                    'rejectcryptomanual'  => 'danger',
                    'cmauto'              => 'success',
                    'cmmanual'            => 'primary',
                    'cmback'              => 'danger',
                ],
                'admin_lists' => [
                    'panel_list'            => 'primary',
                    'paneluser_list'        => 'primary',
                    'usertest_list'         => 'primary',
                    'changeloc_list'        => 'primary',
                    'product_list'          => 'primary',
                    'user_services_list'    => 'primary',
                    'discount_list'         => 'primary',
                    'inbound_list'          => 'primary',
                    'help_list'             => 'primary',
                    'channel_list'          => 'primary',
                    'card_list'             => 'primary',
                    'protocol_list'         => 'primary',
                    'category_list'         => 'primary',
                    'user_list'             => 'primary',
                    'agent_list'            => 'primary',
                    'feature_toggle'        => 'success',
                    'support_response'      => 'primary',
                    'extra_purchase'        => 'success',
                    'btnmsg_settings'       => 'primary',
                    'user_confirms'         => 'success',
                    'admin_removes'         => 'danger',
                    'crypto_actions'        => 'success',
                    'service_actions'       => 'primary',
                    'pagination_btns'       => 'primary',
                    'broadcast_actions'     => 'success',
                    'affiliate_actions'     => 'primary',
                    'shop_edit_actions'     => 'primary',
                    'node_actions'          => 'primary',
                    'admin_iplogin_dyn'     => 'danger',
                    'crypto_manual_actions' => 'success',


                ],
                'service' => [
                    'updateinfo'       => 'primary',
                    'config'           => 'success',
                    'linksub'          => 'primary',
                    'extend'           => 'success',
                    'Extra_volume'     => 'success',
                    'Extra_time'       => 'success',
                    'changestatus'     => 'primary',
                    'change-location'  => 'primary',
                    'transfor'         => 'primary',
                    'ekhtelal'         => 'primary',
                    'removeservice'    => 'danger',
                    'changelink'       => 'primary',
                    'changenameconfig' => 'primary',
                    'backorder'        => 'danger',
                ],
                'account' => [
                    'Discount'    => 'success',
                    'Add_Balance' => 'success',
                    'backuser'    => 'danger',
                ],
                'payment' => [
                    'cart_to_offline'  => 'success',
                    'aqayepardakht'    => 'success',
                    'zarinpal'         => 'success',
                    'zarinpey'         => 'success',
                    'iranpay1'         => 'success',
                    'iranpay2'         => 'success',
                    'iranpay3'         => 'success',
                    'paymentnotverify' => 'success',
                    'plisio'           => 'primary',
                    'nowpayment'       => 'primary',
                    'digitaltron'      => 'primary',
                    'startelegrams'    => 'primary',
                    'colselist'        => 'danger',
                ],
                'user_nav' => [
                    'confirm_pay'      => 'success',
                    'confirm_discount' => 'success',
                    'confirm_back'     => 'danger',
                    'rules_accept'     => 'success',
                    'nav_back'         => 'danger',
                    'contact_phone'    => 'success',
                    'contact_back'     => 'danger',
                ],
                'pay_receipt' => [
                    'pay_sendreceipt' => 'success',
                    'pay_done'        => 'success',
                    'pay_cancel'      => 'danger',
                    'pay_back'        => 'danger',
                    'pay_wallet_copy' => 'primary',
                    'pay_card_copy'   => 'primary',
                    'pay_check'       => 'primary',
                ],
                'user_subscription' => [
                    'buy_service'     => 'success',
                    'helpbtn'         => 'primary',
                    'support'         => 'primary',
                    'Status'          => 'primary',
                    'LastTraffic'     => 'primary',
                    'RemainingVolume' => 'primary',
                    'expirationDate'  => 'primary',
                    'extravolunme'    => 'success',
                    'exntedagei'      => 'success',
                    'Responseuser'    => 'primary',
                    'requestagent'    => 'success',
                    'iduser'          => 'primary',
                    'username'        => 'primary',
                    'notusernameme'   => 'primary',
                ],
                'invoice_copy_buttons' => [
                    'currency_pick' => 'primary',
                    'mode_external' => 'primary',
                    'mode_iranian'  => 'primary',
                    'copy_wallet'   => 'primary',
                    'copy_amount'   => 'primary',
                    'copy_memo'     => 'primary',
                    'paid_submit'   => 'success',
                    'invoice_back'  => 'danger',
                ],
                'recheckcrypto_buttons' => [
                    'recheckcrypto'        => 'success',
                    'rcc_pick_TRX'         => 'primary',
                    'rcc_pick_TON'         => 'primary',
                    'rcc_pick_USDT_TRC20'  => 'primary',
                    'rcc_pick_USDT_TON'    => 'primary',
                    'rcc_skip_photo'       => 'primary',
                    'rcc_cancel'           => 'danger',
                ],
                'user_dynamic_lists' => [
                    'product_buy'      => 'primary',
                    'category_buy'     => 'primary',
                    'time_buy'         => 'primary',
                    'volume_buy'       => 'primary',
                    'helpsection'      => 'primary',
                    'channel_join'     => 'success',
                    'panel_buy'        => 'primary',
                    'panel_back'       => 'danger',
                    'product_back'     => 'danger',
                    'category_back'    => 'danger',
                    'custom_volume'    => 'success',
                    'auto_inline_btn'  => 'primary',
                ],
                'cron_notifications' => [
                    'cron_extend'         => 'success',
                    'cron_manage_user'    => 'primary',
                    'cron_manage_panel'   => 'primary',
                    'cron_action_btn'     => 'primary',
                    'cron_buy_service'    => 'success',
                    'cron_start_bot'      => 'success',
                    'cron_usertest'       => 'success',
                    'cron_help'           => 'primary',
                    'cron_affiliates'     => 'success',
                    'cron_addbalance'     => 'success',
                    'cron_cancel'         => 'danger',
                ],
                'misc_buttons' => [
                    'adm_backmenu'                 => 'danger',
                    'adm_backadmin'                => 'danger',
                    'hide_mini_app'                => 'danger',
                    'backproductadmin'             => 'danger',
                    'backadmin'                    => 'danger',
                    'backlistuser'                 => 'danger',
                    'buyback'                      => 'danger',
                    'cancel_gift'                  => 'danger',
                    'cancel_hash_input'            => 'danger',
                    'cancel_sendmessage'           => 'danger',
                    'close_listusers'              => 'danger',
                    'close_stat'                   => 'danger',
                    'cronjobs_back_settings'       => 'danger',
                    'resetbot_cancel'              => 'danger',
                    'resetbot_confirm'             => 'success',
                    'broadcast_status_refresh'     => 'primary',
                    'reject'                       => 'danger',
                    'accept'                       => 'success',
                    'acceptrule'                   => 'success',
                    'confirmpaid'                  => 'success',
                    'confirmandgetservice'         => 'success',
                    'confirmandgetserviceDiscount' => 'success',
                    'confirmchannel'               => 'success',
                    'confirmserdiscount'           => 'success',
                    'confirmserivce'               => 'success',
                    'agentpanel'                   => 'primary',
                    'cronjob_display'              => 'primary',
                    'startaction'                  => 'success',
                    'locationedit_all'             => 'primary',
                    'locationmessage_all'          => 'primary',


                    'backuser'                     => 'danger',
                    'nav_back'                     => 'danger',
                    'backorder'                    => 'danger',
                    'colselist'                    => 'danger',
                    'supportbtns'                  => 'primary',
                    'helpbtns'                     => 'primary',
                    'usertestbtn'                  => 'success',
                    'inert_label'                  => 'primary',
                ],
            ];










            $rxPanelReplyCommon = [
                "🧩 قابلیت‌های پنل"            => 'success',
                "✍️ نام پنل"                          => 'primary',
                "❌ حذف پنل"                          => 'danger',
                "🔐 تغییر رمز عبور"                  => 'primary',
                "🪪 تغییر نام کاربری"                => 'primary',
                "🔗 تغییر Endpoint پنل"                  => 'primary',
                "♻️ سیاست تمدید"                  => 'success',
                "🪪 الگوی نام کاربری"              => 'primary',
                "🛡️ سقف ساخت اکانت"               => 'success',
                "🧭 جابه‌جایی گروه کاربر"                => 'primary',
                "⏱️ مدت سرویس آزمایشی"                  => 'success',
                "🧪 حجم سرویس آزمایشی"                   => 'success',
                "🌍 تعرفه تغییر لوکیشن"                => 'success',
                "➕ تعرفه حجم اضافه"                   => 'success',
                "⏳ تعرفه زمان اضافه"                  => 'success',
                "🧮 تعرفه حجم سفارشی"          => 'success',
                "⏳ تعرفه زمان سفارشی"                 => 'success',
                "📦 کف حجم سفارشی"                 => 'primary',
                "📦 سقف حجم سفارشی"                => 'primary',
                "⏱️ کف زمان سفارشی"                => 'primary',
                "⏳ سقف زمان سفارشی"               => 'primary',
                "📦 خزانه شبکه ملی"                   => 'primary',
                "🚨 ثبت مسیر اضطراری"                  => 'success',
                "🚨 مسیر اضطراری"                      => 'danger',
                "🌐 وضعیت شبکه داخلی"                    => 'success',
                "🙈 مخفی‌سازی پنل برای کاربر"      => 'primary',
                "👁️ خارج‌کردن کاربر از لیست مخفی"    => 'danger',
                "⚙️  Inbound غیرفعال"          => 'primary',
            ];

            $cache['admin_panel_marzban'] = $rxPanelReplyCommon + [
                "🧬 پروتکل و Inbound" => 'success',
            ];
            $cache['admin_panel_guard'] = $rxPanelReplyCommon + [
                "🔐 ویرایش کلید"                => 'primary',
                "🛰️ تست ارتباط Backend"       => 'primary',
                "🧰 مدیریت اشتراک‌ها"            => 'success',
                "🎛️ پروفایل سرویس"            => 'primary',
            ];
            $cache['admin_panel_ibsng'] = $rxPanelReplyCommon + [
                '🏷️ نام‌گذاری گروه' => 'primary',
            ];
            $cache['admin_panel_mikrotik'] = $rxPanelReplyCommon + [
                '🏷️ نام‌گذاری گروه' => 'primary',
            ];
            $cache['admin_panel_s_ui'] = $rxPanelReplyCommon + [
                "🧬 پروتکل و Inbound" => 'success',
            ];
            $cache['admin_panel_wg'] = $rxPanelReplyCommon + [
                "💎 انتخاب شناسه Inbound" => 'success',
            ];
            $cache['admin_panel_marzneshin'] = $rxPanelReplyCommon + [
                "🧩 مرکز تنظیمات سرویس" => 'primary',
            ];
            $cache['admin_panel_manualsale'] = $rxPanelReplyCommon + [
                "➕ افزودن کانفیگ" => 'success',
                "❌ حذف کانفیگ "       => 'danger',
                "✏️ ویرایش رکورد کانفیگ"    => 'primary',
            ];
            $cache['admin_panel_x_ui_single'] = $rxPanelReplyCommon + [
                "💎 انتخاب شناسه Inbound" => 'success',
                '🔗 دامنه Subscription'      => 'primary',
            ];
            $cache['admin_panel_alireza_single'] = $rxPanelReplyCommon + [
                "💎 انتخاب شناسه Inbound" => 'success',
                '🔗 دامنه Subscription'      => 'primary',
            ];
            $cache['admin_panel_hiddify'] = $rxPanelReplyCommon + [
                '🔗 دامنه Subscription' => 'primary',
                "🔗 uuid admin"     => 'primary',
            ];
            $cache['admin_panel_athmarzban'] = [
                "🛠️ ساخت کانفیگ دستی" => 'primary',
                "🛰️ مدیریت Nodeها"     => 'success',
            ];
            $cache['admin_panel_athx_ui'] = [
                "🛠️ ساخت کانفیگ دستی" => 'primary',
            ];
            // Stock management keyboard (📦 خزانه شبکه ملی) — coloured per-action.
            $cache['admin_panel_stock_manage'] = [
                "➕ افزودن انبار مدنظر"        => 'success',
                "📥 ورود گروهی به خزانه"   => 'success',
                "➕ ثبت کانفیگ در خزانه"   => 'success',
                "✏️ ویرایش خزانه"             => 'primary',
                "🗑️ حذف کانفیگ خزانه"         => 'danger',
                "🧹 حذف کامل خزانه"           => 'danger',
                "📊 گزارش موجودی انبار"        => 'primary',
                "🔄 همگام‌سازی پلن‌ها انبار" => 'primary',
                "🚨 مسیر اضطراری"              => 'danger',
                "🌐 وضعیت شبکه داخلی"            => 'success',
                "↩️ برگشت به انبار"           => 'primary',
                "🧹 حذف کانفیگ با شناسه"        => 'danger',
                "📋 مرور موجودی کانفیگ"      => 'primary',
                "🧹 پاک‌سازی کانفیگ‌های فعال خزانه" => 'danger',
            ];
        }
        if ($section === null) {
            return $cache;
        }
        return isset($cache[$section]) && is_array($cache[$section]) ? $cache[$section] : [];
    }
}

if (!function_exists('rx_kb_guess_style_from_text')) {
    /**
     * Smart heuristic that picks a button style by inspecting the button's
     * visible text + leading emoji.
     *
     *  - success (green)  →  ✅/✔/🟢/☑/➕/👍   OR keywords like
     *                       تایید / موفق / ذخیره / افزایش / افزودن / ساخت /
     *                       ایجاد / فعال / روشن / می‌پذیرم / شروع / تمدید /
     *                       شارژ / خرید / پرداخت کردم / انجام دادم …
     *
     *  - danger  (red)    →  ❌/✖/🚫/🗑/⛔/🔴/🔙/◀/⬅   OR keywords like
     *                       ناموفق / حذف / لغو / انصراف / مسدود / بستن /
     *                       خاموش / متوقف / خروج / جعلی / بازگشت …
     *
     *  - primary (blue)   →  🔵/🛡/📊/⚙/🔧/ℹ/📋/📈/📉/⏱/⏰   OR keywords like
     *                       تنظیم / مدیریت / آمار / گزارش / اطلاعات /
     *                       جستجو / مشاهده / نمایش / لیست / وضعیت / ویرایش
     *
     *  - null              →  nothing matched (button stays uncoloured).
     *
     * Priority: success → danger → primary → null. First match wins so a
     * button that contains both ❌ and a success word will pick success
     * first (which is intentional — explicit ✅ on a button usually
     * overrides everything else).
     *
     * Used as a fallback when the user has not explicitly chosen a colour
     * in the web panel (i.e. picked "default") or when the button isn't
     * mapped through misc/features/prefix lookups.
     */
    function rx_kb_guess_style_from_text(string $text): ?string {
        $t = trim($text);
        if ($t === '') return null;

        static $rules = null;
        if ($rules === null) {
            $rules = [
                'success' => [

                    '✅', '✔', '🟢', '☑', '🆗', '👍', '➕', '🎉', '🆕', '🎁', '💎',

                    'تایید', 'تأیید', 'موفق', 'ذخیره',
                    'افزایش', 'افزودن', 'افزود', 'اضافه',
                    'ساخت', 'ساختن', 'ایجاد',
                    'فعال‌سازی', 'فعال سازی', 'فعال‌',
                    'روشن',
                    'می‌پذیرم', 'می پذیرم', 'بپذیر', 'پذیرفت',
                    'شروع',
                    'تمدید',
                    'شارژ',
                    'خرید',
                    'پرداخت کردم', 'پرداخت و',
                    'انجام دادم',
                    'دریافت هدیه', 'دریافت سرویس',
                    'ثبت کد', 'ثبت و',
                    'تایید پرداخت', 'تایید و',
                    'کش‌بک', 'کش بک',
                ],
                'danger' => [

                    '❌', '✖', '🔴', '🚫', '🗑', '⛔', '🛑', '⚠', '🔙', '◀', '⬅', '⬇',

                    'ناموفق',
                    'حذف',
                    'لغو',
                    'انصراف',
                    'مسدود', 'بلاک',
                    'بستن',
                    'خاموش',
                    'متوقف', 'توقف', 'پایان',
                    'خروج', 'اخراج',
                    'جعلی',
                    'بازگشت',
                    'کاهش',
                    'اختلال',
                    'خطا',
                    'هشدار',
                    'منقضی',
                    'محدود',
                ],
                'primary' => [

                    '🔵', '🛡', '📊', '⚙', '🔧', 'ℹ', '📋', '📈', '📉',
                    '⏱', '⏰', '⏳', '🔍', '👁', '🔗', '📡', '📂', '📁', '📑',
                    '📌', '📨', '📥', '📤', '📝', '🖥', '🌐', '🧪', '🎨', '🔄',
                    '♻', '🆔', '🗂', '🏷', '🌍', '🚚', '🎛',

                    'تنظیم', 'تنظیمات',
                    'مدیریت',
                    'آمار',
                    'گزارش',
                    'اطلاعات',
                    'جستجو',
                    'مشاهده', 'نمایش',
                    'لیست',
                    'وضعیت',
                    'ویرایش',
                    'تغییر',
                    'انتخاب',
                    'اعلان', 'اطلاع‌رسانی', 'اطلاع رسانی',
                    'درخواست',
                    'بروزرسانی', 'بروز رسانی',
                    'دریافت',
                    'انتقال',
                    'ارسال',
                    'لینک',
                    'مشخصات',
                    'پنل',
                    'کانال',
                    'راهنما', 'آموزش',
                    'یادداشت',
                    'دسته‌بندی', 'دسته‌بندی',
                    'محصول',
                    'موقعیت', 'لوکیشن',
                    'اشتراک',
                    'احراز هویت',
                    'کیف‌پول', 'کیف پول',
                ],
            ];
        }

        foreach ($rules as $style => $kws) {
            foreach ($kws as $kw) {
                if (mb_strpos($t, $kw) !== false) return $style;
            }
        }
        return null;
    }
}

if (!function_exists('rx_kb_use_defaults')) {
    /**
     * Whether the bot should merge its built-in default keyboard colours into
     * the user's panel selection.
     *
     * Stored as the special key `_use_defaults` inside the
     * `setting.keyboard_styles_all` JSON column. Default is TRUE (preserves
     * existing behaviour for installs that never touched this).
     *
     * Cached per-request to avoid repeated DB hits.
     */
    function rx_kb_use_defaults(): bool {
        static $cached = null;
        if ($cached !== null) return $cached;
        $cached = true;
        try {
            global $pdo;
            if (isset($pdo) && $pdo instanceof PDO) {
                $stmt = $pdo->query("SELECT keyboard_styles_all FROM setting LIMIT 1");
                $row  = $stmt ? $stmt->fetch(PDO::FETCH_ASSOC) : null;
                if ($row && !empty($row['keyboard_styles_all'])) {
                    $all = json_decode($row['keyboard_styles_all'], true);
                    if (is_array($all) && array_key_exists('_use_defaults', $all)) {
                        $cached = (bool) $all['_use_defaults'];
                    }
                }
            }
        } catch (Throwable $e) { /* keep default true */ }
        return $cached;
    }
}

if (!function_exists('rx_mergeKeyboardSectionDefaults')) {



    function rx_mergeKeyboardSectionDefaults(array $userStyles, $section) {
        if (!rx_kb_use_defaults()) {
            return $userStyles;
        }
        $defaults = rx_getKeyboardDefaultStyles($section);
        if (empty($defaults)) {
            return $userStyles;
        }

        return $userStyles + $defaults;
    }
}

function sendmessage($chat_id,$text,$keyboard,$parse_mode,$bot_token = null){
    if(intval($chat_id) == 0)return ['ok' => false];
    return telegram('sendmessage',[
        'chat_id' => $chat_id,
        'text' => $text,
        'reply_markup' => $keyboard,
        'parse_mode' => $parse_mode,
        ],$bot_token);
}


if (!function_exists('rx_cron_style')) {
    /**
     * Resolve the style configured under panel section "cron_notifications"
     * for a given key (e.g. cron_buy_service, cron_start_bot, …).
     *
     * Returns the style name (success/primary/danger/...) or null when the
     * user picked "default" (no styling).
     *
     * Use this when building inline keyboards in cronbot/* where the
     * callback_data clashes with non-cron menus, so processKeyboardStyles
     * cannot disambiguate by callback_data alone.
     */
    function rx_cron_style(string $key): ?string {
        static $cronStyles = null;
        if ($cronStyles === null) {
            $cronStyles = [];
            try {
                global $pdo;
                if (isset($pdo) && $pdo instanceof PDO) {
                    $stmt = $pdo->query("SELECT keyboard_styles_all FROM setting LIMIT 1");
                    $row  = $stmt ? $stmt->fetch(PDO::FETCH_ASSOC) : null;
                    if ($row && !empty($row['keyboard_styles_all'])) {
                        $all = json_decode($row['keyboard_styles_all'], true);
                        if (is_array($all) && !empty($all['cron_notifications']) && is_array($all['cron_notifications'])) {
                            $cronStyles = $all['cron_notifications'];
                        }
                    }
                }
            } catch (Throwable $e) { /* ignore — defaults below */ }
            if (function_exists('rx_getKeyboardDefaultStyles') && (!function_exists('rx_kb_use_defaults') || rx_kb_use_defaults())) {
                $cronStyles = $cronStyles + rx_getKeyboardDefaultStyles('cron_notifications');
            }
        }
        $v = $cronStyles[$key] ?? null;
        if (!is_string($v) || $v === '' || $v === 'default') return null;
        return $v;
    }
}

if (!function_exists('rx_cron_btn')) {
    /**
     * Apply the panel-configured cron_notifications style to a button array
     * (pass-through if no style configured). Idempotent.
     *
     * Example:
     *   rx_cron_btn('cron_buy_service', ['text' => 'خرید', 'callback_data' => 'buy'])
     */
    function rx_cron_btn(string $cronKey, array $btn): array {
        if (isset($btn['style'])) return $btn;
        $st = rx_cron_style($cronKey);
        if ($st !== null) $btn['style'] = $st;
        return $btn;
    }
}
function prepareTelegramInputFile($input)
{
    if ($input instanceof CURLFile) {
        return $input;
    }

    if (is_string($input)) {
        if (preg_match('/^https?:\/\//i', $input)) {
            return $input;
        }

        $realPath = realpath($input);
        if ($realPath !== false && is_file($realPath) && is_readable($realPath)) {
            return new CURLFile($realPath);
        }

        error_log(sprintf('Telegram document path is not readable: %s', $input));
        return null;
    }

    error_log('Unsupported Telegram input file type: ' . gettype($input));
    return null;
}

function sendDocument($chat_id, $documentPath, $caption)
{
    $document = prepareTelegramInputFile($documentPath);
    if ($document === null) {
        return [
            'ok' => false,
            'description' => 'Document could not be prepared for Telegram upload.'
        ];
    }

    return telegram('sendDocument', [
        'chat_id' => $chat_id,
        'document' => $document,
        'caption' => $caption,
    ]);
}

function forwardMessage($chat_id,$message_id,$chat_id_user){
    return telegram('forwardMessage',[
        'from_chat_id'=> $chat_id,
        'message_id'=> $message_id,
        'chat_id'=> $chat_id_user,
    ]);
}
function sendphoto($chat_id,$photoid,$caption){
    telegram('sendphoto',[
        'chat_id' => $chat_id,
        'photo'=> $photoid,
        'caption'=> $caption,
    ]);
}
function sendvideo($chat_id,$videoid,$caption){
    telegram('sendvideo',[
        'chat_id' => $chat_id,
        'video'=> $videoid,
        'caption'=> $caption,
    ]);
}
function senddocumentsid($chat_id,$documentid,$caption){
    telegram('sendDocument',[
        'chat_id' => $chat_id,
        'document'=> $documentid,
        'caption'=> $caption,
    ]);
}
function Editmessagetext($chat_id, $message_id, $text, $keyboard,$parse_mode = 'HTML'){
    $message_id = (int) $message_id;
    if ($message_id <= 0) {
        if (function_exists('sendmessage')) {
            return sendmessage($chat_id, $text, $keyboard, strtolower($parse_mode));
        }
        return ['ok' => false, 'description' => 'message_id missing'];
    }
    // Premium emoji and keyboard styling are centralized in telegram() so
    // custom-emoji rejection can transparently fall back to ordinary emoji.
    global $update;
    $__callbackMsg = (isset($update['callback_query']['message']) && is_array($update['callback_query']['message']))
        ? $update['callback_query']['message'] : null;
    $__callbackMsgId = $__callbackMsg ? (int)($__callbackMsg['message_id'] ?? 0) : 0;
    $__isPhotoMsg = $__callbackMsg && (
        !empty($__callbackMsg['photo'])
        || !empty($__callbackMsg['video'])
        || !empty($__callbackMsg['animation'])
        || !empty($__callbackMsg['document'])
    );
    if ($__isPhotoMsg && $__callbackMsgId === (int)$message_id && function_exists('sendmessage')) {
        if (function_exists('deletemessage')) {
            @deletemessage($chat_id, $message_id);
        }
        return sendmessage($chat_id, $text, $keyboard, strtolower($parse_mode));
    }

    $result = telegram('editmessagetext', [
        'chat_id' => $chat_id,
        'message_id' => $message_id,
        'text' => $text,
        'reply_markup' => $keyboard,
        'parse_mode' => $parse_mode,
    ]);


    if (is_array($result) && isset($result['ok']) && $result['ok'] === false) {
        $desc = strtolower((string)($result['description'] ?? ''));


        $needsFallback = (
            strpos($desc, 'no text in the message') !== false
            || strpos($desc, "message can't be edited") !== false
            || strpos($desc, 'message to edit not found') !== false
        );
        if ($needsFallback && function_exists('sendmessage')) {
            
            
            if (strpos($desc, 'no text in the message') !== false && function_exists('deletemessage')) {
                @deletemessage($chat_id, $message_id);
            }
            return sendmessage($chat_id, $text, $keyboard, strtolower($parse_mode));
        }
    }
    return $result;
}
 function deletemessage($chat_id, $message_id){
  $message_id = (int) $message_id;
  if ($message_id <= 0) return ['ok' => false, 'description' => 'message_id missing'];
  return telegram('deletemessage', [
'chat_id' => $chat_id,
'message_id' => $message_id,
]);
 }
function getFileddire($photoid){
  return telegram('getFile', [
'file_id' => $photoid,
]);
 }
function pinmessage($from_id,$message_id){
  return telegram('pinChatMessage', [
'chat_id' => $from_id,
'message_id' => $message_id,
]);
 }
 function unpinmessage($from_id){
  return telegram('unpinAllChatMessages', [
'chat_id' => $from_id,
]);
 }
  function answerInlineQuery($inline_query_id,$results){
  return telegram('answerInlineQuery', [
      "inline_query_id" => $inline_query_id,
        "results" => json_encode($results)
]);
 }
function convertPersianNumbersToEnglish($string) {
    $persian_numbers = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
    $english_numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];

    return str_replace($persian_numbers, $english_numbers, $string);
}

function isDuplicateUpdate($updateId)
{
    static $memoryCache = [];

    if (!is_numeric($updateId) || $updateId <= 0) {
        return false;
    }

    $now = time();
    $timeToLive = 120;

    foreach ($memoryCache as $id => $timestamp) {
        if (!is_numeric($timestamp) || ($now - (int)$timestamp) > $timeToLive) {
            unset($memoryCache[$id]);
        }
    }

    if (isset($memoryCache[$updateId])) {
        return true;
    }

    $isDuplicate = false;

    $cacheDir = __DIR__ . '/storage/cache';
    $cacheDirReady = true;

    if (!is_dir($cacheDir)) {
        $cacheDirReady = @mkdir($cacheDir, 0775, true) || is_dir($cacheDir);
    }

    if ($cacheDirReady) {
        $cacheFile = $cacheDir . '/recent_updates.json';
        $handle = @fopen($cacheFile, 'c+');

        if ($handle !== false) {
            try {
                if (flock($handle, LOCK_EX)) {
                    rewind($handle);
                    $contents = stream_get_contents($handle);
                    $recentUpdates = $contents ? json_decode($contents, true) : [];
                    if (!is_array($recentUpdates)) {
                        $recentUpdates = [];
                    }

                    foreach ($recentUpdates as $id => $timestamp) {
                        if (!is_numeric($timestamp) || ($now - (int)$timestamp) > $timeToLive) {
                            unset($recentUpdates[$id]);
                        }
                    }

                    if (array_key_exists((string) $updateId, $recentUpdates) || array_key_exists($updateId, $recentUpdates)) {
                        $isDuplicate = true;
                    } else {
                        $recentUpdates[(string) $updateId] = $now;

                        if (count($recentUpdates) > 200) {
                            asort($recentUpdates);
                            $recentUpdates = array_slice($recentUpdates, -200, null, true);
                        }

                        $encoded = json_encode($recentUpdates);
                        if ($encoded !== false) {
                            rewind($handle);
                            ftruncate($handle, 0);
                            fwrite($handle, $encoded);
                            fflush($handle);
                        }
                    }

                    flock($handle, LOCK_UN);
                }
            } catch (Throwable $e) {
                try {
                    flock($handle, LOCK_UN);
                } catch (Throwable $ignored) {
                }
            }

            fclose($handle);
        }
    }

    if (!$isDuplicate) {
        $pdo = $GLOBALS['pdo'] ?? null;
        if ($pdo instanceof PDO) {
            static $dbInitialised = false;
            static $lastCleanup = 0;

            try {
                if (!$dbInitialised) {
                    $pdo->exec('CREATE TABLE IF NOT EXISTS processed_updates (
                        update_id BIGINT UNSIGNED PRIMARY KEY,
                        processed_at INT UNSIGNED NOT NULL
                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');
                    $dbInitialised = true;
                }

                if ($lastCleanup === 0 || ($now - $lastCleanup) > 60) {
                    $stmtCleanup = $pdo->prepare('DELETE FROM processed_updates WHERE processed_at < :threshold');
                    $stmtCleanup->execute([':threshold' => $now - $timeToLive]);
                    $lastCleanup = $now;
                }

                $stmtInsert = $pdo->prepare('INSERT IGNORE INTO processed_updates (update_id, processed_at) VALUES (:id, :ts)');
                $stmtInsert->execute([':id' => $updateId, ':ts' => $now]);
                if ($stmtInsert->rowCount() === 0) {
                    $isDuplicate = true;
                }
            } catch (Throwable $e) {
                error_log('Duplicate update tracker database fallback error: ' . $e->getMessage());
            }
        }
    }

    if (!$isDuplicate) {
        $memoryCache[$updateId] = $now;
    }

    return $isDuplicate;
}


$rx_request_method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? ''));


if ($rx_request_method !== 'POST') {
    return;
}


if (defined('AYCORE_SKIP_BOTAPI_ROUTER') && AYCORE_SKIP_BOTAPI_ROUTER) {
    return;
}


$rx_raw_body = @file_get_contents('php://input');
if (!is_string($rx_raw_body) || $rx_raw_body === '') {
    return;
}


$rx_update_probe = json_decode($rx_raw_body, true);
if (!is_array($rx_update_probe)) {
    return;
}


if (!isset($rx_update_probe['update_id'])) {
    return;
}


$update = $rx_update_probe;
$update_id = $update['update_id'] ?? 0;
if (isDuplicateUpdate($update_id)) {
    if (!headers_sent()) {
        http_response_code(200);
    }
    exit;
}


try {
    $rxSecretRow = $pdo->query("SELECT webhook_secret_token FROM setting LIMIT 1")->fetch(PDO::FETCH_ASSOC);
    $rxStoredSecret = is_array($rxSecretRow) ? (string)($rxSecretRow['webhook_secret_token'] ?? '') : '';
} catch (\Throwable $rxSecretQueryErr) {
    $rxStoredSecret = '';
}
if ($rxStoredSecret !== '') {
    $rxIncomingSecret = isset($_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'])
        ? (string) $_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN']
        : '';
    if (!hash_equals($rxStoredSecret, $rxIncomingSecret)) {
        $rxRejectIp = (string) ($_SERVER['REMOTE_ADDR'] ?? 'unknown');
        $rxRejectIpKey = preg_replace('/[^A-Fa-f0-9.:]/', '_', $rxRejectIp);
        $rxRejectMarker = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'botapi_reject_' . substr(md5($rxRejectIpKey), 0, 16) . '.flag';
        $rxShouldLog = true;
        if (is_file($rxRejectMarker) && (time() - (int) @filemtime($rxRejectMarker)) < 3600) {
            $rxShouldLog = false;
        }
        if ($rxShouldLog) {
            error_log('[botapi] Rejected webhook: bad or missing secret_token from ' . $rxRejectIp);
            @touch($rxRejectMarker);
        }
        if (!headers_sent()) {
            http_response_code(200);
        }
        exit;
    }
}

$from_id = $update['message']['from']['id'] ?? $update['callback_query']['from']['id'] ?? $update["inline_query"]['from']['id'] ?? 0;
$time_message = $update['message']['date'] ?? $update['callback_query']['date'] ?? $update["inline_query"]['date'] ?? 0;
$is_bot = $update['message']['from']['is_bot'] ?? false;
$chat_member = $update['chat_member'] ?? null;
$language_code = strtolower($update['message']['from']['language_code'] ?? $update['callback_query']['from']['language_code'] ?? "fa");
$Chat_type = $update["message"]["chat"]["type"] ?? $update['callback_query']['message']['chat']['type'] ?? '';
$text = $update["message"]["text"]  ?? '';
if(isset($update['pre_checkout_query'])){
    $Chat_type = "private";
    $from_id = $update['pre_checkout_query']['from']['id'];
}


$rx_assertNumericId = static function ($value): int {
    if (!is_numeric($value)) {
        return 0;
    }
    if ((string)(int) $value !== (string) $value) {
        return 0;
    }
    return (int) $value;
};
if (!is_numeric($from_id) || (string)(int) $from_id !== (string) $from_id) {
    error_log('[botapi] Rejected non-numeric from_id from webhook payload');
    if (!headers_sent()) {
        http_response_code(200);
    }
    exit;
}
$from_id = (int) $from_id;
if (function_exists('itz_event')) {
    $itzUpdateType = isset($update['callback_query']) ? 'callback' : (isset($update['message']) ? 'message' : (isset($update['pre_checkout_query']) ? 'pre_checkout' : 'other'));
    itz_event('telegram.update.received', ['update_id'=>(int)$update_id,'type'=>$itzUpdateType], 'user', (string)$from_id);
    itz_metric('telegram.update.count',1,['type'=>$itzUpdateType]);
}
$text =convertPersianNumbersToEnglish($text);

// AY Core V5 compatibility layer:
// Reply-keyboard labels were redesigned for the independent edition. Existing
// chats may still have an old keyboard cached by Telegram, so accept those
// labels and translate them to the new canonical wording before routing.
if (!function_exists('aycore_normalize_legacy_button_text')) {
    function aycore_normalize_legacy_button_text($value) {
        if (!is_string($value) || $value === '') return $value;
        static $aliases = [
            '⚙️ تنظیمات' => '🧩 مرکز تنظیمات',
            '⚙️ تنظیمات عمومی' => '🧠 تنظیمات هسته',
            '🏠 بازگشت به منوی مدیریت' => '🏠 بازگشت به داشبورد مدیریت',
            '🏠 بازگشت به خانه' => '🏠 بازگشت به خانه',
            '▶️ یک مرحله قبل' => '↩️ یک مرحله قبل',
            '🔙 بازگشت' => '↩️ برگشت',
            '⬅️ بازگشت به فروشگاه' => '↩️ بازگشت به فروشگاه',
            '🏠 بازگشت به لیست سرویس ها' => '🧾 بازگشت به اشتراک‌ها',
            '👤 مدیریت کاربر' => '👤 میز کار مرکز کاربران',
            '🏬 سیاست فروشگاه' => '🛍️ استودیوی فروش',
            '🤙 بخش پشتیبانی' => '🎧 مرکز پشتیبانی',
            '👤 آیدی پشتیبانی' => '🎧 شناسه پشتیبانی',
            '🗂 مدیریت دسته‌بندی' => '🗂️ مدیریت دسته‌ها',
            '🛍 مدیریت پلن‌ها' => '📦 مدیریت پلن‌ها',
            '🛍خرید اشتراک' => '🚀 خرید اشتراک',
            '💰 پرداخت و دریافت سرویس' => '⚡ پرداخت و فعال‌سازی',
            '⚙️ تنظیم سرویس ها' => '🧰 مدیریت اشتراک‌ها',
            '🎛️ تنظیمات سرویس' => '🎛️ پروفایل سرویس',
            '🔋 روش تمدید سرویس' => '♻️ سیاست تمدید',
            '💡 روش ساخت نام کاربری' => '🪪 الگوی نام کاربری',
            '⏳ زمان سرویس تست' => '⏱️ مدت سرویس آزمایشی',
            '💾 حجم اکانت تست' => '🧪 حجم سرویس آزمایشی',
            '⚙️ قیمت حجم سرویس دلخواه' => '🧮 تعرفه حجم سفارشی',
            '➕ قیمت حجم اضافه' => '➕ تعرفه حجم اضافه',
            '⏳ قیمت زمان اضافه' => '⏳ تعرفه زمان اضافه',
            '🔗 ویرایش آدرس پنل' => '🔗 تغییر Endpoint پنل',
            '🚨 محدودیت ساخت اکانت' => '🛡️ سقف ساخت اکانت',
            '📯 تنظیمات کانال' => '📡 تنظیمات عضویت کانال',
            '🗂 خرید انبوه' => '🛒 خرید چندتایی',
            '🔧 مدیریت سرویس' => '🛠️ مدیریت اشتراک',
            '✅ تایید و افزایش اعتبار' => '✅ تأیید و افزایش اعتبار',
        ];
        return $aliases[$value] ?? $value;
    }
}
$text = aycore_normalize_legacy_button_text($text);

$text_inline = $update["callback_query"]["message"]['text'] ?? '';
$message_id = $update["message"]["message_id"] ?? $update["callback_query"]["message"]["message_id"] ?? 0;
$message_id = $rx_assertNumericId($message_id);
$time_message = $update["message"]["date"] ?? $update["callback_query"]["date"] ?? 0;
$photo = $update["message"]["photo"] ?? 0;
$document = $update["message"]["document"] ?? 0;
$fileid = $update["message"]["document"]["file_id"] ?? 0;
$photoid = $photo ? end($photo)["file_id"] : '';
$caption = $update["message"]["caption"] ?? '';
$video = $update["message"]["video"] ?? 0;
$videoid = $video ? $video["file_id"] : 0;
$forward_from_id = $update["message"]["reply_to_message"]["forward_from"]["id"] ?? 0;
$forward_from_id = $rx_assertNumericId($forward_from_id);
$datain = $update["callback_query"]["data"] ?? '';
$last_name = $update['message']['from']['last_name']  ?? $update["callback_query"]["from"]["last_name"] ?? $update["inline_query"]['from']['last_name'] ?? '';
$first_name = $update['message']['from']['first_name']  ?? $update["callback_query"]["from"]["first_name"] ?? $update["inline_query"]['from']['first_name'] ?? '';
$username = $update['message']['from']['username'] ?? $update['callback_query']['from']['username'] ?? $update["callback_query"]["from"]["username"] ?? 'NOT_USERNAME';
$user_phone =$update["message"]["contact"]["phone_number"] ?? 0;
$contact_id = $update["message"]["contact"]["user_id"] ?? 0;
$contact_id = $rx_assertNumericId($contact_id);
$callback_query_id = $update["callback_query"]["id"] ?? 0;


if (!is_string($callback_query_id) && !is_int($callback_query_id)) {
    $callback_query_id = '';
} elseif (!preg_match('/^\d{1,32}$/', (string) $callback_query_id)) {
    $callback_query_id = '';
}
$inline_query_id = $update["inline_query"]["id"] ?? 0;
if (!is_string($inline_query_id) && !is_int($inline_query_id)) {
    $inline_query_id = '';
} elseif (!preg_match('/^\d{1,32}$/', (string) $inline_query_id)) {
    $inline_query_id = '';
}
$query = $update["inline_query"]["query"] ?? 0;
