<?php

namespace Bot;

use GuzzleHttp\Client;

class Bot
{
    private string $token;
    private string $apiUrl;
    private Client $httpClient;
    private Database $db;
    private array $admins;
    private int $ownerId;
    private bool $broadcastMode = false;
    private ?int $lastErrorCode = null;

    public function __construct(string $token, Database $db, array $admins, int $ownerId)
    {
        $this->token = $token;
        $this->apiUrl = "https://api.telegram.org/bot{$token}/";
        $this->httpClient = new Client([
            'base_uri' => $this->apiUrl,
            'timeout'  => 30.0,
            'connect_timeout' => 10.0,
        ]);
        $this->db = $db;
        $this->admins = $admins;
        $this->ownerId = $ownerId;
        
        // Note: setMyCommands is called in set_webhook.php, not here
        // to avoid rate limiting on every request
    }
    
    /**
     * Switch to broadcast mode: shorter timeouts, fewer retries
     */
    public function setBroadcastMode(): void
    {
        $this->broadcastMode = true;
        $this->httpClient = new Client([
            'base_uri' => $this->apiUrl,
            'timeout'  => 10.0,
            'connect_timeout' => 5.0,
        ]);
    }

    private function setMyCommands(): void
    {
        $commands = [
            ['command' => 'start', 'description' => 'Start the bot and open the main menu']
        ];
        
        $this->request('setMyCommands', ['commands' => $commands]);
    }

    public function handleUpdates()
    {
        $input = file_get_contents('php://input');
        $update = json_decode($input, true);

        if (!$update) {
            return;
        }

        // Handle Bot Added to Channel/Group (my_chat_member)
        if (isset($update['my_chat_member'])) {
            $this->handleMyChatMember($update['my_chat_member']);
            return;
        }

        // Handle Chat Join Request
        if (isset($update['chat_join_request'])) {
            $this->handleJoinRequest($update['chat_join_request']);
            return;
        }

        // Handle Messages
        if (isset($update['message'])) {
            $this->handleMessage($update['message']);
            return;
        }

        if (isset($update['callback_query'])) {
            $this->handleCallback($update['callback_query']);
            return;
        }
        
        // Handle Chat Member Updates (Greetings/Farewells)
        if (isset($update['chat_member'])) {
            $this->handleChatMember($update['chat_member']);
            return;
        }
    }

    private function handleMyChatMember(array $update)
    {
        $chat = $update['chat'];
        $newStatus = $update['new_chat_member']['status'];
        $user = $update['from'];

        // If bot is added as administrator
        if ($newStatus === 'administrator') {
            $this->db->addChannel($chat['id'], $user['id'], $chat['title'], $chat['type'] ?? 'channel');
            // Notify user
            try {
                $safeTitle = htmlspecialchars($chat['title'] ?? 'Unknown', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
                $this->sendMessage($user['id'], "✅ Channel <b>{$safeTitle}</b> added successfully! You can now manage it from the Main Menu.");
            } catch (\Exception $e) {
                // User might not have started the bot yet
            }
        }
    }

    private function handleJoinRequest(array $request)
    {
        $chatId = $request['chat']['id'];
        $userId = $request['from']['id'];
        
        // Save user info for greeting/farewell functionality
        $firstName = $request['from']['first_name'] ?? 'User';
        $username = $request['from']['username'] ?? null;
        $this->db->saveUser($userId, $firstName, $username);
        
        // Mark as started: Telegram grants the bot messaging permission when a
        // chat_join_request is received, so include this user in broadcasts.
        // If the permission later expires or the user blocks the bot, the
        // broadcast processor will automatically mark them as not-started.
        $this->db->markUserStarted($userId);
        
        // Log request
        $this->db->logJoinRequest($chatId, $userId);
        
        // Send greeting instantly when user sends join request (DM to user)
        // Works for both groups and channels, regardless of settings
        // Lock is set AFTER successful send to allow retry from approval paths if DM fails
        // Send greeting instantly when user sends join request (DM to user)
        // FORCE used to allow multiple greetings if user clicks multiple times
        // Lock remains active to prevent DOUBLE greeting on approval (handleChatMember)
        $this->sendGreetingToUser($chatId, $userId, true); // force=true

        // Check Protection Settings
        $filterLang = $this->db->getChannelSetting($chatId, 'filter_languages', false);
        $filterRtl = $this->db->getChannelSetting($chatId, 'filter_rtl', false);
        $filterIdeo = $this->db->getChannelSetting($chatId, 'filter_ideographs', false);

        $lastName = $request['from']['last_name'] ?? '';
        $name = $firstName . ' ' . $lastName;
        
        $reject = false;
        $forceAccept = false;
        $forceReject = false;
        
        // RTL Check
        if ($filterRtl && preg_match('/[\p{Arabic}\p{Hebrew}]/u', $name)) {
            $reject = true;
        }
        
        // Ideograph Check
        if ($filterIdeo && preg_match('/[\p{Han}\p{Hiragana}\p{Katakana}]/u', $name)) {
            $reject = true;
        }

        // Language Filter (Advanced)
        $filterLangCodes = $this->db->getChannelSetting($chatId, 'filter_lang_codes', '');
        if (!empty($filterLangCodes)) {
             $userLang = $request['from']['language_code'] ?? 'null';
             // Normalize to 2 chars usually, but user might restrict specific locale e.g. pt-br. 
             // The simple approach: Match strict first, then try 2-char prefix?
             // Or just simple string match. 
             // Logic:
             // 1. Parse config into $allowed and $blocked.
             // 2. If $blocked has match -> Reject
             // 3. If $allowed not empty -> Must match -> else Reject.
             
             $rules = array_map('trim', explode(',', strtolower($filterLangCodes)));
             $allowed = [];
             $blocked = [];
             
             foreach ($rules as $rule) {
                 if (empty($rule)) continue;
                 if (str_starts_with($rule, '-')) {
                     $blocked[] = mb_substr($rule, 1, null, 'UTF-8');
                 } else {
                     $allowed[] = $rule;
                 }
             }
             
             $userLang = strtolower($userLang);
             // Also consider just the first part 'en' from 'en-us'
             $userLangShort = explode('-', $userLang)[0];
             
             // Check Blocked
             foreach ($blocked as $b) {
                 if ($userLang === $b || $userLangShort === $b) {
                     $reject = true;
                     break;
                 }
             }
             
             // Check Allowed (Only if reject is false yet)
             if (!$reject && !empty($allowed)) {
                 $match = false;
                 foreach ($allowed as $a) {
                     if ($userLang === $a || $userLangShort === $a) {
                         $match = true;
                         break;
                     }
                 }
                 if (!$match) {
                     $reject = true;
                 }
             }
        }

        if ($reject) {
            $this->declineChatJoinRequest($chatId, $userId);
            return;
        }
        
        // Check Invite Link Logic (+ / -)

        // Check Channel Settings
        $acceptEnabled = $this->db->getChannelSetting($chatId, 'app_accept_enabled', true);
        
        // Logic: if forceAccept, we bypass "acceptEnabled == false".
        if (!$acceptEnabled && !$forceAccept) {
            return;
        }
        
        // If forceAccept is true, we should probably skip Captcha/Interact too? 
        // "based on this, the bot will accept [..] regardless of the Applications accept setting"
        // Usually implies immediate acceptance.
        if ($forceAccept) {
            $this->approveChatJoinRequest($chatId, $userId, true); // skipWelcome: greeting already sent at line 114
            return;
        }

        // === START BOT FIRST CHECK REMOVED ===
        // User is already saved in DB at start of function.
        // Proceeding to auto-approval without forcing /start
        
        // We still mark them as "started" or at least ensure they are in DB (already done above)
        // If you want to simulate "started" status without /start command:
        // $this->db->markUserStarted($userId); // Optional, if analytics depend on it

        // 1. Accept on Interact?
        $interact = $this->db->getChannelSetting($chatId, 'accept_interact', false); // Restore variable
        if ($interact) {
            $this->sendInteractChallenge($userId, $chatId, $request['chat']['title']);
            return;
        }
        
        // 2. CAPTCHA?
        $captcha = $this->db->getChannelSetting($chatId, 'use_captcha', false); // Restore variable
        if ($captcha) {
            $this->sendCaptchaChallenge($userId, $chatId, $request['chat']['title']);
            return;
        }

        // 3. Auto Approve (if not blocked by above)
        $autoApprove = $this->db->getChannelSetting($chatId, 'auto_approve', true);
        
        if ($autoApprove) {
            $deferTime = (int)$this->db->getChannelSetting($chatId, 'defer_time', 0); // Restore variable
            if ($deferTime > 0) {
                // Defer approval (Stored in seconds)
                $this->db->deferJoinRequest($chatId, $userId, time() + $deferTime);
            } else {
                $this->approveChatJoinRequest($chatId, $userId, true); // skipWelcome: greeting already sent at line 114
            }
        }
        // No else block needed - force greeting already sent at start
    }

    private function handleMessage(array $message)
    {
        $chatId = $message['chat']['id'];
        $text = $message['text'] ?? '';
        $userId = $message['from']['id'];
        
        // === ESCAPE HATCH: Always clear state on /start or /cancel ===
        if ($text === '/start' || $text === '/cancel') {
             $this->db->clearUserState($userId);
        }
        
        // === SERVICE MESSAGES (must be processed first, before any other checks) ===
        // These are group/channel events, not user interactions
        // left_chat_member farewell is handled by handleChatMember (single source)
        // Do NOT handle it here to avoid double farewells
        if (isset($message['left_chat_member'])) {
            return;
        }
        
        // Save user and mark as started (enables broadcasts)
        $this->db->saveUser($userId, $message['from']['first_name'], $message['from']['username'] ?? null);
        $this->db->markUserStarted($userId);
        
        // === HANDLE /start join_CHATID for pending join requests ===
        if (preg_match('/^\/start join_(-?\d+)$/', $text, $matches)) {
            $targetChatId = (int)$matches[1];
            $this->processPendingJoinRequest($userId, $targetChatId, $chatId);
            return;
        }
        
        // === HANDLE /start with any pending requests ===
        if ($text === '/start') {
            $pendingRequests = $this->db->getPendingJoinRequestsForUser($userId);
            if (!empty($pendingRequests)) {
                foreach ($pendingRequests as $req) {
                    $this->processPendingJoinRequest($userId, $req['chat_id'], $chatId);
                }
                return;
            }
        }
        
        // === BAN CHECK ===
        if ($this->db->isUserBanned($userId)) {
            $this->sendMessage($chatId, "🚫 You are banned from using this bot.");
            return;
        }
        
        // === LINK FILTER (Groups only) ===
        $chatType = $message['chat']['type'] ?? 'private';
        if (in_array($chatType, ['group', 'supergroup'])) {
            $filterLinksSetting = $this->db->getChannelSetting($chatId, 'filter_links', '0');
            $filterLinks = ($filterLinksSetting === '1' || $filterLinksSetting === true || $filterLinksSetting === 1);
            
            if ($filterLinks) {
                // Check if user is admin in the GROUP (not bot admin)
                $memberStatus = $this->getChatMemberStatus($chatId, $userId);
                $isGroupAdmin = in_array($memberStatus, ['administrator', 'creator']);
                
                if (!$isGroupAdmin) {
                    $hasLink = false;
                    
                    // Check for URL entities (official Telegram detection)
                    if (isset($message['entities'])) {
                        foreach ($message['entities'] as $entity) {
                            if (in_array($entity['type'], ['url', 'text_link'])) {
                                $hasLink = true;
                                break;
                            }
                        }
                    }
                    
                    // Also check caption entities for media messages
                    if (!$hasLink && isset($message['caption_entities'])) {
                        foreach ($message['caption_entities'] as $entity) {
                            if (in_array($entity['type'], ['url', 'text_link'])) {
                                $hasLink = true;
                                break;
                            }
                        }
                    }
                    
                    // Fallback: regex check for URLs not detected by Telegram
                    if (!$hasLink && isset($message['text'])) {
                        if (preg_match('~(https?://|www\.|@[a-zA-Z0-9_]{5,})[^\s]+~i', $message['text'])) {
                            $hasLink = true;
                        }
                    }
                    if (!$hasLink && isset($message['caption'])) {
                        if (preg_match('~(https?://|www\.|@[a-zA-Z0-9_]{5,})[^\s]+~i', $message['caption'])) {
                            $hasLink = true;
                        }
                    }
                    
                    // Delete message if link detected
                    if ($hasLink) {
                        try {
                            $this->deleteMessage($chatId, $message['message_id']);
                        } catch (\Exception $e) {
                            error_log("Failed to delete linked message: " . $e->getMessage());
                        }
                        return;
                    }
                }
            }
        }
        
        // === SYMBOL FILTER (RTL/Ideographs) ===
        if (in_array($chatType, ['group', 'supergroup', 'channel'])) {
            $filterRtl = $this->db->getChannelSetting($chatId, 'filter_rtl', false);
            $filterIdeo = $this->db->getChannelSetting($chatId, 'filter_ideographs', false);
            
            $checkText = $text;
            if (isset($message['caption'])) $checkText .= ' ' . $message['caption'];
            $checkText .= ' ' . ($message['from']['first_name'] ?? '') . ' ' . ($message['from']['last_name'] ?? '');
            
            $delete = false;
            // RTL Check
            if ($filterRtl && preg_match('/[\p{Arabic}\p{Hebrew}]/u', $checkText)) {
                $delete = true;
            }
            
            // Ideograph Check
            if ($filterIdeo && preg_match('/[\p{Han}\p{Hiragana}\p{Katakana}]/u', $checkText)) {
                $delete = true;
            }
            
            if ($delete && !$this->isAdmin($userId) && $this->getChatMemberStatus($chatId, $userId) !== 'administrator') {
                try {
                    $this->deleteMessage($chatId, $message['message_id']);
                } catch (\Exception $e) {}
                return;
            }
        }
        
        // Check for User State (Input)
        $state = $this->db->getUserState($userId);
        
        // === CHANNEL FORWARD HANDLER ===
        if ($state && $state['state'] === 'AWAITING_CHANNEL_FORWARD') {
            // Support both old API (forward_from_chat) and new API 7.0+ (forward_origin)
            $forwardChat = null;
            
            if (isset($message['forward_from_chat'])) {
                // Old API: forward_from_chat (deprecated but some clients still send it)
                $forwardChat = $message['forward_from_chat'];
            } elseif (isset($message['forward_origin'])) {
                // New API 7.0+: forward_origin with type 'channel'
                $origin = $message['forward_origin'];
                if (($origin['type'] ?? '') === 'channel' && isset($origin['chat'])) {
                    $forwardChat = $origin['chat'];
                } elseif (($origin['type'] ?? '') === 'chat' && isset($origin['sender_chat'])) {
                    $forwardChat = $origin['sender_chat'];
                }
            }
            
            if ($forwardChat) {
                $forwardChatType = $forwardChat['type'] ?? 'unknown';
                
                if ($forwardChatType === 'channel' || $forwardChatType === 'supergroup') {
                    $channelId = $forwardChat['id'];
                    $title = $forwardChat['title'] ?? 'Unknown';
                    $safeTitle = htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
                    
                    // Check if bot is admin in this channel
                    try {
                        $botInfo = $this->request('getMe');
                        $botId = $botInfo['result']['id'] ?? 0;
                        $memberInfo = $this->request('getChatMember', [
                            'chat_id' => $channelId,
                            'user_id' => $botId
                        ]);
                        
                        $botStatus = $memberInfo['result']['status'] ?? 'left';
                        
                        if (in_array($botStatus, ['administrator', 'creator'])) {
                            // Add channel to database
                            $this->db->addChannel($channelId, $userId, $title);
                            $this->db->clearUserState($userId);
                            $this->sendMessage($chatId, "✅ Channel <b>$safeTitle</b> added successfully!\n\nGo to 'My Channels' to manage it.");
                            $this->sendMainMenu($chatId);
                        } else {
                            $this->sendMessage($chatId, "❌ <b>Bot is not an admin</b>\n\nPlease add this bot as an administrator to <b>$safeTitle</b> first, then try again.");
                        }
                    } catch (\Exception $e) {
                        $this->sendMessage($chatId, "❌ <b>Could not verify permissions</b>\n\nMake sure the bot is an admin in the channel.");
                        error_log("Channel forward check error: " . $e->getMessage());
                    }
                } else {
                    $this->sendMessage($chatId, "⚠️ Please forward a message from a <b>channel</b>, not a " . $forwardChatType . ".");
                }
            } else {
                $this->sendMessage($chatId, "⚠️ Please forward a message from your channel.\n\n<i>Make sure forwarding is not restricted in the channel settings.</i>\n\n<i>Click Cancel to abort.</i>");
            }
            return;
        }
        
        // === ADMIN STATE HANDLERS ===
        if ($state && $this->isAdmin($userId)) {
            $stateData = $state['data'] ?? [];
            $botMessageId = $stateData['message_id'] ?? null;
            
            // Delete user's input message
            try { $this->deleteMessage($chatId, $message['message_id']); } catch (\Exception $e) {}
            
            // --- Admin Broadcast Handler ---
            if ($state['state'] === 'ADMIN_BROADCAST') {
                $target = $stateData['target'];
                
                // Detect content type
                $contentType = 'text';
                $fileId = null;
                $msgText = $message['text'] ?? $message['caption'] ?? null;
                
                if (isset($message['photo'])) {
                    $contentType = 'photo';
                    $fileId = end($message['photo'])['file_id'];
                } elseif (isset($message['video'])) {
                    $contentType = 'video';
                    $fileId = $message['video']['file_id'];
                } elseif (isset($message['animation'])) {
                    $contentType = 'animation';
                    $fileId = $message['animation']['file_id'];
                } elseif (isset($message['document'])) {
                    $contentType = 'document';
                    $fileId = $message['document']['file_id'];
                } elseif (isset($message['audio'])) {
                    $contentType = 'audio';
                    $fileId = $message['audio']['file_id'];
                } elseif (isset($message['voice'])) {
                    $contentType = 'voice';
                    $fileId = $message['voice']['file_id'];
                } elseif (isset($message['sticker'])) {
                    $contentType = 'sticker';
                    $fileId = $message['sticker']['file_id'];
                }
                
                // Transition to Button Input
                $newStateData = [
                    'target' => $target,
                    'message_id' => $botMessageId,
                    'content_type' => $contentType,
                    'file_id' => $fileId,
                    'text' => $msgText
                ];
                
                $this->db->setUserState($userId, 'ADMIN_BROADCAST_BUTTONS', $newStateData);
                
                $sentPrompt = $this->sendMessage($chatId, "<b>Add Buttons?</b>\n\n" .
                    "Send me the list of URL-buttons.\n" .
                    "Format: <code>Button Text - https://example.com</code>\n" .
                    "Multiple: <code>Btn1 - link1 | Btn2 - link2</code>\n\n" .
                    "Reply with <b>0</b> or click <b>Skip</b> to send without buttons.", [
                    'reply_markup' => ['inline_keyboard' => [[['text' => 'Skip Buttons', 'callback_data' => 'admin_bc_skip_btns']]]]
                ]);
                
                if (isset($sentPrompt['result']['message_id'])) {
                    $newStateData['prompt_message_id'] = $sentPrompt['result']['message_id'];
                    // Update state with new data
                    $this->db->setUserState($userId, 'ADMIN_BROADCAST_BUTTONS', $newStateData);
                }
                return;
            }
            
            // --- Admin Broadcast Buttons ---
            if ($state['state'] === 'ADMIN_BROADCAST_BUTTONS') {
                $buttons = [];
                $input = trim($text);
                
                if ($input !== '0') {
                    // Parse Buttons
                    $rows = explode("\n", $input);
                    foreach ($rows as $row) {
                        $rowBtns = [];
                        $cols = explode('|', $row);
                        foreach ($cols as $col) {
                            $parts = explode('-', $col, 2);
                            if (count($parts) === 2) {
                                $btnText = trim($parts[0]);
                                $btnUrl = trim($parts[1]);
                                // Basic URL validation
                                if (strpos($btnUrl, 'http') === 0) {
                                    $rowBtns[] = ['text' => $btnText, 'url' => $btnUrl];
                                }
                            }
                        }
                        if (!empty($rowBtns)) $buttons[] = $rowBtns;
                    }
                }
                
                $d = $stateData;
                
                // Cleanup: Delete User Input AND Prompt
                try {
                    $this->deleteMessage($chatId, $botMessageId); // Delete user input ("0" or "Link")
                    if (!empty($d['prompt_message_id'])) {
                        $this->deleteMessage($chatId, $d['prompt_message_id']); // Delete "Add Buttons?" prompt
                    }
                } catch (\Throwable $e) {}
                
                $bcId = $this->db->queueBroadcastEnhanced($d['target'], null, $d['text'], $d['content_type'], $d['file_id'], $buttons);

                // Immediately trigger broadcast processor
                $this->triggerBroadcastProcessor();
                
                $workerUrl = rtrim($_ENV['SITE_URL'] ?? '', '/') . '/broadcast_worker.php?key=' . urlencode($_ENV['CRON_SECRET'] ?? '');
                
                $this->sendMessage($chatId, "✅ <b>Broadcast Queued and Started!</b>\n\n" .
                    "├ ID: <code>$bcId</code>\n" .
                    "├ Target: <b>{$d['target']}</b>\n" .
                    "├ Type: <b>{$d['content_type']}</b>\n" .
                    "├ Buttons: <b>" . (empty($buttons) ? 'No' : 'Yes') . "</b>\n\n" .
                    "The broadcast is being processed automatically.\n\n" .
                    "<b>If it gets stuck</b>, open this link in your browser and keep the tab open:\n<code>$workerUrl</code>", [
                    'reply_markup' => ['inline_keyboard' => [
                        [['text' => '📈 Check Progress', 'callback_data' => 'admin_bc_progress_' . $bcId]],
                        [['text' => '🔙 Back', 'callback_data' => 'admin_broadcast']]
                    ]]
                ]);
                return;
            }
            
            // --- Admin Search User ---
            if ($state['state'] === 'ADMIN_SEARCH_USER') {
                $this->db->clearUserState($userId);
                $user = $this->db->searchUser($text);
                if ($user) {
                    $this->sendAdminUserInfo($chatId, $botMessageId, $user['user_id']);
                } else {
                    $this->editMessage($chatId, $botMessageId, "❌ User not found: <code>$text</code>", [
                        'inline_keyboard' => [[['text' => '🔙 Back', 'callback_data' => 'admin_users']]]
                    ]);
                }
                return;
            }
            
            // --- Admin Ban User ---
            if ($state['state'] === 'ADMIN_BAN_USER') {
                $this->db->clearUserState($userId);
                if (!is_numeric($text)) {
                    $this->editMessage($chatId, $botMessageId, "❌ Invalid user ID. Please enter a numeric ID.", [
                        'inline_keyboard' => [[['text' => '🔙 Back', 'callback_data' => 'admin_users']]]
                    ]);
                    return;
                }
                $targetId = (int)$text;
                $this->db->banUser($targetId, $userId, 'Banned by admin');
                $this->editMessage($chatId, $botMessageId, "✅ User <code>$targetId</code> has been banned.", [
                    'inline_keyboard' => [[['text' => '🔙 Back', 'callback_data' => 'admin_users']]]
                ]);
                return;
            }
            
            // --- Admin Unban User ---
            if ($state['state'] === 'ADMIN_UNBAN_USER') {
                $this->db->clearUserState($userId);
                if (!is_numeric($text)) {
                    $this->editMessage($chatId, $botMessageId, "❌ Invalid user ID. Please enter a numeric ID.", [
                        'inline_keyboard' => [[['text' => '🔙 Back', 'callback_data' => 'admin_users']]]
                    ]);
                    return;
                }
                $targetId = (int)$text;
                $this->db->unbanUser($targetId);
                $this->editMessage($chatId, $botMessageId, "✅ User <code>$targetId</code> has been unbanned.", [
                    'inline_keyboard' => [[['text' => '🔙 Back', 'callback_data' => 'admin_users']]]
                ]);
                return;
            }
            
            // --- Admin Force Subscribe Set Channel ---
            if ($state['state'] === 'ADMIN_FS_SETCHANNEL') {
                $this->db->clearUserState($userId);
                if (!is_numeric($text)) {
                    $this->editMessage($chatId, $botMessageId, "❌ Invalid channel ID. Enter a numeric ID like <code>-1001234567890</code>", [
                        'inline_keyboard' => [[['text' => '🔙 Back', 'callback_data' => 'admin_forcesub']]]
                    ]);
                    return;
                }
                
                $fsChatId = trim($text);
                $this->db->setGlobalSetting('force_subscribe_chat_id', $fsChatId);
                
                // Auto-fetch Invite Link
                $inviteLink = null;
                $errorMsg = null;
                
                try {
                    // Try export first (generates new if needed, requires admin)
                    $res = $this->request('exportChatInviteLink', ['chat_id' => $fsChatId]);
                    $inviteLink = $res['result'] ?? null;
                } catch (\Exception $e) {
                    // Fallback: try getChat in case it already has one or public username
                    try {
                        $chatInfo = $this->request('getChat', ['chat_id' => $fsChatId]);
                        if (!empty($chatInfo['result']['username'])) {
                            $inviteLink = "https://t.me/" . $chatInfo['result']['username'];
                        } elseif (!empty($chatInfo['result']['invite_link'])) {
                            $inviteLink = $chatInfo['result']['invite_link'];
                        }
                    } catch (\Exception $ex) {
                        $errorMsg = $ex->getMessage();
                    }
                }
                
                if ($inviteLink) {
                    $this->db->setGlobalSetting('force_subscribe_link', $inviteLink);
                    $msg = "✅ <b>Channel ID Set!</b>\n\n" .
                           "Channel: <code>$fsChatId</code>\n" .
                           "Link fetched: $inviteLink\n\n" .
                           "Force Subscribe is ready to use.";
                } else {
                    // Failed to fetch link
                    $this->db->setGlobalSetting('force_subscribe_link', '');
                    $msg = "⚠️ <b>Channel ID Set, but Link Failed</b>\n\n" .
                           "Channel: <code>$fsChatId</code>\n\n" .
                           "<b>Could not fetch invite link.</b>\n" .
                           "Make sure the bot is an <b>Administrator</b> in that channel with 'Invite Users' permission.\n\n" .
                           "Once promoted, try setting the Channel ID again to fetch the link.";
                }
                
                $this->editMessage($chatId, $botMessageId, $msg, [
                    'inline_keyboard' => [[['text' => '🔙 Back', 'callback_data' => 'admin_forcesub']]]
                ]);
                return;
            }
            

            
            // --- Admin Add Admin ---
            if ($state['state'] === 'ADMIN_ADD_ADMIN') {
                $this->db->clearUserState($userId);
                if (!is_numeric($text)) {
                    $this->editMessage($chatId, $botMessageId, "❌ Invalid user ID. Please enter a numeric ID.", [
                        'inline_keyboard' => [[['text' => '🔙 Back', 'callback_data' => 'admin_panel']]]
                    ]);
                    return;
                }
                $newAdminId = trim($text);
                
                // Add to runtime admins array
                if (!in_array($newAdminId, $this->admins)) {
                    $this->admins[] = $newAdminId;
                }
                
                // Save to .env file permanently
                $envPath = __DIR__ . '/../.env';
                $saved = false;
                if (file_exists($envPath) && is_writable($envPath)) {
                    $envContent = file_get_contents($envPath);
                    if (preg_match('/^ADMIN_IDS=(.*)$/m', $envContent, $matches)) {
                        $currentIds = trim($matches[1]);
                        if (!empty($currentIds)) {
                            $newIds = $currentIds . ',' . $newAdminId;
                        } else {
                            $newIds = $newAdminId;
                        }
                        $envContent = preg_replace('/^ADMIN_IDS=.*$/m', 'ADMIN_IDS=' . $newIds, $envContent);
                    } else {
                        $envContent .= "\nADMIN_IDS=" . $newAdminId;
                    }
                    $saved = file_put_contents($envPath, $envContent) !== false;
                }
                
                $statusMsg = $saved 
                    ? "✅ <b>Admin Added & Saved!</b>\n\nUser ID: <code>$newAdminId</code>\n\n<i>The ID has been saved to .env file permanently.</i>"
                    : "✅ <b>Admin Added!</b>\n\nUser ID: <code>$newAdminId</code>\n\n⚠️ <b>Note:</b> Runtime only. Could not write to .env file.";
                
                $this->editMessage($chatId, $botMessageId, $statusMsg, [
                    'inline_keyboard' => [[['text' => '🔙 Back', 'callback_data' => 'admin_panel']]]
                ]);
                return;
            }
        }
        
        // === FORCE SUBSCRIBE CHECK (only for private chats, after admin states, before regular commands) ===
        // Skip force subscribe check for group/channel events (like new_chat_members, left_chat_member)
        if ($chatId > 0 && empty($message['new_chat_members']) && empty($message['left_chat_member'])) {
            if (!$this->checkForceSubscribe($userId, $chatId)) {
                return;
            }
        }
        
        // === /START COMMAND - Always takes priority, clears any pending state ===
        if ($text === '/start') {
            $this->db->clearUserState($userId);
            $this->sendMainMenu($chatId);
            return;
        }

        $state = $this->db->getUserState($userId);

        if ($state) {
            $data = $state['data'];
            $targetChatId = $data['chat_id'] ?? null;
            $botMessageId = $data['message_id'] ?? null;
            
            // Delete User's Input Message to keep chat clean (if text/media)
            try {
                $this->deleteMessage($chatId, $message['message_id']);
            } catch (\Exception $e) {}
            
                // Capture caption for media messages
                $caption = $message['caption'] ?? null;
                
                if (isset($message['text'])) {
                    $content = $message['text'];
                    $type = 'text';
                } elseif (isset($message['photo'])) {
                    $type = 'photo';
                    $content = end($message['photo'])['file_id'];
                } elseif (isset($message['video'])) {
                    $type = 'video';
                    $content = $message['video']['file_id'];
                } elseif (isset($message['animation'])) {
                    $type = 'animation';
                    $content = $message['animation']['file_id'];
                } elseif (isset($message['document'])) {
                    $type = 'document';
                    $content = $message['document']['file_id'];
                } else {
                     $this->sendMessage($userId, "⚠️ Unsupported media type. Send text, photo, video, gif or document.");
                     return;
                }
                
                // --- Advanced Greeting & Farewell Inputs ---
                $validStates = [
                    'SET_GREETING_CONTENT', 'SET_GREETING_LANG', 'SET_GREETING_DEFER', 'SET_GREETING_AUTODEL', 'SET_GREETING_BUTTONS',
                    'SET_FAREWELL_CONTENT', 'SET_FAREWELL_LANG', 'SET_FAREWELL_DEFER', 'SET_FAREWELL_AUTODEL', 'SET_FAREWELL_BUTTONS'
                ];
                
                if (in_array($state['state'], $validStates)) {
                     $templateId = $data['template_id'] ?? null;
                     $isNew = $data['new'] ?? false;
                     $isFarewell = str_contains($state['state'], 'FAREWELL');
                     $msgType = $isFarewell ? 'farewell' : 'greeting';
                     
                     if (str_ends_with($state['state'], '_CONTENT')) {
                         // ... (existing content logic)
                         if ($isNew) {
                             $templateId = $this->db->addChannelMessage($data['chat_id'], $msgType, [
                                 'content_type' => $type,
                                 'file_id' => ($type !== 'text') ? $content : null,
                                 'text_content' => ($type === 'text') ? $content : $caption,
                                 'language' => 'all'
                             ]);
                         } else {
                             $this->db->updateChannelMessage($templateId, [
                                 'content_type' => $type,
                                 'file_id' => ($type !== 'text') ? $content : null,
                                 'text_content' => ($type === 'text') ? $content : $caption
                             ]);
                         }
                     } elseif (str_ends_with($state['state'], '_LANG')) {
                         $lang = strtolower(trim($message['text']));
                         if ($lang === 'all' || mb_strlen($lang, 'UTF-8') === 2 || $lang === 'other') {
                             $this->db->updateChannelMessage($templateId, ['language' => $lang]);
                         }
                     } elseif (str_ends_with($state['state'], '_DEFER') || str_ends_with($state['state'], '_AUTODEL')) {
                         $sec = $this->parseDuration($message['text']);
                         $field = str_ends_with($state['state'], '_DEFER') ? 'defer_seconds' : 'delete_seconds';
                         $this->db->updateChannelMessage($templateId, [$field => $sec]);
                     } elseif (str_ends_with($state['state'], '_BUTTONS')) { // Logic for buttons
                         $lines = explode("\n", $message['text']);
                         $buttons = [];
                         foreach ($lines as $line) {
                             $row = [];
                             $parts = explode('|', $line);
                             foreach ($parts as $part) {
                                 $item = explode('-', $part, 2);
                                 if (count($item) === 2) {
                                     $btnText = trim($item[0]);
                                     $url = trim($item[1]);
                                     if (str_starts_with($url, 'http')) {
                                         $row[] = ['text' => $btnText, 'url' => $url];
                                     }
                                 }
                             }
                             if (!empty($row)) $buttons[] = $row;
                         }
                         // updateChannelMessage expects array, it handles json_encode
                         $this->db->updateChannelMessage($templateId, ['buttons' => $buttons]);
                     }
                     
                     $botMessageId = $state['data']['message_id'] ?? null;
                     // FIX: Only override templateId if it's not already set (from addChannelMessage for new items)
                     if (!$templateId) {
                         $templateId = $state['data']['template_id'] ?? null;
                     }
                     
                     $this->db->clearUserState($userId);
                     
                     // Redirect back to EDIT menu
                     if ($isFarewell) {
                         $this->sendFarewellEditMenu($chatId, $botMessageId, $templateId);
                     } else {
                         $this->sendGreetingEditMenu($chatId, $botMessageId, $templateId);
                     }
                     return;
                }
                

            
            if ($state['state'] === 'SET_DEFER') {
                // Parse "1h 10m 15s"
                $text = strtolower($text);
                $seconds = 0;
                
                // Weeks
                if (preg_match('/(\d+)w/', $text, $w)) $seconds += $w[1] * 604800;
                // Days
                if (preg_match('/(\d+)d/', $text, $d)) $seconds += $d[1] * 86400;
                // Hours
                if (preg_match('/(\d+)h/', $text, $h)) $seconds += $h[1] * 3600;
                // Minutes
                if (preg_match('/(\d+)m/', $text, $m)) $seconds += $m[1] * 60;
                // Seconds
                if (preg_match('/(\d+)s/', $text, $s)) $seconds += $s[1];
                
                // Direct number = minutes (legacy/default assumption if no unit? user prompt says "Available measures: seconds...". 
                // If they type just "10", assume minutes as per common habit? Or seconds?
                // Given "1h 10m 15s" example, usually explicit units are preferred. 
                // Let's assume minutes for bare numbers to avoid "10" becoming 10s (too short), 
                // UNLESS user intends seconds. 
                // But prompt lists "seconds" as first measure. 
                // Let's stick to explicit units requirement or default to minutes if numeric?
                // Let's default to minutes for bare numbers to preserve backward compat behavior.
                if (preg_match('/^\d+$/', $text)) $seconds = (int)$text * 60;
                
                // Max limit: 1d = 86400s
                if ($seconds > 86400) {
                     $seconds = 86400;
                }
                
                $this->db->setChannelSetting($targetChatId, 'defer_time', $seconds);
                $this->db->clearUserState($userId);
                
                if ($botMessageId) {
                    $this->sendApplicationsAcceptMenu($chatId, $botMessageId, $targetChatId);
                } else {
                    $this->sendApplicationsAcceptMenu($chatId, null, $targetChatId);
                }
                return;
            }
            
            if ($state['state'] === 'SET_LANG_FILTER') {
                // Save setting
                $this->db->setChannelSetting($targetChatId, 'filter_lang_codes', $text);
                $this->db->clearUserState($userId);
                
                if ($botMessageId) {
                    $this->sendLanguageFilterMenu($chatId, $botMessageId, $targetChatId);
                } else {
                    // Send new message as we might not be able to edit if it was too old or different type? 
                    // Actually we usually edit.
                    $this->sendLanguageFilterMenu($chatId, null, $targetChatId); 
                }
                return;
            }
        }

        // Check for Broadcast Reply
        if (isset($message['reply_to_message']['text'])) {
            $replyText = $message['reply_to_message']['text'];
            
            // 1. Admin Broadcast to All Users
            if ($replyText === "ADMIN: Reply with message to broadcast to ALL USERS." && $this->isAdmin($userId)) {
                 $this->queueBroadcast('all_users', $text);
                 $this->sendMessage($chatId, "✅ Global User Broadcast queued.");
                 return;
            }
            
            // 2. Admin Broadcast to All Channels
            if ($replyText === "ADMIN: Reply with message to broadcast to ALL CHANNELS." && $this->isAdmin($userId)) {
                 $this->queueBroadcast('all_channels', $text);
                 $this->sendMessage($chatId, "✅ Global Channel Broadcast queued.");
                 return;
            }
            
            // 3. (User Broadcast Removed)
        }

        // === AUTO-REPLY TO RANDOM TEXT ===
        // If it's a private chat, text message, not a command, and user is not in a state
        if ($chatType === 'private' && !empty($text) && $text[0] !== '/' && !$state) {
            $autoReply = $_ENV['AUTO_REPLY_TEXT'] ?? null;
            if ($autoReply) {
                $this->sendMessage($chatId, $autoReply);
                return;
            }
        }

        // Note: new_chat_members greeting is handled in handleJoinRequest (at request time)
        // left_chat_member farewell is handled at the top of this function
    }
    
    // Helper: Parse "1h 10m" to seconds
    private function parseDuration($str) {
        if (is_numeric($str)) return (int)$str;
        $str = strtolower($str);
        $total = 0;
        
        if (preg_match('/(\d+)\s*d/', $str, $m)) $total += $m[1] * 86400;
        if (preg_match('/(\d+)\s*h/', $str, $m)) $total += $m[1] * 3600;
        if (preg_match('/(\d+)\s*m/', $str, $m)) $total += $m[1] * 60;
        if (preg_match('/(\d+)\s*s/', $str, $m)) $total += $m[1];
        
        return $total;
    }

    private function sendGreeting($chatId, $members, $chatTitle = null) {
        // Fetch all greeting templates
        $msgs = $this->db->getChannelMessages($chatId, 'greeting');
        
        // === DEFAULT GREETING CHECK ===
        if (empty($msgs)) {
            // No custom greetings set -> Use Default
            // Construct a "fake" message template for default behavior
            $msgs = [[
                'id' => 0, // dummy ID
                'language' => 'all',
                'content_type' => 'text',
                'file_id' => null,
                'text_content' => "👋 <b>Welcome {name}!</b>\n\nYou have been added to <b>{channel}</b>.", 
                'defer_seconds' => 0,
                'delete_seconds' => 0,
                'buttons' => []
            ]];
        }

        foreach ($members as $user) {
             if ($user['is_bot']) continue;
             
             // ATOMIC LOCK CLAIM: Try to insert lock. 
             // If returns false, it means lock already exists (race condition or already greeted).
             if (!$this->db->setGreetingLock($chatId, $user['id'])) {
                 continue; 
             }
             
             $userLang = mb_substr($user['language_code'] ?? 'en', 0, 2, 'UTF-8');
             $anySent = false;
             
             foreach ($msgs as $msg) {
                 $shouldSend = false;
                 if ($msg['language'] === 'all') {
                     $shouldSend = true;
                 } elseif ($msg['language'] === $userLang) {
                     $shouldSend = true;
                 } elseif ($msg['language'] === 'other') {
                     $hasSpecific = false;
                     foreach ($msgs as $m2) {
                         if ($m2['language'] === $userLang) {
                             $hasSpecific = true; 
                             break;
                         }
                     }
                     if (!$hasSpecific) $shouldSend = true;
                 }
                 
                 if ($shouldSend) {
                     $payload = [
                         'user_id' => $user['id'],
                         'first_name' => $user['first_name'],
                         'last_name' => $user['last_name'] ?? '',
                         'username' => $user['username'] ?? '',
                         'chat_title' => $chatTitle ?? 'Channel'
                     ];
                     
                     if ($msg['defer_seconds'] > 0) {
                         $this->db->scheduleAction($user['id'], $msg['id'], 'send', time() + $msg['defer_seconds'], $payload);
                         $anySent = true;
                     } else {
                         $sentMsgId = $this->sendTemplateMessage($user['id'], $msg, $payload);
                         
                         if ($sentMsgId) {
                             $anySent = true;
                             if ($msg['delete_seconds'] > 0) {
                                 $this->db->scheduleDeleteAction($user['id'], $sentMsgId, time() + $msg['delete_seconds']);
                             }
                         }
                     }
                 }
             }
             
             // If sending failed, RELEASE the lock so we can try again later
             if (!$anySent) {
                 $this->db->deleteGreetingLock($chatId, $user['id']);
             }
        }
    }
    
    /**
     * Send greeting to a single user via DM.
     * Lock is set AFTER successful send so that if the DM fails,
     * another caller (e.g. approveChatJoinRequest) can retry.
     */
    public function sendGreetingToUser($channelChatId, $userId, $force = false) {
        // ATOMIC LOCK CLAIM: Try to insert lock.
        // If returns false, it means lock already exists (race condition or already greeted).
        // If FORCE is true, we ignore if lock exists and proceed anyway.
        $locked = $this->db->setGreetingLock($channelChatId, $userId);
        
        if (!$locked && !$force) {
            return; 
        }
        
        // Fetch all greeting templates
        $msgs = $this->db->getChannelMessages($channelChatId, 'greeting');
        
        // === DEFAULT GREETING CHECK ===
        if (empty($msgs)) {
             $msgs = [[
                'id' => 0,
                'language' => 'all',
                'content_type' => 'text',
                'file_id' => null,
                'text_content' => "👋 <b>Welcome {name}!</b>\n\nYou have been added to <b>{channel}</b>.", 
                'defer_seconds' => 0,
                'delete_seconds' => 0,
                'buttons' => []
            ]];
        }
        
        // Try to get user info from the database
        $userInfo = $this->db->getUser($userId);
        $userLang = 'en'; // Default language
        
        $user = [
            'id' => $userId,
            'first_name' => $userInfo['first_name'] ?? 'User',
            'last_name' => $userInfo['last_name'] ?? '',
            'username' => $userInfo['username'] ?? '',
            'language_code' => $userLang
        ];
        
        // Get channel info for chat_title
        $channel = $this->db->getChannel($channelChatId);
        $chatTitle = $channel['title'] ?? 'Channel';
        
        $anySent = false;
        
        foreach ($msgs as $msg) {
            $shouldSend = false;
            if ($msg['language'] === 'all') {
                $shouldSend = true;
            } elseif ($msg['language'] === $userLang) {
                $shouldSend = true;
            } elseif ($msg['language'] === 'other') {
                $hasSpecific = false;
                foreach ($msgs as $m2) {
                    if ($m2['language'] === $userLang) {
                        $hasSpecific = true;
                        break;
                    }
                }
                if (!$hasSpecific) $shouldSend = true;
            }
            
            if ($shouldSend) {
                $payload = [
                    'user_id' => $user['id'],
                    'first_name' => $user['first_name'],
                    'last_name' => $user['last_name'],
                    'username' => $user['username'],
                    'chat_title' => $chatTitle
                ];
                
                if ($msg['defer_seconds'] > 0) {
                    $this->db->scheduleAction($user['id'], $msg['id'], 'send', time() + $msg['defer_seconds'], $payload);
                    $anySent = true;
                } else {
                    $sentMsgId = $this->sendTemplateMessage($user['id'], $msg, $payload);
                    
                    if ($sentMsgId) {
                        $anySent = true;
                        
                        if ($msg['delete_seconds'] > 0) {
                            $this->db->scheduleDeleteAction($user['id'], $sentMsgId, time() + $msg['delete_seconds']);
                        }
                    }
                }
            }
        }
        
        // If sending failed (no messages sent), RELEASE the lock so we can try again later.
        // (If messages were sent, the lock remains set from the start of this function)
        if (!$anySent) {
            $this->db->deleteGreetingLock($channelChatId, $userId);
        }
    }
    
    // Helper to send formatted message
    public function sendTemplateMessage($chatId, $msgTemplate, $payload = [], $isPreview = false) {
        $type = $msgTemplate['content_type'];
        $content = $msgTemplate['file_id'] ?: $msgTemplate['text_content'];
        $caption = $msgTemplate['file_id'] ? $msgTemplate['text_content'] : null;
        
        $txt = $caption ?: $content;
        if ($txt) {
             $txt = str_replace(
                 ['{name}', '{fullname}', '{username}', '{channel}'],
                 [$payload['first_name'], $payload['first_name'].' '.($payload['last_name']??''), $payload['username']??'', $payload['chat_title']??'Channel'],
                 $txt
             );
        }
        
        $params = ['chat_id' => $chatId, 'parse_mode' => 'HTML'];
        $keyboard = [];
        
        if (!empty($msgTemplate['buttons'])) {
             // Decode if string (from DB JSON column)
             $btns = is_string($msgTemplate['buttons']) ? json_decode($msgTemplate['buttons'], true) : $msgTemplate['buttons'];
             if (is_array($btns)) {
                 $keyboard = $btns;
             }
        }
        
        if ($isPreview) {
            $keyboard[] = [['text' => 'Close ❌', 'callback_data' => 'delete_msg']];
        }
        
        if (!empty($keyboard)) {
            $params['reply_markup'] = ['inline_keyboard' => $keyboard];
        }

        try {
            if ($type === 'text') {
                $params['text'] = $txt;
                $res = $this->request('sendMessage', $params);
            } else {
                $params[$type] = $content;
                $params['caption'] = $txt;
                $res = $this->request('send' . ucfirst($type), $params);
            }
            return $res['result']['message_id'] ?? null;
        } catch (\Exception $e) {
        error_log("Failed to send message to $chatId: " . $e->getMessage());
        return null;
    }
    }

    private function sendFarewell($chatId, $user) {
        $msgs = $this->db->getChannelMessages($chatId, 'farewell');
        if (empty($msgs)) return;

        // Determine User Language
        $userLang = mb_substr($user['language_code'] ?? 'en', 0, 2, 'UTF-8');

        foreach ($msgs as $msg) {
             $shouldSend = false;
             if ($msg['language'] === 'all') {
                 $shouldSend = true;
             } elseif ($msg['language'] === $userLang) {
                 $shouldSend = true;
             } elseif ($msg['language'] === 'other') {
                 $hasSpecific = false;
                 foreach ($msgs as $m2) {
                     if ($m2['language'] === $userLang) {
                         $hasSpecific = true; 
                         break;
                     }
                 }
                 if (!$hasSpecific) $shouldSend = true;
             }
             
             if ($shouldSend) {
                 $channel = $this->db->getChannel($chatId);
                 $payload = [
                     'user_id' => $user['id'],
                     'first_name' => $user['first_name'],
                     'last_name' => $user['last_name'] ?? '',
                     'username' => $user['username'] ?? '',
                     'chat_title' => $channel['title'] ?? 'Channel' 
                 ];

                 if ($msg['defer_seconds'] > 0) {
                     $this->db->scheduleAction($user['id'], $msg['id'], 'send', time() + $msg['defer_seconds'], $payload);
                 } else {
                     $sentMsgId = $this->sendTemplateMessage($user['id'], $msg, $payload);
                     if ($sentMsgId && $msg['delete_seconds'] > 0) {
                         $this->db->scheduleDeleteAction($user['id'], $sentMsgId, time() + $msg['delete_seconds']);
                     }
                 }
             }
        }
    }


    private function queueBroadcast($target, $text) {
        // We'll store this in settings for the Cron to pick up
        // Improved structure needed for multiple queues, but reusing 'pending_broadcast' for simplicity 
        // In real app, make a 'broadcasts' collection.
        $this->db->queueBroadcast($target, ($target === 'all_users' || $target === 'all_channels') ? 0 : $target, $text);
    }

    private function handleCallback(array $callback)
    {
        foreach ($callback as $key => $value) {
            if (is_string($value)) {
               $callback[$key] = trim($value);
            }
        }
        $chatId = $callback['message']['chat']['id'];
        $messageId = $callback['message']['message_id'];
        $data = $callback['data'];
        $userId = $callback['from']['id'];

        if ($data === 'back_main') {
            $this->sendMainMenu($chatId, $messageId);
            return;
        }
        
        // --- Bot Info ---
        if ($data === 'bot_info') {
            $text = "ℹ️ <b>Bot Information</b>\n\n" .
                    "🤖 <b>Auto Approval Bot</b>\n" .
                    "├ Version: <code>1.0</code>\n" .
                    "└ Language: PHP\n\n" .
                    "📋 <b>Features</b>\n" .
                    "├ Auto-approve join requests\n" .
                    "├ CAPTCHA verification\n" .
                    "├ Greeting & farewell messages\n" .
                    "├ Language-based filtering\n" .
                    "├ Deferred approvals\n" .
                    "└ Mass broadcast\n\n" .
                    "<i>Made with ❤️</i>";
            
            $this->editMessage($chatId, $messageId, $text, [
                'inline_keyboard' => [[['text' => '🔙 Back', 'callback_data' => 'back_main']]]
            ]);
            return;
        }

        // --- My Channels ---
        if ($data === 'my_channels') {
            $this->sendMyChannels($chatId, $userId, $messageId);
            return;
        }

        // --- Manage/Dashboard ---
        if (str_starts_with($data, 'interact_accept_')) {
            $targetChatId = (int)str_replace('interact_accept_', '', $data);
            // Approve the join request (greeting is sent inside approveChatJoinRequest)
            $this->approveChatJoinRequest($targetChatId, $userId);
            
            // Delete the challenge message to clean up
            try {
                $this->request('deleteMessage', ['chat_id' => $chatId, 'message_id' => $messageId]);
            } catch (\Exception $e) {}

            $this->answerCallback($callback['id'], "✅ Verified!", false);
            return;
        }
        
        if (str_starts_with($data, 'captcha_ans_')) {
            // Format: captcha_ans_{chatId}_{answer}
            $parts = explode('_', $data);
            $ans = (int)array_pop($parts);
            $targetChatId = (int)array_pop($parts);
            
            $state = $this->db->getUserState($userId);
            if ($state && $state['state'] === 'CAPTCHA_WAIT' && isset($state['data']['answer'])) {
                if ($ans === (int)$state['data']['answer']) {
                    // Correct — approve the join request (greeting is sent inside approveChatJoinRequest)
                    $this->approveChatJoinRequest($targetChatId, $userId);
                    $this->db->clearUserState($userId);
                    
                    // Delete the captcha message to clean up
                    try {
                        $this->request('deleteMessage', ['chat_id' => $chatId, 'message_id' => $messageId]);
                    } catch (\Exception $e) {}

                    $this->answerCallback($callback['id'], "✅ Correct!", false);
                } else {
                    // Wrong
                    $this->answerCallback($callback['id'], "❌ Wrong answer! Try again.");
                }
            } else {
                 $this->answerCallback($callback['id'], "⚠️ Session expired.");
            }
            return;
        }

        if (str_starts_with($data, 'manage_')) {
            $targetChatId = (int)str_replace('manage_', '', $data);
            $this->sendChannelSettings($chatId, $messageId, $targetChatId);
            return;
        }

        // --- Sub-Menus ---
        if (str_starts_with($data, 'menu_apps_')) {
            $targetChatId = (int)str_replace('menu_apps_', '', $data);
            $this->sendApplicationsAcceptMenu($chatId, $messageId, $targetChatId);
            return;
        }

        if (str_starts_with($data, 'menu_protect_')) {
            // Handle back button specifically
            if (str_ends_with($data, '_back')) {
                 $temp = str_replace(['menu_protect_', '_back'], '', $data);
                 $targetChatId = (int)$temp;
                 $this->db->clearUserState($userId);
            } else {
                 $targetChatId = (int)str_replace('menu_protect_', '', $data);
            }
            $this->sendProtectionMenu($chatId, $messageId, $targetChatId);
            return;
        }
        
        if (str_starts_with($data, 'menu_lang_filter_')) {
            $targetChatId = (int)str_replace('menu_lang_filter_', '', $data);
            $this->sendLanguageFilterMenu($chatId, $messageId, $targetChatId);
            return;
        }
        
        if (str_starts_with($data, 'reset_lang_filter_')) {
            $targetChatId = (int)str_replace('reset_lang_filter_', '', $data);
            $this->db->setChannelSetting($targetChatId, 'filter_lang_codes', '');
            $this->sendLanguageFilterMenu($chatId, $messageId, $targetChatId);
            return;
        }
        
        // --- Greeting Wizard Handlers ---
        // --- Greeting Wizard Handlers ---
        if (str_starts_with($data, 'greet_prev_')) {
            $tId = (int)str_replace('greet_prev_', '', $data);
            $msg = $this->db->getChannelMessage($tId);
            if ($msg) {
                // Use user data from callback (the admin clicking preview)
                $u = $callback['from'];
                $payload = [
                    'first_name' => $u['first_name'],
                    'last_name' => $u['last_name'] ?? '',
                    'username' => $u['username'] ?? '',
                    'chat_title' => 'Channel' // Placeholder or fetch if possible
                ];
                
                // Fetch real chat title if possible
                try {
                     $ch = $this->db->getChannel($msg['chat_id']);
                     if ($ch && !empty($ch['title'])) {
                         $payload['chat_title'] = $ch['title'];
                     }
                } catch (\Exception $e) {}

                $this->sendTemplateMessage($chatId, $msg, $payload, true);
            }
            return;
        }

        if (str_starts_with($data, 'greet_content_')) {
            $tId = (int)str_replace('greet_content_', '', $data);
            $this->db->setUserState($userId, 'SET_GREETING_CONTENT', ['template_id' => $tId, 'message_id' => $messageId]);
             $this->editMessage($chatId, $messageId, "<b>Update greeting content</b>\n
You can send captioned media or just text!

<code>{name}</code> - user name
<code>{fullname}</code> - user full name
<code>{username}</code> - username of account
<code>{channel}</code> - channel name

Hit <b>Cancel</b> to abort.", [
                'inline_keyboard' => [[['text' => 'Cancel', 'callback_data' => 'cancel_state']]]
            ]);
            return;
        }

        if (str_starts_with($data, 'greet_defer_')) {
            $tId = (int)str_replace('greet_defer_', '', $data);
            $this->db->setUserState($userId, 'SET_GREETING_DEFER', ['template_id' => $tId, 'message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "<b>Set defer time</b>\n
Example: <code>1h 10m 15s</code>
Max: 1d

Reply with <b>0</b> to disable delay.", [
                'inline_keyboard' => [[['text' => 'Cancel', 'callback_data' => 'cancel_state']]]
            ]);
            return;
        }

        if (str_starts_with($data, 'greet_autodel_')) {
            $tId = (int)str_replace('greet_autodel_', '', $data);
            $this->db->setUserState($userId, 'SET_GREETING_AUTODEL', ['template_id' => $tId, 'message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "<b>Set auto-delete time</b>\n
Example: <code>1h 10m 15s</code>

Reply with <b>0</b> to disable auto-delete.", [
                'inline_keyboard' => [[['text' => 'Cancel', 'callback_data' => 'cancel_state']]]
            ]);
            return;
        }
        
        if (str_starts_with($data, 'greet_lang_')) {
             $tId = (int)str_replace('greet_lang_', '', $data);
             // Show Lang Menu
             $this->db->setUserState($userId, 'SET_GREETING_LANG', ['template_id' => $tId, 'message_id' => $messageId]);
             $this->editMessage($chatId, $messageId, "<b>Set Language</b>\n
Enter language code (e.g. <code>en</code>, <code>ru</code>) or <code>all</code> for all languages.

Current settings will be overwritten.", [
                'inline_keyboard' => [[['text' => 'Cancel', 'callback_data' => 'cancel_state']]]
            ]);
            return;
        }

        // IMPORTANT: Check for greet_btns_remove_ FIRST (more specific pattern)
        if (str_starts_with($data, 'greet_btns_remove_')) {
            $tId = (int)str_replace('greet_btns_remove_', '', $data);
            $this->db->updateChannelMessage($tId, ['buttons' => []]);
            // Set state so 'Back' from the Edit Menu knows where to go
            $this->db->setUserState($userId, 'SET_GREETING_EDIT', ['template_id' => $tId]);
            $this->answerCallback($callback['id'], "✅ Buttons removed.");
            $this->sendGreetingEditMenu($chatId, $messageId, $tId);
            return;
        }

        if (str_starts_with($data, 'greet_btns_')) {
             $tId = (int)str_replace('greet_btns_', '', $data);
             $this->db->setUserState($userId, 'SET_GREETING_BUTTONS', ['template_id' => $tId, 'message_id' => $messageId]);
             
             // Check if buttons exist to show "Remove"
             $msg = $this->db->getChannelMessage($tId);
             $hasButtons = !empty($msg['buttons']) && $msg['buttons'] !== '[]';

             $keyboard = [];
             if ($hasButtons) {
                 $keyboard[] = [['text' => 'Remove buttons', 'callback_data' => 'greet_btns_remove_' . $tId]];
             }
             $keyboard[] = [['text' => 'Back', 'callback_data' => 'cancel_state']];

             $this->editMessage($chatId, $messageId, "<b>Set URL-buttons</b>\n
Send me a message with the list of URL-buttons.
Please, follow the next format:

Button 1 - http://example1.com
Button 2 - http://example2.com

Use
<code>|</code>
symbol to add multiple buttons in the row:

Button 1 - http://example1.com | Button 2 - http://example2.com
Button 3 - http://example3.com | Button 4 - http://example4.com", [
                'inline_keyboard' => $keyboard
            ]);
            return;
        }

        // --- Farewell Handlers (Mirrored) ---
        if (str_starts_with($data, 'farewell_add_')) {
            $targetChatId = (int)str_replace('farewell_add_', '', $data);
            $this->db->setUserState($userId, 'SET_FAREWELL_CONTENT', ['chat_id' => $targetChatId, 'message_id' => $messageId, 'new' => true]);
             $this->editMessage($chatId, $messageId, "<b>New farewell</b>\n
You can send captioned media or just text!

<code>{name}</code> - user name
<code>{fullname}</code> - user full name
<code>{username}</code> - username of account
<code>{channel}</code> - channel name

Hit <b>Cancel</b> to abort.", [
                'inline_keyboard' => [[['text' => 'Cancel', 'callback_data' => 'cancel_state']]]
            ]);
            return;
        }
        
        if (str_starts_with($data, 'farewell_edit_')) {
             $tId = (int)str_replace('farewell_edit_', '', $data);
             $this->sendFarewellEditMenu($chatId, $messageId, $tId);
             return;
        }
        
        if (str_starts_with($data, 'farewell_delete_')) {
             $tId = (int)str_replace('farewell_delete_', '', $data);
             $msg = $this->db->getChannelMessage($tId);
             if ($msg) {
                 $this->db->deleteChannelMessage($tId);
                 $this->sendFarewellsMenu($chatId, $messageId, $msg['chat_id']);
             }
             return;
        }
        
        if (str_starts_with($data, 'farewell_prev_')) {
            $tId = (int)str_replace('farewell_prev_', '', $data);
            $msg = $this->db->getChannelMessage($tId);
            if ($msg) {
                // Fetch user data, fallback to callback from
                $u = $callback['from'];
                $payload = [
                    'first_name' => $u['first_name'],
                    'last_name' => $u['last_name'] ?? '',
                    'username' => $u['username'] ?? '',
                    'chat_title' => 'Channel' 
                ];
                
                // Fetch real chat title if possible
                try {
                     $ch = $this->db->getChannel($msg['chat_id']);
                     if ($ch && !empty($ch['title'])) {
                         $payload['chat_title'] = $ch['title'];
                     }
                } catch (\Exception $e) {}
                
                $this->sendTemplateMessage($chatId, $msg, $payload, true);
            }
            return;
        }

        // IMPORTANT: Check for farewell_btns_remove_ FIRST (more specific pattern)
        if (str_starts_with($data, 'farewell_btns_remove_')) {
            $tId = (int)str_replace('farewell_btns_remove_', '', $data);
            $this->db->updateChannelMessage($tId, ['buttons' => []]);
            // Set state so 'Back' from the Edit Menu knows where to go
            $this->db->setUserState($userId, 'SET_FAREWELL_EDIT', ['template_id' => $tId]);
            $this->answerCallback($callback['id'], "✅ Buttons removed.");
            $this->sendFarewellEditMenu($chatId, $messageId, $tId);
            return;
        }

        if (str_starts_with($data, 'farewell_btns_')) {
             $tId = (int)str_replace('farewell_btns_', '', $data);
             $this->db->setUserState($userId, 'SET_FAREWELL_BUTTONS', ['template_id' => $tId, 'message_id' => $messageId]);
             
             // Check if buttons exist to show "Remove"
             $msg = $this->db->getChannelMessage($tId);
             $hasButtons = !empty($msg['buttons']) && $msg['buttons'] !== '[]';

             $keyboard = [];
             if ($hasButtons) {
                 $keyboard[] = [['text' => 'Remove buttons', 'callback_data' => 'farewell_btns_remove_' . $tId]];
             }
             $keyboard[] = [['text' => 'Back', 'callback_data' => 'cancel_state']];

             $this->editMessage($chatId, $messageId, "<b>Set URL-buttons</b>\n
Send me a message with the list of URL-buttons.
Please, follow the next format:

Button 1 - http://example1.com
Button 2 - http://example2.com

Use
<code>|</code>
symbol to add multiple buttons in the row:

Button 1 - http://example1.com | Button 2 - http://example2.com
Button 3 - http://example3.com | Button 4 - http://example4.com", [
                'inline_keyboard' => $keyboard
            ]);
            return;
        }

        if (str_starts_with($data, 'farewell_content_')) {
            $tId = (int)str_replace('farewell_content_', '', $data);
            $this->db->setUserState($userId, 'SET_FAREWELL_CONTENT', ['template_id' => $tId, 'message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "<b>Update farewell content</b>\n
You can send captioned media or just text!

<code>{name}</code> - user name
<code>{fullname}</code> - user full name
<code>{username}</code> - username of account
<code>{channel}</code> - channel name

Hit <b>Cancel</b> to abort.", [
                'inline_keyboard' => [[['text' => 'Cancel', 'callback_data' => 'cancel_state']]]
            ]);
            return;
        }

        if (str_starts_with($data, 'farewell_defer_')) {
            $tId = (int)str_replace('farewell_defer_', '', $data);
            $this->db->setUserState($userId, 'SET_FAREWELL_DEFER', ['template_id' => $tId, 'message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "<b>Set defer time</b>\n
Example: <code>1h 10m 15s</code>
Max: 1d

Reply with <b>0</b> to disable delay.", [
                'inline_keyboard' => [[['text' => 'Cancel', 'callback_data' => 'cancel_state']]]
            ]);
            return;
        }

        if (str_starts_with($data, 'farewell_autodel_')) {
            $tId = (int)str_replace('farewell_autodel_', '', $data);
            $this->db->setUserState($userId, 'SET_FAREWELL_AUTODEL', ['template_id' => $tId, 'message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "<b>Set auto-delete time</b>\n
Example: <code>1h 10m 15s</code>

Reply with <b>0</b> to disable auto-delete.", [
                'inline_keyboard' => [[['text' => 'Cancel', 'callback_data' => 'cancel_state']]]
            ]);
            return;
        }
        
        if (str_starts_with($data, 'farewell_lang_')) {
             $tId = (int)str_replace('farewell_lang_', '', $data);
             $this->db->setUserState($userId, 'SET_FAREWELL_LANG', ['template_id' => $tId, 'message_id' => $messageId]);
             $this->editMessage($chatId, $messageId, "<b>Set Language</b>\n
Enter language code (e.g. <code>en</code>, <code>ru</code>) or <code>all</code> for all languages.

Current settings will be overwritten.", [
                'inline_keyboard' => [[['text' => 'Cancel', 'callback_data' => 'cancel_state']]]
            ]);
            return;
        }

        // --- New Handlers for Advanced Greetings ---
        if (str_starts_with($data, 'greet_add_')) {
            $targetChatId = (int)str_replace('greet_add_', '', $data);
            // Initialize 'SET_GREETING' state with empty data
            // We need a wizard flow. 
            // Step 1: Input content (Media/Text)
            $this->db->setUserState($userId, 'SET_GREETING_CONTENT', ['chat_id' => $targetChatId, 'message_id' => $messageId, 'new' => true]);
             $this->editMessage($chatId, $messageId, "<b>New greeting</b>\n
You can send captioned media or just text!

You can insert placeholders into the text.
Use the following notation:
<code>{name}</code> - user name
<code>{fullname}</code> - user full name
<code>{username}</code> - username of account
<code>{channel}</code> - channel name

Hit <b>Cancel</b> to abort.", [
                'inline_keyboard' => [[['text' => 'Cancel', 'callback_data' => 'cancel_state']]]
            ]);
            return;
        }
        
        if (str_starts_with($data, 'greet_edit_')) {
             // greet_edit_{template_id}
             $tId = (int)str_replace('greet_edit_', '', $data);
             $this->sendGreetingEditMenu($chatId, $messageId, $tId);
             return;
        }

        if (str_starts_with($data, 'greet_delete_')) {
             $tId = (int)str_replace('greet_delete_', '', $data);
             $msg = $this->db->getChannelMessage($tId);
             if ($msg) {
                 $this->db->deleteChannelMessage($tId);
                 $this->sendGreetingsMenu($chatId, $messageId, $msg['chat_id']);
             }
             return;
        }
        
        if (str_starts_with($data, 'set_greet_')) {
            $targetChatId = (int)str_replace('set_greet_', '', $data);
            $this->db->setUserState($userId, 'SET_GREETING', ['chat_id' => $targetChatId, 'message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "<b>New greeting</b>\n
You can send captioned media or just text!

You can insert placeholders into the text.
Use the following notation:
<code>{name}</code> - user name
<code>{fullname}</code> - user full name
<code>{username}</code> - username of account
<code>{channel}</code> - channel name

Hit <b>Cancel</b> to abort.", [
                'inline_keyboard' => [[['text' => 'Cancel', 'callback_data' => 'cancel_state']]]
            ]);
            return;
        }
        
        if (str_starts_with($data, 'menu_greet_')) {
            $targetChatId = (int)str_replace('menu_greet_', '', $data);
            $this->sendGreetingsMenu($chatId, $messageId, $targetChatId);
            return;
        }

        if (str_starts_with($data, 'menu_farewell_')) {
            $targetChatId = (int)str_replace('menu_farewell_', '', $data);
            $this->sendFarewellsMenu($chatId, $messageId, $targetChatId);
            return;
        }
        
        if (str_starts_with($data, 'set_farewell_')) {
            $targetChatId = (int)str_replace('set_farewell_', '', $data);
            $this->db->setUserState($userId, 'SET_FAREWELL', ['chat_id' => $targetChatId, 'message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "<b>New farewell</b>\n
You can send captioned media or just text!

You can insert placeholders into the text.
Use the following notation:
<code>{name}</code> - user name
<code>{fullname}</code> - user full name
<code>{username}</code> - username of account
<code>{channel}</code> - channel name

Hit <b>Cancel</b> to abort.", [
                'inline_keyboard' => [[['text' => 'Cancel', 'callback_data' => 'cancel_state']]]
            ]);
            return;
        }
        
        if (str_starts_with($data, 'reset_farewell_')) {
            $targetChatId = (int)str_replace('reset_farewell_', '', $data);
            $this->db->setChannelSetting($targetChatId, 'farewell_text', null); 
            $this->sendFarewellsMenu($chatId, $messageId, $targetChatId);
            return;
        }
        
        if ($data === 'delete_msg') {
            $this->deleteMessage($chatId, $messageId);
            return;
        }

        if ($data === 'cancel_state') {
            $state = $this->db->getUserState($userId);
            $this->db->clearUserState($userId);
            $this->answerCallback($callback['id'], "❌ Action cancelled.");
            
            if ($state && isset($state['state'])) {
                $s = $state['state'];
                $d = $state['data'] ?? [];
                
                // Editing existing item -> Back to Edit Menu
                if ((str_starts_with($s, 'SET_GREETING_') || str_starts_with($s, 'SET_GREETING')) && isset($d['template_id'])) {
                    $this->sendGreetingEditMenu($chatId, $messageId, $d['template_id']);
                    return;
                }
                if ((str_starts_with($s, 'SET_FAREWELL_') || str_starts_with($s, 'SET_FAREWELL')) && isset($d['template_id'])) {
                    $this->sendFarewellEditMenu($chatId, $messageId, $d['template_id']);
                    return;
                }
                
                // Adding new item -> Back to List Menu
                if ($s === 'SET_GREETING_CONTENT' && isset($d['chat_id']) && !isset($d['template_id'])) {
                     $this->sendGreetingsMenu($chatId, $messageId, $d['chat_id']);
                     return;
                }
                if ($s === 'SET_FAREWELL_CONTENT' && isset($d['chat_id']) && !isset($d['template_id'])) {
                     $this->sendFarewellsMenu($chatId, $messageId, $d['chat_id']);
                     return;
                }
            }
            
            $this->sendMainMenu($chatId, $messageId);
            return;
        }
        
        if (str_starts_with($data, 'set_defer_')) {
            $targetChatId = (int)str_replace('set_defer_', '', $data);
            $this->db->setUserState($userId, 'SET_DEFER', ['chat_id' => $targetChatId, 'message_id' => $messageId]);
             $this->editMessage($chatId, $messageId, "<b>Set auto-approve defer</b>\n" .
             "You can set the desired time to defer auto-approve! For example, if you set the value to `1h 10m 15s`, the bot will wait 1 hour, 10 minutes and 15 seconds and then approve join request.\n\n" .
             "Available measures: seconds, minutes, hours, days, weeks\n\n" .
             "Max defer value: 1d", [
                'inline_keyboard' => [[['text' => 'Cancel', 'callback_data' => 'cancel_state']]]
            ]);
            return;
        }

        // --- Channel Toggles ---
        if (str_starts_with($data, 'toggle_')) {
            // Format: toggle_settingname_chatid
            $parts = explode('_', $data);
            $targetChatId = (int)array_pop($parts);
            $settingKey = implode('_', array_slice($parts, 1)); // remove 'toggle'
            
            // Security: Check owner
            $channel = $this->db->getChannel($targetChatId);
            if (!$channel || $channel['owner_id'] != $userId) {
                $this->answerCallback($callback['id'], "🚫 Access Denied.");
                return;
            }

            $current = $this->db->getChannelSetting($targetChatId, $settingKey, '0');
            // Convert string '0'/'1' to boolean for proper toggle
            $currentBool = ($current === '1' || $current === true || $current === 1);
            $this->db->setChannelSetting($targetChatId, $settingKey, !$currentBool);
            
            // Return to the correct menu based on setting key
            if (in_array($settingKey, ['app_accept_enabled', 'auto_approve', 'use_captcha', 'accept_interact'])) {
                 $this->sendApplicationsAcceptMenu($chatId, $messageId, $targetChatId);
            } elseif (str_starts_with($settingKey, 'filter_')) {
                 $this->sendProtectionMenu($chatId, $messageId, $targetChatId);
            } else {
                 $this->sendChannelSettings($chatId, $messageId, $targetChatId);
            }
            return;
        }

        // === FORCE SUBSCRIBE VERIFICATION ===
        if ($data === 'verify_forcesub') {
            $fsChannelId = $this->db->getGlobalSetting('force_subscribe_chat_id', '');
            
            if (empty($fsChannelId)) {
                $this->answerCallback($callback['id'], "Force subscribe not configured.");
                return;
            }
            
            $status = $this->getChatMemberStatus((int)$fsChannelId, $userId);
            if (in_array($status, ['member', 'administrator', 'creator', 'restricted'])) {
                $this->answerCallback($callback['id'], "✅ Verified! You can now use the bot.");
                $this->deleteMessage($chatId, $messageId);
                // Trigger start command
                $this->handleMessage(['chat' => ['id' => $chatId], 'from' => ['id' => $userId, 'first_name' => ''], 'text' => '/start', 'message_id' => 0]);
            } else {
                $this->answerCallback($callback['id'], "❌ You haven't joined the channel yet!", true);
            }
            return;
        }

            // (Broadcast callback removed)

        // =========================================

        // ENHANCED ADMIN PANEL
        // =========================================
        
        if ($data === 'admin_panel') {
            if (!$this->isAdmin($userId)) return;
            $stats = $this->db->getStats();
            
            $text = "👤 <b>Admin Panel</b>\n\n" .
                    "📊 <b>Quick Stats</b>\n" .
                    "├ Users: <b>{$stats['users']}</b>\n" .
                    "├ Channels: <b>{$stats['channels']}</b>\n" .
                    "├ Banned: <b>{$stats['banned']}</b>\n" .
                    "└ Broadcasts: <b>{$stats['broadcasts']}</b>";
            
            $keyboard = [
                [['text' => '📊 Statistics', 'callback_data' => 'admin_stats']],
                [['text' => '📢 Broadcast', 'callback_data' => 'admin_broadcast']],
                [['text' => '👥 User Management', 'callback_data' => 'admin_users']],
                [['text' => '📋 View All Channels', 'callback_data' => 'admin_channels']],
                [['text' => '🔗 Force Subscribe', 'callback_data' => 'admin_forcesub']],
            ];
            
            // Add Admin button - only visible to owner
            if ($userId === $this->ownerId) {
                $keyboard[] = [['text' => '➕ Add Admin', 'callback_data' => 'admin_add_admin']];
            }
            
            $keyboard[] = [['text' => '🔄 Restart Bot', 'callback_data' => 'admin_restart']];
            $keyboard[] = [['text' => '🔙 Back', 'callback_data' => 'back_main']];
            
            $this->editMessage($chatId, $messageId, $text, ['inline_keyboard' => $keyboard]);
            return;
        }
        
        // --- Admin Statistics ---
        if ($data === 'admin_stats') {
            if (!$this->isAdmin($userId)) return;
            $stats = $this->db->getStats();
            $refreshTime = date('h:i:s A');
            
            $text = "📊 <b>Detailed Statistics</b>\n\n" .
                    "👥 <b>Users</b>\n" .
                    "├ Total: <b>{$stats['users']}</b>\n" .
                    "└ Banned: <b>{$stats['banned']}</b>\n\n" .
                    "📢 <b>Channels</b>\n" .
                    "└ Total: <b>{$stats['channels']}</b>\n\n" .
                    "📝 <b>Join Requests</b>\n" .
                    "├ Pending: <b>{$stats['requests']['pending']}</b>\n" .
                    "├ Approved: <b>{$stats['requests']['approved']}</b>\n" .
                    "├ Declined: <b>{$stats['requests']['declined']}</b>\n" .
                    "└ Deferred: <b>{$stats['requests']['deferred']}</b>\n\n" .
                    "📢 <b>Broadcasts</b>\n" .
                    "└ Total: <b>{$stats['broadcasts']}</b>\n\n" .
                    "<i>🕐 Updated: {$refreshTime}</i>";
            
            $this->editMessage($chatId, $messageId, $text, [
                'inline_keyboard' => [
                    [['text' => '🔄 Refresh', 'callback_data' => 'admin_stats']],
                    [['text' => '🔙 Back', 'callback_data' => 'admin_panel']]
                ]
            ]);
            return;
        }
        
        // --- Admin Broadcast Menu ---
        if ($data === 'admin_broadcast') {
            if (!$this->isAdmin($userId)) return;
            
            // Check for active broadcast
            $active = $this->db->getActiveBroadcast();
            $activeText = "";
            $activeBtn = [];
            
            if ($active) {
                $progress = $active['total_targets'] > 0 
                    ? round(($active['sent_count'] + $active['failed_count']) / $active['total_targets'] * 100) 
                    : 0;
                $activeText = "\n\n⏳ <b>Active Broadcast</b>\n" .
                              "├ Progress: <b>{$progress}%</b>\n" .
                              "├ Sent: <b>{$active['sent_count']}</b>\n" .
                              "├ Failed: <b>{$active['failed_count']}</b>\n" .
                              "└ Total: <b>{$active['total_targets']}</b>";
                $activeBtn = [['text' => '📈 Check Progress', 'callback_data' => 'admin_bc_progress_' . $active['id']]];
            }
            
            $text = "📢 <b>Broadcast</b>\n\n" .
                    "Send a message to all users or channels.\n" .
                    "Supports: Text, Photo, Video, Document, Audio, Voice, GIF, Sticker" . $activeText;
            
            $keyboard = [];
            if (!empty($activeBtn)) $keyboard[] = $activeBtn;
            $keyboard[] = [['text' => '👥 Broadcast to All Users', 'callback_data' => 'admin_bc_users']];
            $keyboard[] = [['text' => '📢 Broadcast to All Channels', 'callback_data' => 'admin_bc_channels']];
            $keyboard[] = [['text' => '🔙 Back', 'callback_data' => 'admin_panel']];
            
            $this->editMessage($chatId, $messageId, $text, ['inline_keyboard' => $keyboard]);
            return;
        }
        
        if ($data === 'admin_bc_users') {
            if (!$this->isAdmin($userId)) return;
            $this->db->setUserState($userId, 'ADMIN_BROADCAST', ['target' => 'all_users', 'message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "📢 <b>Broadcast to All Users</b>\n\nSend me the content to broadcast.\n\n<b>Personalization:</b>\n<code>{name}</code> - First name\n<code>{fullname}</code> - Full name\n<code>{username}</code> - @username\n\n<i>Supports: Text, Photo, Video, Document, Audio, Voice, GIF, Sticker</i>", [
                'inline_keyboard' => [[['text' => '❌ Cancel', 'callback_data' => 'admin_broadcast']]]
            ]);
            return;
        }
        
        if ($data === 'admin_bc_channels') {
            if (!$this->isAdmin($userId)) return;
            $this->db->setUserState($userId, 'ADMIN_BROADCAST', ['target' => 'all_channels', 'message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "📢 <b>Broadcast to All Channels</b>\n\nSend me the content to broadcast.\n\n<i>Supports: Text, Photo, Video, Document, Audio, Voice, GIF, Sticker</i>", [
                'inline_keyboard' => [[['text' => '❌ Cancel', 'callback_data' => 'admin_broadcast']]]
            ]);
            return;
        }
        
        if ($data === 'admin_bc_skip_btns') {
            if (!$this->isAdmin($userId)) return;
            
            // Retrieve state data
            $state = $this->db->getUserState($userId);
            if (!$state || $state['state'] !== 'ADMIN_BROADCAST_BUTTONS') {
                $this->answerCallback($callback['id'], "Session expired.");
                return;
            }
            
            $d = $state['data'];
            // Queue without buttons
            $bcId = $this->db->queueBroadcastEnhanced($d['target'], null, $d['text'], $d['content_type'], $d['file_id'], []);
            $this->db->clearUserState($userId);
            
            // TRIGGER BROADCAST PROCESSOR IMMEDIATELY
            $siteUrl = $_ENV['SITE_URL'] ?? '';
            if ($siteUrl) {
                $parts = parse_url($siteUrl);
                $path = rtrim($parts['path'] ?? '/', '/') . '/process_broadcast.php';
                $query = "?key=" . ($_ENV['CRON_SECRET'] ?? '');
                $host = $parts['host'] ?? 'localhost';
                $scheme = $parts['scheme'] ?? 'http';
                $port = $parts['port'] ?? ($scheme === 'https' ? 443 : 80);
                $fp = @fsockopen(($scheme === 'https' ? 'ssl://' : '') . $host, $port, $errno, $errstr, 1);
                if ($fp) {
                    $out = "GET " . $path . $query . " HTTP/1.1\r\n";
                    $out .= "Host: " . $host . "\r\n";
                    $out .= "Connection: Close\r\n\r\n";
                    fwrite($fp, $out);
                    fclose($fp);
                }
            }

            $this->editMessage($chatId, $messageId, "✅ <b>Broadcast Queued and Started!</b>\n\n" .
                "├ ID: <code>$bcId</code>\n" .
                "├ Target: <b>{$d['target']}</b>\n" .
                "├ Type: <b>{$d['content_type']}</b>\n" .
                "├ Buttons: <b>No</b>\n\n" .
                "The broadcast will be processed in the background.", [
                'inline_keyboard' => [
                    [['text' => '📈 Check Progress', 'callback_data' => 'admin_bc_progress_' . $bcId]],
                    [['text' => '🔙 Back', 'callback_data' => 'admin_broadcast']]
                ]
            ]);
            return;
        }

        if (str_starts_with($data, 'admin_bc_progress_')) {
            if (!$this->isAdmin($userId)) return;
            $bcId = (int)str_replace('admin_bc_progress_', '', $data);
            $bc = $this->db->getBroadcastById($bcId);
            
            if (!$bc) {
                $this->answerCallback($callback['id'], "Broadcast not found.");
                return;
            }
            
            // Every progress check also kicks the broadcast processor (acts as manual trigger)
            if (in_array($bc['status'], ['pending', 'processing'])) {
                $this->triggerBroadcastProcessor();
            }
            
            $progress = $bc['total_targets'] > 0 
                ? round(($bc['sent_count'] + $bc['failed_count']) / $bc['total_targets'] * 100) 
                : 0;
            $progressBar = $this->getProgressBar($progress);
            $refreshTime = date('h:i:s A');
            
            $statusEmoji = match($bc['status']) {
                'pending' => '⏳',
                'processing' => '🔄',
                'completed' => '✅',
                'failed' => '❌',
                default => '❓'
            };
            
            $text = "📢 <b>Broadcast Progress</b>\n\n" .
                    "$progressBar <b>{$progress}%</b>\n\n" .
                    "├ Status: {$statusEmoji} {$bc['status']}\n" .
                    "├ Target: <b>{$bc['target_type']}</b>\n" .
                    "├ Sent: <b>{$bc['sent_count']}</b>\n" .
                    "├ Failed: <b>{$bc['failed_count']}</b>\n" .
                    "└ Total: <b>{$bc['total_targets']}</b>\n\n" .
                    "<i>🕐 Updated: {$refreshTime}</i>";
            
            $this->editMessage($chatId, $messageId, $text, [
                'inline_keyboard' => [
                    [['text' => '🔄 Refresh', 'callback_data' => 'admin_bc_progress_' . $bcId]],
                    [['text' => '🔙 Back', 'callback_data' => 'admin_broadcast']]
                ]
            ]);
            return;
        }
        
        // --- Admin User Management ---
        if ($data === 'admin_users') {
            if (!$this->isAdmin($userId)) return;
            
            $text = "👥 <b>User Management</b>\n\n" .
                    "Search, ban, or unban users.";
            
            $this->editMessage($chatId, $messageId, $text, [
                'inline_keyboard' => [
                    [['text' => '🔍 Search User', 'callback_data' => 'admin_search_user']],
                    [['text' => '🚫 Ban User', 'callback_data' => 'admin_ban_user']],
                    [['text' => '✅ Unban User', 'callback_data' => 'admin_unban_user']],
                    [['text' => '📋 Banned List', 'callback_data' => 'admin_banned_list']],
                    [['text' => '🔙 Back', 'callback_data' => 'admin_panel']]
                ]
            ]);
            return;
        }
        
        if ($data === 'admin_search_user') {
            if (!$this->isAdmin($userId)) return;
            $this->db->setUserState($userId, 'ADMIN_SEARCH_USER', ['message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "🔍 <b>Search User</b>\n\nSend me a user ID or @username to search.", [
                'inline_keyboard' => [[['text' => '❌ Cancel', 'callback_data' => 'admin_users']]]
            ]);
            return;
        }
        
        if ($data === 'admin_ban_user') {
            if (!$this->isAdmin($userId)) return;
            $this->db->setUserState($userId, 'ADMIN_BAN_USER', ['message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "🚫 <b>Ban User</b>\n\nSend me the user ID to ban.", [
                'inline_keyboard' => [[['text' => '❌ Cancel', 'callback_data' => 'admin_users']]]
            ]);
            return;
        }
        
        if ($data === 'admin_unban_user') {
            if (!$this->isAdmin($userId)) return;
            $this->db->setUserState($userId, 'ADMIN_UNBAN_USER', ['message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "✅ <b>Unban User</b>\n\nSend me the user ID to unban.", [
                'inline_keyboard' => [[['text' => '❌ Cancel', 'callback_data' => 'admin_users']]]
            ]);
            return;
        }
        
        if ($data === 'admin_banned_list') {
            if (!$this->isAdmin($userId)) return;
            $banned = $this->db->getBannedUsers(10, 0);
            
            if (empty($banned)) {
                $text = "📋 <b>Banned Users</b>\n\nNo banned users.";
            } else {
                $text = "📋 <b>Banned Users</b>\n\n";
                foreach ($banned as $i => $b) {
                    $name = $b['first_name'] ?? 'Unknown';
                    $uname = $b['username'] ? "@{$b['username']}" : '';
                    $text .= ($i + 1) . ". <code>{$b['user_id']}</code> - {$name} {$uname}\n";
                }
            }
            
            $this->editMessage($chatId, $messageId, $text, [
                'inline_keyboard' => [
                    [['text' => '🔙 Back', 'callback_data' => 'admin_users']]
                ]
            ]);
            return;
        }
        
        // --- View All Channels ---
        if (str_starts_with($data, 'admin_channels')) {
            if (!$this->isAdmin($userId)) return;
            
            // Parse page: admin_channels or admin_channels_2
            $page = 0;
            if (preg_match('/admin_channels_(\d+)/', $data, $m)) {
                $page = (int)$m[1];
            }
            
            $perPage = 20;
            $total = $this->db->getTotalChannelCount();
            $channels = $this->db->getAllChannelsWithDetails($perPage, $page * $perPage);
            
            $startNum = $page * $perPage + 1;
            $endNum = min(($page + 1) * $perPage, $total);
            
            $text = "📋 <b>All Channels</b> ({$startNum}-{$endNum} of {$total})\n\n";
            foreach ($channels as $ch) {
                $title = htmlspecialchars($ch['title'] ?? 'Untitled', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
                $text .= "• <code>{$ch['chat_id']}</code> - {$title}\n";
            }
            
            $keyboard = [];
            
            // CSV download button for large lists
            if ($total > 100) {
                $keyboard[] = [['text' => '📥 Download CSV', 'callback_data' => 'admin_channels_csv']];
            }
            
            // Pagination buttons
            $navRow = [];
            if ($page > 0) {
                $navRow[] = ['text' => '◀️ Prev', 'callback_data' => 'admin_channels_' . ($page - 1)];
            }
            if (($page + 1) * $perPage < $total) {
                $navRow[] = ['text' => 'Next ▶️', 'callback_data' => 'admin_channels_' . ($page + 1)];
            }
            if (!empty($navRow)) {
                $keyboard[] = $navRow;
            }
            
            $keyboard[] = [['text' => '🔙 Back', 'callback_data' => 'admin_panel']];
            
            $this->editMessage($chatId, $messageId, $text, ['inline_keyboard' => $keyboard]);
            return;
        }
        
        // --- Download Channels CSV ---
        if ($data === 'admin_channels_csv') {
            if (!$this->isAdmin($userId)) return;
            
            $channels = $this->db->getAllChannelsWithDetails(100000, 0);
            $csv = "chat_id,title,owner_id,created_at\n";
            foreach ($channels as $ch) {
                $title = str_replace('"', '""', $ch['title'] ?? 'Untitled');
                $csv .= "{$ch['chat_id']},\"{$title}\",{$ch['owner_id']},{$ch['created_at']}\n";
            }
            
            $filename = sys_get_temp_dir() . '/channels_' . time() . '.csv';
            file_put_contents($filename, $csv);
            
            // Send document
            $this->request('sendDocument', [
                'chat_id' => $chatId,
                'document' => new \CURLFile($filename, 'text/csv', 'channels_export.csv'),
                'caption' => '📋 All Channels Export (' . count($channels) . ' channels)'
            ]);
            
            unlink($filename);
            $this->answerCallback($callback['id'], '📥 CSV sent!');
            return;
        }
        
        if (str_starts_with($data, 'admin_do_ban_')) {
            if (!$this->isAdmin($userId)) return;
            $targetId = (int)str_replace('admin_do_ban_', '', $data);
            $this->db->banUser($targetId, $userId, 'Banned by admin');
            $this->answerCallback($callback['id'], "✅ User banned!");
            $this->sendAdminUserInfo($chatId, $messageId, $targetId);
            return;
        }
        
        if (str_starts_with($data, 'admin_do_unban_')) {
            if (!$this->isAdmin($userId)) return;
            $targetId = (int)str_replace('admin_do_unban_', '', $data);
            $this->db->unbanUser($targetId);
            $this->answerCallback($callback['id'], "✅ User unbanned!");
            $this->sendAdminUserInfo($chatId, $messageId, $targetId);
            return;
        }
        
        // --- Force Subscribe Settings ---
        if ($data === 'admin_forcesub') {
            if (!$this->isAdmin($userId)) return;
            
            $enabled = $this->db->getGlobalSetting('force_subscribe_enabled', '0') === '1';
            $chatIdFs = $this->db->getGlobalSetting('force_subscribe_chat_id', '');
            $link = $this->db->getGlobalSetting('force_subscribe_link', '');
            
            $statusText = $enabled ? "✅ Enabled" : "🔴 Disabled";
            $channelText = $chatIdFs ? "<code>{$chatIdFs}</code>" : "Not set";
            $linkText = $link ? "<a href=\"{$link}\">Link</a>" : "Not set";
            
            $text = "🔗 <b>Force Subscribe</b>\n\n" .
                    "Require users to join a channel before using the bot.\n\n" .
                    "├ Status: {$statusText}\n" . 
                    "├ Channel ID: {$channelText}\n" .
                    "└ Invite Link: {$linkText}";
            
            $toggleText = $enabled ? '🔴 Disable' : '✅ Enable';
            
            $this->editMessage($chatId, $messageId, $text, [
                'inline_keyboard' => [
                    [['text' => $toggleText, 'callback_data' => 'admin_fs_toggle']],
                    [['text' => '📢 Set Channel ID', 'callback_data' => 'admin_fs_setchannel']],
                    [['text' => '🔙 Back', 'callback_data' => 'admin_panel']]
                ]
            ]);
            return;
        }
        
        if ($data === 'admin_fs_toggle') {
            if (!$this->isAdmin($userId)) return;
            $current = $this->db->getGlobalSetting('force_subscribe_enabled', '0') === '1';
            $this->db->setGlobalSetting('force_subscribe_enabled', !$current);
            $this->answerCallback($callback['id'], $current ? "Force subscribe disabled!" : "Force subscribe enabled!");
            // Refresh the page
            $this->handleCallback(array_merge($callback, ['data' => 'admin_forcesub']));
            return;
        }
        
        if ($data === 'admin_fs_setchannel') {
            if (!$this->isAdmin($userId)) return;
            $this->db->setUserState($userId, 'ADMIN_FS_SETCHANNEL', ['message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "📢 <b>Set Force Subscribe Channel</b>\n\nSend me the channel ID (e.g., <code>-1001234567890</code>).\n\n<i>The bot must be an admin in that channel to generate an invite link.</i>", [
                'inline_keyboard' => [[['text' => '❌ Cancel', 'callback_data' => 'admin_forcesub']]]
            ]);
            return;
        }

        // --- Add Admin (Owner only) ---
        if ($data === 'admin_add_admin') {
            if ($userId !== $this->ownerId) {
                $this->answerCallback($callback['id'], "🚫 Only the owner can add admins.");
                return;
            }
            $this->db->setUserState($userId, 'ADMIN_ADD_ADMIN', ['message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, "➕ <b>Add Admin</b>\n\nSend me the Telegram user ID to add as admin.\n\n<i>The user ID is a numeric ID like <code>123456789</code>.</i>\n", [
                'inline_keyboard' => [[['text' => '❌ Cancel', 'callback_data' => 'admin_panel']]]
            ]);
            return;
        }
        
        // --- Restart Bot ---
        if ($data === 'admin_restart') {
            if (!$this->isAdmin($userId)) return;
            $this->editMessage($chatId, $messageId, "🔄 <b>Restart Bot</b>\n\nThis will clear all user states and reset the bot's session.\n\n<b>Are you sure?</b>", [
                'inline_keyboard' => [
                    [['text' => '✅ Yes, Restart', 'callback_data' => 'admin_restart_confirm']],
                    [['text' => '❌ Cancel', 'callback_data' => 'admin_panel']]
                ]
            ]);
            return;
        }
        
        if ($data === 'admin_restart_confirm') {
            if (!$this->isAdmin($userId)) return;
            // Clear all user states (soft restart)
            $this->db->clearAllUserStates();
            $this->answerCallback($callback['id'], "✅ Bot restarted successfully!");
            $this->editMessage($chatId, $messageId, "✅ <b>Bot Restarted</b>\n\nAll user states have been cleared.\nThe bot is now running fresh.", [
                'inline_keyboard' => [[['text' => '🔙 Back to Admin Panel', 'callback_data' => 'admin_panel']]]
            ]);
            return;
        }


        // --- Approve Requests ---
        if (str_starts_with($data, 'action_approve_')) {
            $targetChatId = (int)str_replace('action_approve_', '', $data);
            
            // Security: Check owner
            $channel = $this->db->getChannel($targetChatId);
            if (!$channel || $channel['owner_id'] != $userId) {
                $this->answerCallback($callback['id'], "🚫 Access Denied.");
                return;
            }
            
            // Get pending requests from DB
            $pending = $this->db->getPendingJoinRequests($targetChatId);
            
            // Get fresh channel title from Telegram (ensures Unicode names are properly stored and displayed)
            $title = htmlspecialchars($this->getChannelTitle($targetChatId, true), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
            
            // Skip the member status check loop to avoid webhook timeout
            // The check will be done during the actual approval process
            
            if (empty($pending)) {
                $this->editMessage($chatId, $messageId, "👥 <b>Approve requests</b>\n📢 <b>$title</b>\n\nNo pending join requests.", [
                    'inline_keyboard' => [[['text' => '🔙 Back', 'callback_data' => 'manage_' . $targetChatId]]]
                ]);
                return;
            }
            
            $count = count($pending);
            $text = "👥 <b>Approve requests</b>\n📢 <b>$title</b>\n\n<b>$count</b> pending request(s).\n\nClick the button below to approve all pending requests.";
            
            $keyboard = [
                'inline_keyboard' => [
                    [['text' => '✅ Approve All (' . $count . ')', 'callback_data' => 'approve_all_' . $targetChatId]],
                    [['text' => '🔙 Back', 'callback_data' => 'manage_' . $targetChatId]]
                ]
            ];
            
            $this->editMessage($chatId, $messageId, $text, $keyboard);
            return;
        }
        
        if (str_starts_with($data, 'approve_all_')) {
            $targetChatId = (int)str_replace('approve_all_', '', $data);
            
            // Security: Check owner
            $channel = $this->db->getChannel($targetChatId);
            if (!$channel || $channel['owner_id'] != $userId) {
                $this->answerCallback($callback['id'], "🚫 Access Denied.");
                return;
            }
            
            // Answer callback immediately to prevent timeout
            $this->answerCallback($callback['id'], "⏳ Processing approvals...");
            
            // Get pending requests from DB
            $dbPending = $this->db->getPendingJoinRequests($targetChatId);
            $approvedCount = 0;
            $alreadyMemberCount = 0;
            $failedCount = 0;
            
            // Get fresh channel title (uses cached version, no API call)
            $title = htmlspecialchars($this->getChannelTitle($targetChatId), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
            
            // Update message to show processing
            $this->editMessage($chatId, $messageId, "👥 <b>Approve requests</b>\n📢 <b>$title</b>\n\n⏳ Processing " . count($dbPending) . " request(s)...", []);
            
            foreach ($dbPending as $req) {
                try {
                    // Try to approve the join request
                    $this->approveChatJoinRequest($targetChatId, $req['user_id']);
                    $approvedCount++;
                } catch (\Exception $e) {
                    $errorMsg = $e->getMessage();
                    
                    // Check if user is already a member (common case)
                    if (strpos($errorMsg, 'USER_ALREADY_PARTICIPANT') !== false) {
                        // User is already a member, just update DB status
                        $this->db->approveJoinRequest($targetChatId, $req['user_id']);
                        $alreadyMemberCount++;
                    } else {
                        error_log("Failed to approve request for user {$req['user_id']} in chat $targetChatId: " . $errorMsg);
                        $failedCount++;
                    }
                }
            }
            
            // Build result message
            $resultText = "👥 <b>Approve requests</b>\n📢 <b>$title</b>\n\n";
            
            if ($approvedCount > 0) {
                $resultText .= "✅ Successfully approved <b>$approvedCount</b> request(s).\n";
            }
            
            if ($alreadyMemberCount > 0) {
                $resultText .= "\n<i>📝 Note: $alreadyMemberCount user(s) were already members.</i>\n";
            }
            
            if ($failedCount > 0) {
                $resultText .= "\n<i>⚠️ Failed to approve $failedCount request(s). They may have canceled their request.</i>\n";
            }
            
            if ($approvedCount === 0 && $alreadyMemberCount === 0 && $failedCount === 0) {
                $resultText .= "No pending requests found.";
            }
            
            $this->editMessage($chatId, $messageId, trim($resultText), [
                'inline_keyboard' => [[['text' => '🔙 Back', 'callback_data' => 'manage_' . $targetChatId]]]
            ]);
            return;
        }


        // --- Remove Channel ---
        if (str_starts_with($data, 'action_remove_')) {
            $targetChatId = (int)str_replace('action_remove_', '', $data);
            
            // Security: Check owner
            $channel = $this->db->getChannel($targetChatId);
            if (!$channel || $channel['owner_id'] != $userId) {
                $this->answerCallback($callback['id'], "🚫 Access Denied.");
                return;
            }
            
            $title = htmlspecialchars($channel['title'] ?? 'Unknown', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
            $text = "🗑 <b>Remove channel</b>\n📢 <b>$title</b>\n\n⚠️ Are you sure you want to remove this channel from the bot?\n\nThis will delete all settings, greetings, farewells, and pending requests for this channel.";
            
            $keyboard = [
                'inline_keyboard' => [
                    [['text' => '⚠️ Yes, Remove Channel', 'callback_data' => 'confirm_remove_' . $targetChatId]],
                    [['text' => '🔙 Cancel', 'callback_data' => 'manage_' . $targetChatId]]
                ]
            ];
            
            $this->editMessage($chatId, $messageId, $text, $keyboard);
            return;
        }
        
        if (str_starts_with($data, 'confirm_remove_')) {
            $targetChatId = (int)str_replace('confirm_remove_', '', $data);
            
            // Security: Check owner
            $channel = $this->db->getChannel($targetChatId);
            if (!$channel || $channel['owner_id'] != $userId) {
                $this->answerCallback($callback['id'], "🚫 Access Denied.");
                return;
            }
            
            $title = $channel['title'] ?? 'Unknown';
            
            // Remove channel from database
            $this->db->removeChannel($targetChatId);
            
            $this->answerCallback($callback['id'], "✅ Channel removed!");
            
            // Go back to channel list
            $this->sendMyChannels($chatId, $userId, $messageId);
            return;
        }

        // --- Add Channel Help ---
        if ($data === 'add_channel_help') {
            $botUsername = $this->getBotUsername();
            $groupLink = "https://t.me/{$botUsername}?startgroup&admin=change_info+invite_users+manage_chat";
            $channelLink = "https://t.me/{$botUsername}?startchannel&admin=change_info+invite_users+manage_chat";
            $this->editMessage($chatId, $messageId,
                "<b>📢 Add to Group or Channel</b>\n\n" .
                "Tap a button below to see your groups or channels and add the bot directly.\n\n" .
                "<i>The bot will be added as an admin with the required permissions.</i>", [
                'inline_keyboard' => [
                    [['text' => '👥 Add to Group', 'url' => $groupLink]],
                    [['text' => '📢 Add to Channel', 'url' => $channelLink]],
                    [['text' => '🔙 Back', 'callback_data' => 'back_main']]
                ]
            ]);
            return;
        }
        
        // --- Add Channel via Forward ---
        if ($data === 'add_channel_forward') {
            $this->db->setUserState($userId, 'AWAITING_CHANNEL_FORWARD', ['message_id' => $messageId]);
            $this->editMessage($chatId, $messageId, 
                "<b>📨 Add Channel via Forward</b>\n\n" .
                "Forward any message from your channel here.\n\n" .
                "<b>Requirements:</b>\n" .
                "• You must be an admin of the channel\n" .
                "• The bot must be added as admin first\n\n" .
                "<i>Forward a message now...</i>", 
                ['inline_keyboard' => [[['text' => '❌ Cancel', 'callback_data' => 'cancel_state']]]]
            );
            return;
        }
    }

    private function sendMainMenu($chatId, $messageId = null) {
    $text = "🤖 <b>Auto Approval Bot</b>\n\n" .
            "Welcome! This bot helps you manage Telegram channels with:\n\n" .
            "✅ <b>Auto-Approve</b> - Instantly approve join requests\n" .
            "👋 <b>Greetings/Farewells</b> - Custom welcome & goodbye messages\n" .
            "🛡 <b>Protection</b> - CAPTCHA, language filters, RTL blocking\n" .
            "📢 <b>Broadcast</b> - Send messages to all users/channels\n" .
            "⏰ <b>Deferred Approval</b> - Approve after a delay\n\n" .
            "<i>Use the menu below to get started!</i>";
    $keyboard = [
        'inline_keyboard' => [
            [['text' => '📢 My Channels', 'callback_data' => 'my_channels']],
            [['text' => '➕ Add Channel to Bot', 'callback_data' => 'add_channel_help']],
            [['text' => 'ℹ️ Bot Info', 'callback_data' => 'bot_info']],
            [['text' => '👤 Admin Panel', 'callback_data' => 'admin_panel']]
        ]
    ];
    
    if (!$this->isAdmin($chatId)) {
         array_pop($keyboard['inline_keyboard']);
    }

    if ($messageId) {
        $this->editMessage($chatId, $messageId, $text, $keyboard);
    } else {
        $this->sendMessage($chatId, $text, $keyboard);
    }
}
    private function sendMyChannels($chatId, $userId, $messageId) {
        $channels = $this->db->getUserChannels($userId);
        
        if (empty($channels)) {
            $this->editMessage($chatId, $messageId, "You haven't added any channels yet.", [
                 'inline_keyboard' => [
                    [['text' => '➕ Add to Group or Channel', 'callback_data' => 'add_channel_help']],
                    [['text' => '🔙 Back', 'callback_data' => 'back_main']]
                ]
            ]);
            return;
        }

        $keyboard = ['inline_keyboard' => []];
        foreach ($channels as $ch) {
            $keyboard['inline_keyboard'][] = [['text' => $ch['title'], 'callback_data' => 'manage_' . $ch['chat_id']]];
        }
        $keyboard['inline_keyboard'][] = [['text' => '🔙 Back', 'callback_data' => 'back_main']];
        $this->editMessage($chatId, $messageId, "Select a channel to manage:", $keyboard);
    }

    // THIS IS NOW THE MAIN DASHBOARD
    private function sendChannelSettings($chatId, $messageId, $targetChatId) {
        $channel = $this->db->getChannel($targetChatId);
        $title = htmlspecialchars($channel['title'] ?? 'Unknown', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        
        // Fetch status for summary
        $acceptEnabled = $this->db->getChannelSetting($targetChatId, 'app_accept_enabled', true);
        $auto = $this->db->getChannelSetting($targetChatId, 'auto_approve', true);
        $deferSeconds = (int)$this->db->getChannelSetting($targetChatId, 'defer_time', 0);
        $captcha = $this->db->getChannelSetting($targetChatId, 'use_captcha', false);
        $filterLangCodes = $this->db->getChannelSetting($targetChatId, 'filter_lang_codes', ''); // Plain text
        $isEnabledLang = !empty($filterLangCodes);
        $filterRtl = $this->db->getChannelSetting($targetChatId, 'filter_rtl', false);
        $filterIdeo = $this->db->getChannelSetting($targetChatId, 'filter_ideographs', false);

        // Format Defer
        $deferStr = $deferSeconds . 's';
        if ($deferSeconds == 0) $deferStr = '0';
        else {
            $h = floor($deferSeconds / 3600);
            $m = floor(($deferSeconds % 3600) / 60);
            $s = $deferSeconds % 60;
            $parts = [];
            if ($h > 0) $parts[] = "{$h}h";
            if ($m > 0) $parts[] = "{$m}m";
            if ($s > 0) $parts[] = "{$s}s";
            $deferStr = implode(' ', $parts);
        }

        $text = "📢 <b>$title</b>\n" .
                "Application accept: " . ($acceptEnabled ? "Enabled" : "Disabled") . "\n" .
                "Auto-approve: " . ($auto ? "Enabled" : "Disabled") . "\n" .
                "Auto-approve defer: $deferStr\n" .
                "Use CAPTCHA: " . ($captcha ? "Enabled" : "Disabled") . "\n" .
                "Language filter: " . ($isEnabledLang ? "Enabled" : "Disabled") . "\n" . 
                "RTL filter: " . ($filterRtl ? "Enabled" : "Disabled") . "\n" .
                "Ideograph filter: " . ($filterIdeo ? "Enabled" : "Disabled");

        $keyboard = [
            'inline_keyboard' => [
                [['text' => '👥 Applications accept', 'callback_data' => 'menu_apps_' . $targetChatId]],
                [['text' => '🛡 Protection', 'callback_data' => 'menu_protect_' . $targetChatId]],
                [['text' => '✏️ Set greetings', 'callback_data' => 'menu_greet_' . $targetChatId]],
                [['text' => '✏️ Set farewells', 'callback_data' => 'menu_farewell_' . $targetChatId]],
                [['text' => '👥 Approve requests', 'callback_data' => 'action_approve_' . $targetChatId]],
                [['text' => '🗑 Remove channel', 'callback_data' => 'action_remove_' . $targetChatId]],
                [['text' => '🔙 Back', 'callback_data' => 'my_channels']]
            ]
        ];

        $this->editMessage($chatId, $messageId, $text, $keyboard);
    }

    private function sendApplicationsAcceptMenu($chatId, $messageId, $targetChatId) {
        $enabled = $this->db->getChannelSetting($targetChatId, 'app_accept_enabled', true);
        $auto = $this->db->getChannelSetting($targetChatId, 'auto_approve', true);
        $deferSeconds = (int)$this->db->getChannelSetting($targetChatId, 'defer_time', 0);
        $captcha = $this->db->getChannelSetting($targetChatId, 'use_captcha', false);
        $interact = $this->db->getChannelSetting($targetChatId, 'accept_interact', false);

        $channel = $this->db->getChannel($targetChatId);
        $title = htmlspecialchars($channel['title'] ?? 'Unknown', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        
        // Format Defer String
        $deferStr = $deferSeconds . 's';
        if ($deferSeconds == 0) $deferStr = '0';
        else {
            // Fancy formatting: e.g. 1h 10m 15s
            // Simple approach:
            $h = floor($deferSeconds / 3600);
            $m = floor(($deferSeconds % 3600) / 60);
            $s = $deferSeconds % 60;
            $parts = [];
            if ($h > 0) $parts[] = "{$h}h";
            if ($m > 0) $parts[] = "{$m}m";
            if ($s > 0) $parts[] = "{$s}s";
            $deferStr = implode(' ', $parts);
        }

        // Base Text
        $text = "👥 <b>Applications accept</b>\n📢 <b>$title</b>\n\n";

        if (!$enabled) {
            $text .= "<b>While the application accept is disabled, the bot will not accept any applications to the channel, regardless of the captcha settings, auto-accept and other settings.</b>\n\n";
        }

        $text .= "Application accept: " . ($enabled ? "Enabled" : "Disabled") . "\n" .
                 "Auto-approve: " . ($auto ? "Enabled" : "Disabled") . "\n";
                 
        if ($auto) {
            $text .= "Auto-approve defer: $deferStr\n";
        }
        
        $text .= "Use CAPTCHA: " . ($captcha ? "Enabled" : "Disabled") . "\n" .
                 "Accept on interact: " . ($interact ? "Enabled" : "Disabled") . "\n\n";
                 
        // Always show this text
        $text .= "You can also specify a \"+\" or \"-\" sign at the end of the invitation link name, and based on this, the bot will accept or ignore requests for this link, regardless of the \"Applications accept\" setting.";

        // Buttons
        $buttons = [];
        // 1. Toggle Accept
        $buttons[] = [['text' => ($enabled ? '✅' : '🔴') . ' ' . ($enabled ? 'Disable' : 'Enable') . ' application accept', 'callback_data' => 'toggle_app_accept_enabled_' . $targetChatId]];
        
        if ($enabled) {
            // SHOW ALL
            $buttons[] = [['text' => ($auto ? '✅' : '🔴') . ' ' . ($auto ? 'Disable' : 'Enable') . ' auto-approve', 'callback_data' => 'toggle_auto_approve_' . $targetChatId]];
            
            // Only show Defer if Auto-Approve is ENABLED
            if ($auto) {
                $buttons[] = [['text' => '⏱ Defer auto-approve', 'callback_data' => 'set_defer_' . $targetChatId]];
            }
            
            $buttons[] = [['text' => ($captcha ? '✅' : '🔴') . ' ' . ($captcha ? 'Disable' : 'Enable') . ' CAPTCHA', 'callback_data' => 'toggle_use_captcha_' . $targetChatId]];
            $buttons[] = [['text' => ($interact ? '✅' : '🔴') . ' ' . ($interact ? 'Disable' : 'Enable') . ' accept on interact', 'callback_data' => 'toggle_accept_interact_' . $targetChatId]];
        } else {
            // SHOW REDUCED (Matches screenshot: Enable CAPTCHA, Disable accept on interact were verified present/absent? 
            // User said: "It should disappear the buttons, how to approve, deferred, auto approve, etc."
            // Screenshot showed: Enable CAPTCHA and Disable accept on interact. 
            // So we KEEP Captcha and Interact toggles, but HIDE Auto-Approve and Defer.
            
            $buttons[] = [['text' => ($captcha ? '✅' : '🔴') . ' ' . ($captcha ? 'Disable' : 'Enable') . ' CAPTCHA', 'callback_data' => 'toggle_use_captcha_' . $targetChatId]];
            $buttons[] = [['text' => ($interact ? '✅' : '🔴') . ' ' . ($interact ? 'Disable' : 'Enable') . ' accept on interact', 'callback_data' => 'toggle_accept_interact_' . $targetChatId]];
        }
        
        $buttons[] = [['text' => '🔙 Back', 'callback_data' => 'manage_' . $targetChatId]];

        $keyboard = ['inline_keyboard' => $buttons];
        
        $this->editMessage($chatId, $messageId, $text, $keyboard);
    }

    private function sendProtectionMenu($chatId, $messageId, $targetChatId) {
        $filterLangCodes = $this->db->getChannelSetting($targetChatId, 'filter_lang_codes', '');
        $filterLang = !empty($filterLangCodes);
        $filterRtlVal = $this->db->getChannelSetting($targetChatId, 'filter_rtl', '0');
        $filterRtl = ($filterRtlVal === '1' || $filterRtlVal === true || $filterRtlVal === 1);
        $filterIdeoVal = $this->db->getChannelSetting($targetChatId, 'filter_ideographs', '0');
        $filterIdeo = ($filterIdeoVal === '1' || $filterIdeoVal === true || $filterIdeoVal === 1);
        $filterLinksVal = $this->db->getChannelSetting($targetChatId, 'filter_links', '0');
        $filterLinks = ($filterLinksVal === '1' || $filterLinksVal === true || $filterLinksVal === 1);
        $refreshTime = date('h:i:s A');

        $channel = $this->db->getChannel($targetChatId);
        $title = htmlspecialchars($channel['title'] ?? 'Unknown', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

        $text = "🛡 <b>Protection</b>\n📢 <b>$title</b>\n\n" .
                ($filterLang ? "✅" : "🔴") . " Language filter (Groups & Channels)\n" .
                ($filterRtl ? "✅" : "🔴") . " RTL symbols filter (Groups & Channels)\n" . 
                ($filterIdeo ? "✅" : "🔴") . " Ideographs filter (Groups & Channels)\n" .
                ($filterLinks ? "✅" : "🔴") . " Link filter (Groups only)\n\n" .
                "<i>🕐 Updated: $refreshTime</i>";

        $keyboard = [
            'inline_keyboard' => [
                [['text' => 'Language filter', 'callback_data' => 'menu_lang_filter_' . $targetChatId]],
                [['text' => ($filterRtl ? '✅' : '🔴') . ' Filter RTL symbols', 'callback_data' => 'toggle_filter_rtl_' . $targetChatId]],
                [['text' => ($filterIdeo ? '✅' : '🔴') . ' Filter ideographs', 'callback_data' => 'toggle_filter_ideographs_' . $targetChatId]],
                [['text' => ($filterLinks ? '✅' : '🔴') . ' Filter links', 'callback_data' => 'toggle_filter_links_' . $targetChatId]],
                [['text' => '🔙 Back', 'callback_data' => 'manage_' . $targetChatId]]
            ]
        ];
        
        $this->editMessage($chatId, $messageId, $text, $keyboard);
    }

    private function sendLanguageFilterMenu($chatId, $messageId, $targetChatId) {
        $this->db->setUserState($this->db->getUser($chatId)['user_id'], 'SET_LANG_FILTER', ['chat_id' => $targetChatId, 'message_id' => $messageId]);

        $currentCodes = $this->db->getChannelSetting($targetChatId, 'filter_lang_codes', '');
        
        $channel = $this->db->getChannel($targetChatId);
        $title = htmlspecialchars($channel['title'] ?? 'Unknown', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

        $text = "<b>Language filter</b>\n" .
                "📢 <b>$title</b>\n\n" .
                "If you want to filter join requests with language, send me accepted language codes. E.g.:\n" .
                "<code>en, ru, de, null</code>\n\n" . 
                "To set languages that should not be accepted, use a minus sign:\n" . 
                "<code>-fr, -ar, -null</code>\n\n" .
                "Current setting: " . ($currentCodes ? "<code>$currentCodes</code>" : "(Disabled)") . "\n\n" .
                "Language table:\n" .
                '<a href="https://telegra.ph/Language-codes-09-16">Link</a>';

        $buttons = [];
        if (!empty($currentCodes)) {
            $buttons[] = [['text' => '❌ Disable filter', 'callback_data' => 'reset_lang_filter_' . $targetChatId]];
        }
        $buttons[] = [['text' => '🔙 Back', 'callback_data' => 'menu_protect_' . $targetChatId]];
        
        $keyboard = ['inline_keyboard' => $buttons];
        
        // We use editMessage usually.
        $this->editMessage($chatId, $messageId, $text, $keyboard);
    }

    private function sendGreetingsMenu($chatId, $messageId, $targetChatId) {
        try {
            $channel = $this->db->getChannel($targetChatId);
            $title = htmlspecialchars($channel['title'] ?? 'Unknown', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
            
            $msgs = $this->db->getChannelMessages($targetChatId, 'greeting');
            
            $text = "<b>Greetings</b>\n" .
                    "In this section you can manage greetings for the channel $title\n\n" .
                    "(I advise you not to put more than 3 greetings under 1 language.)";

            $keyboard = ['inline_keyboard' => []];
            
            if (empty($msgs)) {
                $text .= "\n\nNo greetings set yet.";
            }
            
            foreach ($msgs as $msg) {
                 $defer = ($msg['defer_seconds'] > 0) ? ($msg['defer_seconds'] . 's. ⏱') : '0s. ⏱';
                 $lang = ($msg['language'] === 'all') ? 'all 🌍' : $msg['language'];
                 $content = $msg['text_content'] ?? '[Media]';
                 if (mb_strlen($content, 'UTF-8') > 20) $content = mb_substr($content, 0, 17, 'UTF-8') . '...';
                 $content = htmlspecialchars($content, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
                 
                 $btnText = "$defer | $lang | $content";
                 $keyboard['inline_keyboard'][] = [['text' => $btnText, 'callback_data' => 'greet_edit_' . $msg['id']]];
            }
            
            $keyboard['inline_keyboard'][] = [['text' => '➕ Add greeting', 'callback_data' => 'greet_add_' . $targetChatId]];
            $keyboard['inline_keyboard'][] = [['text' => '🔙 Back', 'callback_data' => 'manage_' . $targetChatId]];

            $this->editMessage($chatId, $messageId, $text, $keyboard);
        } catch (\Exception $e) {
            error_log("Error in sendGreetingsMenu: " . $e->getMessage());
            $this->sendMessage($chatId, "⚠️ Error: " . $e->getMessage());
        }
    }
    
    private function sendGreetingEditMenu($chatId, $messageId, $templateId) {
        $msg = $this->db->getChannelMessage($templateId);
        if (!$msg) {
             $this->answerCallback($messageId, "Message not found."); // ID might be huge, answerCallback uses ID not msgID usually.
             // But here we might be calling from elsewhere. 
             // Actually answerCallback needs callback query ID. We don't have it passed here easily.
             // We'll just return.
             return;
        }
        
        // Prepare info
        $lang = ($msg['language'] === 'all') ? 'all 🌍' : $msg['language'];
        $defer = ($msg['defer_seconds'] > 0) ? $this->formatDuration($msg['defer_seconds']) : 'disabled';
        $del = ($msg['delete_seconds'] > 0) ? $this->formatDuration($msg['delete_seconds']) : 'disabled';
        $rawPreview = $msg['text_content'] ?? '[Media]';
        $contentPreview = mb_substr($rawPreview, 0, 15, 'UTF-8');
        if (mb_strlen($rawPreview, 'UTF-8') > 15) $contentPreview .= '...';
        $contentPreview = htmlspecialchars($contentPreview, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        
        $text = "<b>Greeting</b>\n" .
                "<i>$contentPreview</i>\n\n" .
                "Channel: " . htmlspecialchars($this->db->getChannel($msg['chat_id'])['title'] ?? 'Unknown', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . "\n" .
                "Languages: $lang\n" .
                "Defer: $defer\n\n" .
                "Default settings are set to increase conversions.\n\n" .
                "❗️ IMPORTANT: In order for a subscriber to be saved in the bot, he must press the keyboard button in the greeting (this is a Tg limitation). Therefore, add keyboard buttons.";

        $keyboard = [
            'inline_keyboard' => [
                [['text' => '👁 Preview', 'callback_data' => 'greet_prev_' . $templateId]],
                [['text' => '🔗 Set URL-buttons', 'callback_data' => 'greet_btns_' . $templateId]],
                [['text' => '🌍 Languages: ' . $lang, 'callback_data' => 'greet_lang_' . $templateId]],
                [['text' => '📝 Change content', 'callback_data' => 'greet_content_' . $templateId]],
                [['text' => 'Auto-delete: ' . $del, 'callback_data' => 'greet_autodel_' . $templateId]],
                [['text' => '⏱ Defer: ' . $defer, 'callback_data' => 'greet_defer_' . $templateId]],
                [['text' => '🗑 Delete', 'callback_data' => 'greet_delete_' . $templateId]],
                [['text' => '🔙 Back', 'callback_data' => 'menu_greet_' . $msg['chat_id']]] // Go back to list
            ]
        ];
        
        // If messageId is present, edit. Else send.
        // We usually pass messageId.
        $this->editMessage($chatId, $messageId, $text, $keyboard);
    }
    
    // Helper for duration formatting
    private function formatDuration($seconds) {
        if ($seconds == 0) return '0s';
        $h = floor($seconds / 3600);
        $m = floor(($seconds % 3600) / 60);
        $s = $seconds % 60;
        $parts = [];
        if ($h > 0) $parts[] = "{$h}h";
        if ($m > 0) $parts[] = "{$m}m";
        if ($s > 0) $parts[] = "{$s}s";
        return implode(' ', $parts);
    }
    private function sendFarewellsMenu($chatId, $messageId, $targetChatId) {
        try {
            $channel = $this->db->getChannel($targetChatId);
            $title = htmlspecialchars($channel['title'] ?? 'Unknown', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
            
            $msgs = $this->db->getChannelMessages($targetChatId, 'farewell');
            
            $text = "<b>Farewells</b>\n" .
                    "In this section you can manage farewells for the channel $title\n" .
                    "<i>Note: Farewells work when a user leaves or is removed.</i>\n\n";

            $keyboard = ['inline_keyboard' => []];
            
            if (empty($msgs)) {
                $text .= "No farewells set yet.";
            }
            
            foreach ($msgs as $msg) {
                 $defer = ($msg['defer_seconds'] > 0) ? ($msg['defer_seconds'] . 's. ⏱') : '0s. ⏱';
                 $lang = ($msg['language'] === 'all') ? 'all 🌍' : $msg['language'];
                 $content = $msg['text_content'] ?? '[Media]';
                 if (mb_strlen($content, 'UTF-8') > 20) $content = mb_substr($content, 0, 17, 'UTF-8') . '...';
                 $content = htmlspecialchars($content, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
                 
                 $btnText = "$defer | $lang | $content";
                 $keyboard['inline_keyboard'][] = [['text' => $btnText, 'callback_data' => 'farewell_edit_' . $msg['id']]];
            }
            
            $keyboard['inline_keyboard'][] = [['text' => '➕ Add farewell', 'callback_data' => 'farewell_add_' . $targetChatId]];
            $keyboard['inline_keyboard'][] = [['text' => '🔙 Back', 'callback_data' => 'manage_' . $targetChatId]];

            $this->editMessage($chatId, $messageId, $text, $keyboard);
        } catch (\Exception $e) {
            error_log("Error in sendFarewellsMenu: " . $e->getMessage());
            $this->sendMessage($chatId, "⚠️ Error: " . $e->getMessage());
        }
    }

    private function sendFarewellEditMenu($chatId, $messageId, $templateId) {
        $msg = $this->db->getChannelMessage($templateId);
        if (!$msg) {
             // Handle error
             return;
        }
        
        $lang = ($msg['language'] === 'all') ? 'all 🌍' : $msg['language'];
        $defer = ($msg['defer_seconds'] > 0) ? $this->formatDuration($msg['defer_seconds']) : 'disabled';
        $del = ($msg['delete_seconds'] > 0) ? $this->formatDuration($msg['delete_seconds']) : 'disabled';
        $rawPreview = $msg['text_content'] ?? '[Media]';
        $contentPreview = mb_substr($rawPreview, 0, 15, 'UTF-8');
        if (mb_strlen($rawPreview, 'UTF-8') > 15) $contentPreview .= '...';
        $contentPreview = htmlspecialchars($contentPreview, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        
        $text = "<b>Farewell</b>\n" .
                "<i>$contentPreview</i>\n\n" .
                "Channel: " . htmlspecialchars($this->db->getChannel($msg['chat_id'])['title'] ?? 'Unknown', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . "\n" .
                "Languages: $lang\n" .
                "Defer: $defer\n\n" .
                "Default settings are set to increase conversions.\n\n" .
                "❗️ IMPORTANT: In order for a subscriber to be saved in the bot, he must press the keyboard button in the greeting (this is a Tg limitation). Therefore, add keyboard buttons.";

        $keyboard = [
            'inline_keyboard' => [
                [['text' => '👁 Preview', 'callback_data' => 'farewell_prev_' . $templateId]],
                [['text' => '🔗 Set URL-buttons', 'callback_data' => 'farewell_btns_' . $templateId]],
                [['text' => '🌍 Languages: ' . $lang, 'callback_data' => 'farewell_lang_' . $templateId]],
                [['text' => '📝 Change content', 'callback_data' => 'farewell_content_' . $templateId]],
                [['text' => 'Auto-delete: ' . $del, 'callback_data' => 'farewell_autodel_' . $templateId]],
                [['text' => '⏱ Defer: ' . $defer, 'callback_data' => 'farewell_defer_' . $templateId]],
                [['text' => '🗑 Delete', 'callback_data' => 'farewell_delete_' . $templateId]],
                [['text' => '🔙 Back', 'callback_data' => 'menu_farewell_' . $msg['chat_id']]]
            ]
        ];
        
        $this->editMessage($chatId, $messageId, $text, $keyboard);
    }
    private function sendInteractChallenge($userId, $chatId, $title) {
        $title = htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        $text = "Hello! 👋\n\nTo join <b>$title</b>, please click the button below to verify you are human.";
        $keyboard = ['inline_keyboard' => [[
            ['text' => '✅ Verify & Join', 'callback_data' => 'interact_accept_' . $chatId]
        ]]];
        $this->sendMessage($userId, $text, $keyboard);
    }
    
    private function sendCaptchaChallenge($userId, $chatId, $title) {
        $title = htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        $a = rand(1, 10);
        $b = rand(1, 10);
        $sum = $a + $b;
        
        // Store answer
        $this->db->setUserState($userId, 'CAPTCHA_WAIT', ['chat_id' => $chatId, 'answer' => $sum]);
        
        // Generate options
        $options = [$sum];
        while (count($options) < 4) {
            $r = rand(2, 20);
            if (!in_array($r, $options)) $options[] = $r;
        }
        shuffle($options);
        
        $buttons = [];
        foreach ($options as $opt) {
            $buttons[] = ['text' => (string)$opt, 'callback_data' => 'captcha_ans_' . $chatId . '_' . $opt];
        }
        
        $text = "Hello! 👋\n\nTo join <b>$title</b>, please solve this:\n\n<b>$a + $b = ?</b>";
        $keyboard = ['inline_keyboard' => [$buttons]]; // Single row
        
        $this->sendMessage($userId, $text, $keyboard);
    }

    public function deleteMessage($chatId, $messageId)
    {
        try {
            $this->request('deleteMessage', ['chat_id' => $chatId, 'message_id' => $messageId]);
        } catch (\Exception $e) {
            // Suppress 'message to delete not found' errors
        }
    }
    
    private function handleChatMember(array $update) {
        $chat = $update['chat'];
        $newMember = $update['new_chat_member'];
        $oldMember = $update['old_chat_member'];
        
        $statusNew = $newMember['status'];
        $statusOld = $oldMember['status'];
        
        // Extract the actual User object from ChatMember
        // ChatMember = {user: {id, first_name, ...}, status: 'member'}
        $user = $newMember['user'];
        
        // User joined (became member) — send greeting
        // This covers manual approvals via Telegram UI and any other join mechanism
        // The greeting lock inside sendGreetingToUser prevents double-sends
        if ($statusNew === 'member' 
            && in_array($statusOld, ['left', 'kicked'])) {
            $this->sendGreetingToUser($chat['id'], $user['id']);
        }
        
        // User left or was kicked (works for both channels and groups)
        // For channels and large groups (50+ members) Telegram only sends chat_member updates
        if (($statusNew === 'left' || $statusNew === 'kicked') 
            && in_array($statusOld, ['member', 'administrator', 'restricted'])) {
            $this->sendFarewell($chat['id'], $user);
            
            // Delete greeting lock so user can be greeted again if they rejoin
            $this->db->deleteGreetingLock($chat['id'], $user['id']);
        }
    }

    public function approveChatJoinRequest($chatId, $userId, $skipWelcome = false)
    {
        try {
            $this->request('approveChatJoinRequest', [
                'chat_id' => $chatId,
                'user_id' => $userId
            ]);
        } catch (\Exception $e) {
             // Ignore common harmless errors
             $msg = $e->getMessage();
             if (strpos($msg, 'USER_ALREADY_PARTICIPANT') !== false || 
                 strpos($msg, 'HIDE_REQUESTER_MISSING') !== false) {
                 // proceed to update DB anyway
             } else {
                 error_log("approveChatJoinRequest error: " . $msg);
             }
        }
        
        // Log update
        $this->db->approveJoinRequest($chatId, $userId);
        
        // Send greeting after approval (unless caller handles it separately)
        if (!$skipWelcome) {
            $this->sendGreetingToUser($chatId, $userId);
        }
    }
    
    private function declineChatJoinRequest($chatId, $userId)
    {
         $this->request('declineChatJoinRequest', [
            'chat_id' => $chatId,
            'user_id' => $userId
        ]);
        $this->db->declineJoinRequest($chatId, $userId);
    }

    public function sendMessage($chatId, $text, $replyMarkup = null)
    {
        $text = $this->normalizeText($text);
        $data = ['chat_id' => $chatId, 'text' => $text, 'parse_mode' => 'HTML'];
        if ($replyMarkup) {
            $data['reply_markup'] = json_encode($replyMarkup);
        }
        return $this->request('sendMessage', $data);
    }
    
    /**
     * Get chat type (channel/group/supergroup/private)
     * Checks DB first, if not found/set, fetches from Telegram and updates DB
     */
    private function getChatType($chatId) {
        // 1. Try DB
        $type = $this->db->getChannelSetting($chatId, 'chat_type', null);
        if ($type) return $type;
        
        // 2. Fetch from Telegram
        try {
            $chat = $this->request('getChat', ['chat_id' => $chatId]);
            if ($chat && isset($chat['result']['type'])) {
                $type = $chat['result']['type'];
                // Save to DB for future
                $this->db->setChannelSetting($chatId, 'chat_type', $type);
                return $type;
            }
        } catch (\Exception $e) {
            error_log("Failed to get chat type for $chatId: " . $e->getMessage());
        }
        
        // Default fallback (safe for greeting logic -> treats as group to avoid spam)
        return 'group'; 
    }
    
    // === PUBLIC MEDIA SEND METHODS (for broadcast) ===
    
    public function sendPhoto($chatId, $fileId, $caption = null, $replyMarkup = null) {
        $caption = $this->normalizeText($caption);
        $data = ['chat_id' => $chatId, 'photo' => $fileId, 'parse_mode' => 'HTML'];
        if ($caption) $data['caption'] = $caption;
        if ($replyMarkup) $data['reply_markup'] = json_encode($replyMarkup);
        return $this->request('sendPhoto', $data);
    }
    
    public function sendVideo($chatId, $fileId, $caption = null, $replyMarkup = null) {
        $caption = $this->normalizeText($caption);
        $data = ['chat_id' => $chatId, 'video' => $fileId, 'parse_mode' => 'HTML'];
        if ($caption) $data['caption'] = $caption;
        if ($replyMarkup) $data['reply_markup'] = json_encode($replyMarkup);
        return $this->request('sendVideo', $data);
    }
    
    public function sendDocument($chatId, $fileId, $caption = null, $replyMarkup = null) {
        $caption = $this->normalizeText($caption);
        $data = ['chat_id' => $chatId, 'document' => $fileId, 'parse_mode' => 'HTML'];
        if ($caption) $data['caption'] = $caption;
        if ($replyMarkup) $data['reply_markup'] = json_encode($replyMarkup);
        return $this->request('sendDocument', $data);
    }
    
    public function sendAudio($chatId, $fileId, $caption = null, $replyMarkup = null) {
        $caption = $this->normalizeText($caption);
        $data = ['chat_id' => $chatId, 'audio' => $fileId, 'parse_mode' => 'HTML'];
        if ($caption) $data['caption'] = $caption;
        if ($replyMarkup) $data['reply_markup'] = json_encode($replyMarkup);
        return $this->request('sendAudio', $data);
    }
    
    public function sendVoice($chatId, $fileId, $caption = null, $replyMarkup = null) {
        $caption = $this->normalizeText($caption);
        $data = ['chat_id' => $chatId, 'voice' => $fileId, 'parse_mode' => 'HTML'];
        if ($caption) $data['caption'] = $caption;
        if ($replyMarkup) $data['reply_markup'] = json_encode($replyMarkup);
        return $this->request('sendVoice', $data);
    }
    
    public function sendAnimation($chatId, $fileId, $caption = null, $replyMarkup = null) {
        $caption = $this->normalizeText($caption);
        $data = ['chat_id' => $chatId, 'animation' => $fileId, 'parse_mode' => 'HTML'];
        if ($caption) $data['caption'] = $caption;
        if ($replyMarkup) $data['reply_markup'] = json_encode($replyMarkup);
        return $this->request('sendAnimation', $data);
    }
    
    public function sendSticker($chatId, $fileId, $replyMarkup = null) {
        $data = ['chat_id' => $chatId, 'sticker' => $fileId];
        if ($replyMarkup) $data['reply_markup'] = json_encode($replyMarkup);
        return $this->request('sendSticker', $data);
    }


    private function editMessage($chatId, $messageId, $text, $replyMarkup = null)
    {
        try {
            $text = $this->normalizeText($text);
            $data = ['chat_id' => $chatId, 'message_id' => $messageId, 'text' => $text, 'parse_mode' => 'HTML'];
            if ($replyMarkup) {
                $data['reply_markup'] = json_encode($replyMarkup);
            }
            $this->request('editMessageText', $data);
        } catch (\Exception $e) {
            // Suppress 'message is not modified' errors
        }
    }
    
    private function answerCallback($callbackQueryId, $text = null, $showAlert = false) {
        $data = ['callback_query_id' => $callbackQueryId];
        if ($text) {
            $data['text'] = $text;
            $data['show_alert'] = $showAlert;
        }
        $this->request('answerCallbackQuery', $data);
    }

    /**
     * Get the HTTP status code of the last failed API request.
     * Returns null if the last request succeeded.
     */
    public function getLastErrorCode(): ?int
    {
        return $this->lastErrorCode;
    }

    private function request($method, $data)
    {
        $this->lastErrorCode = null;
        // In broadcast mode: fewer retries, shorter sleeps to avoid hosting timeout
        $maxRetries = $this->broadcastMode ? 2 : 3;
        $attempt = 0;
        
        while ($attempt < $maxRetries) {
            $attempt++;
            try {
                $response = $this->httpClient->post($this->apiUrl . $method, ['json' => $data]);
                return json_decode($response->getBody(), true);
            } catch (\GuzzleHttp\Exception\ClientException $e) {
                // 4xx Errors
                $response = $e->getResponse();
                $statusCode = $response->getStatusCode();
                
                // Handle Rate Limiting (429)
                if ($statusCode === 429) {
                    $retryAfter = (int)$response->getHeaderLine('Retry-After');
                    if (!$retryAfter) {
                        try {
                            $json = json_decode((string)$response->getBody(), true);
                            if (isset($json['parameters']['retry_after'])) {
                                $retryAfter = (int)$json['parameters']['retry_after'];
                            }
                        } catch (\Exception $ex) {}
                    }
                    
                    $retryAfter = $retryAfter ?: 10;
                    // Broadcast mode: max 3s sleep to avoid hosting timeout
                    // Normal mode: max 60s sleep
                    $maxSleep = $this->broadcastMode ? 3 : 60;
                    $sleepTime = min($retryAfter, $maxSleep);
                    
                    error_log("Rate limit hit ($method). Sleeping {$sleepTime}s (Attempt $attempt/$maxRetries)...");
                    sleep($sleepTime);
                    continue; // Retry
                }
                
                // Log and Fail for other 4xx errors
                $this->lastErrorCode = $statusCode;
                $errorMsg = $e->getMessage();
                $this->logError($errorMsg);
                return null;
                
            } catch (\Exception $e) {
                // 5xx or Connection Errors - Retry connection issues
                $retrySleep = $this->broadcastMode ? 1 : 2;
                if ($attempt < $maxRetries && ($e instanceof \GuzzleHttp\Exception\ConnectException || $e instanceof \GuzzleHttp\Exception\ServerException)) {
                    sleep($retrySleep);
                    continue;
                }
                
                $errorMsg = $e->getMessage();
                $this->logError($errorMsg);
                return null; 
            }
        }
        
        return null;
    }

    private function logError($errorMsg) {
        // Suppress expected/harmless errors from terminal AND file logging
        $suppressPatterns = [
            'message to delete not found',
            'bot can\'t initiate conversation',
            'message is not modified',
            'query is too old',
            'chat not found',
            'user not found',
            'bots can\'t send messages to bots',
            'group chat was deactivated'
        ];
        
        foreach ($suppressPatterns as $pattern) {
            if (stripos($errorMsg, $pattern) !== false) {
                return; // completely ignore
            }
        }
        
        // Always log to file for debugging
        $msg = date('Y-m-d H:i:s') . ' ' . $errorMsg . PHP_EOL;
        @file_put_contents(__DIR__ . '/../logs/error.log', $msg, FILE_APPEND);
        
        error_log("API Request Error: " . $errorMsg);
    }

    /**
     * Normalize text to valid UTF-8 string.
     * Returns null if input was null.
     */
    private function normalizeText($text) {
        if ($text === null) return null;
        $text = (string)$text;
        if (mb_check_encoding($text, 'UTF-8')) {
            return $text;
        }
        // Try to strip invalid bytes
        $converted = @iconv('UTF-8', 'UTF-8//IGNORE', $text);
        if ($converted !== false) return $converted;
        // Fallback: attempt mb_convert_encoding
        return mb_convert_encoding($text, 'UTF-8', 'UTF-8');
    }
    
    /**
     * Get chat member status for a user in a channel/group
     * @return string|null Returns status (creator, administrator, member, restricted, left, kicked) or null on error
     */
    private function getChatMemberStatus($chatId, $userId): ?string
    {
        try {
            $response = $this->httpClient->post($this->apiUrl . 'getChatMember', [
                'json' => [
                    'chat_id' => $chatId,
                    'user_id' => $userId
                ]
            ]);
            $result = json_decode($response->getBody(), true);
            if ($result && $result['ok'] && isset($result['result']['status'])) {
                return $result['result']['status'];
            }
        } catch (\Exception $e) {
            // User not found or other error - treat as not a member
            error_log("getChatMember error for user $userId in chat $chatId: " . $e->getMessage());
        }
        return null;
    }
    
    /**
     * Get channel title with automatic refresh from Telegram API if needed
     * This ensures the title is always up-to-date, even if changed on Telegram
     * @param int $chatId The chat ID
     * @param bool $forceRefresh Force refresh from Telegram API (default: false)
     * @return string The channel title or 'Unknown' if not found
     */
    private function getChannelTitle(int $chatId, bool $forceRefresh = false): string
    {
        // Get channel from database
        $channel = $this->db->getChannel($chatId);
        
        // If channel doesn't exist in DB, return Unknown
        if (!$channel) {
            return 'Unknown';
        }
        
        // If force refresh or title is empty/null, fetch from Telegram
        if ($forceRefresh || empty($channel['title'])) {
            try {
                $response = $this->request('getChat', ['chat_id' => $chatId]);
                if ($response && $response['ok'] && isset($response['result']['title'])) {
                    $newTitle = $response['result']['title'];
                    // Update title in database
                    $this->db->updateChannelTitle($chatId, $newTitle);
                    return $newTitle;
                }
            } catch (\Exception $e) {
                error_log("Failed to refresh channel title for chat $chatId: " . $e->getMessage());
            }
        }
        
        // Return stored title or Unknown
        return $channel['title'] ?? 'Unknown';
    }
    
    private function isAdmin($userId): bool
    {
        return in_array((string)$userId, $this->admins) || (int)$userId === $this->ownerId;
    }
    
    // =========================================
    // ADMIN HELPER METHODS
    // =========================================
    
    /**
     * Generate a visual progress bar
     */
    private function getProgressBar(int $percent): string {
        $filled = (int)($percent / 10);
        $empty = 10 - $filled;
        return str_repeat('▓', $filled) . str_repeat('░', $empty);
    }
    
    /**
     * Send admin user info panel with ban/unban buttons
     */
    private function sendAdminUserInfo($chatId, $messageId, $targetUserId) {
        $user = $this->db->searchUser((string)$targetUserId);
        
        if (!$user) {
            $this->editMessage($chatId, $messageId, "❌ User not found.", [
                'inline_keyboard' => [[['text' => '🔙 Back', 'callback_data' => 'admin_users']]]
            ]);
            return;
        }
        
        $isBanned = $this->db->isUserBanned($targetUserId);
        $banStatus = $isBanned ? "🚫 Banned" : "✅ Active";
        $name = $user['first_name'] ?? 'N/A';
        $username = $user['username'] ? "@{$user['username']}" : 'N/A';
        
        $text = "👤 <b>User Info</b>\n\n" .
                "├ ID: <code>{$user['user_id']}</code>\n" .
                "├ Name: {$name}\n" .
                "├ Username: {$username}\n" .
                "├ Status: {$banStatus}\n" .
                "└ Joined: {$user['created_at']}";
        
        $keyboard = [];
        if ($isBanned) {
            $keyboard[] = [['text' => '✅ Unban User', 'callback_data' => 'admin_do_unban_' . $targetUserId]];
        } else {
            $keyboard[] = [['text' => '🚫 Ban User', 'callback_data' => 'admin_do_ban_' . $targetUserId]];
        }
        $keyboard[] = [['text' => '🔙 Back', 'callback_data' => 'admin_users']];
        
        $this->editMessage($chatId, $messageId, $text, ['inline_keyboard' => $keyboard]);
    }
    
    /**
     * Check if force subscribe is required and user is not subscribed
     * @return bool True if user can proceed, False if blocked
     */
    private function checkForceSubscribe($userId, $chatId): bool {
        // Skip for admins
        if ($this->isAdmin($userId)) return true;
        
        $enabled = $this->db->getGlobalSetting('force_subscribe_enabled', '0') === '1';
        if (!$enabled) return true;
        
        $fsChannelId = $this->db->getGlobalSetting('force_subscribe_chat_id', '');
        if (empty($fsChannelId)) return true;
        
        // Check if user is member
        // Check if user is member
        // Remove int cast to avoid overflow on 32-bit systems for large channel IDs
        $status = $this->getChatMemberStatus($fsChannelId, $userId);
        if (in_array($status, ['member', 'administrator', 'creator', 'restricted'])) {
            return true;
        }
        
        // User is not subscribed - send force subscribe message
        $link = $this->db->getGlobalSetting('force_subscribe_link', '');
        $text = "🔒 <b>Join Required</b>\n\nTo use this bot, you must first join our channel.\n\nClick the button below to join, then come back and click ✅ Verify.";
        
        $keyboard = [];
        if (!empty($link)) {
            $keyboard[] = [['text' => '📢 Join Channel', 'url' => $link]];
        }
        $keyboard[] = [['text' => '✅ Verify', 'callback_data' => 'verify_forcesub']];
        
        $this->sendMessage($chatId, $text, ['inline_keyboard' => $keyboard]);
        return false;
    }
    
    /**
     * Get the bot's username for deep links
     */
    private function getBotUsername(): string {
        try {
            $response = $this->httpClient->post($this->apiUrl . 'getMe', ['json' => []]);
            $result = json_decode($response->getBody(), true);
            if ($result && $result['ok'] && isset($result['result']['username'])) {
                return $result['result']['username'];
            }
        } catch (\Exception $e) {}
        return 'bot'; // Fallback
    }
    
    /**
     * Process a pending join request after user starts the bot
     */
    private function processPendingJoinRequest($userId, $targetChatId, $userChatId): void {
        // Get channel info
        $channel = $this->db->getChannel($targetChatId);
        $channelTitle = $channel['title'] ?? 'the channel';
        
        // Check if interact or captcha is enabled
        $interact = $this->db->getChannelSetting($targetChatId, 'accept_interact', false);
        if ($interact) {
            $this->sendInteractChallenge($userId, $targetChatId, $channelTitle);
            return;
        }
        
        $captcha = $this->db->getChannelSetting($targetChatId, 'use_captcha', false);
        if ($captcha) {
            $this->sendCaptchaChallenge($userId, $targetChatId, $channelTitle);
            return;
        }
        
        // Auto-approve (greeting is sent inside approveChatJoinRequest)
        $this->approveChatJoinRequest($targetChatId, $userId);
        
        // Update status in database (from pending_start to approved)
        $this->db->approveJoinRequest($targetChatId, $userId);
    }
    
    /**
     * Trigger the broadcast processor script asynchronously.
     * Uses multiple methods for maximum shared hosting compatibility.
     */
    public function triggerBroadcastProcessor(): void
    {
        $siteUrl = $_ENV['SITE_URL'] ?? '';
        $cronSecret = $_ENV['CRON_SECRET'] ?? '';
        if (empty($siteUrl)) return;
        
        $url = rtrim($siteUrl, '/') . '/process_broadcast.php?key=' . urlencode($cronSecret);
        $this->triggerAsyncUrl($url);
    }
    
    /**
     * Fire-and-forget HTTP request using multiple methods.
     * Tries cURL → fsockopen → file_get_contents for maximum compatibility.
     */
    private function triggerAsyncUrl(string $url): void
    {
        // Method 1: cURL (most compatible with shared hosting like Hostinger)
        if (function_exists('curl_init')) {
            $ch = curl_init();
            curl_setopt_array($ch, [
                CURLOPT_URL => $url,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT => 2,
                CURLOPT_CONNECTTIMEOUT => 2,
                CURLOPT_NOSIGNAL => 1,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_SSL_VERIFYHOST => 0,
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_USERAGENT => 'BotBroadcastTrigger/1.0',
            ]);
            @curl_exec($ch);
            curl_close($ch);
            return;
        }
        
        // Method 2: fsockopen
        $parts = parse_url($url);
        $host = $parts['host'] ?? 'localhost';
        $scheme = $parts['scheme'] ?? 'http';
        $port = $parts['port'] ?? ($scheme === 'https' ? 443 : 80);
        $path = ($parts['path'] ?? '/') . (isset($parts['query']) ? '?' . $parts['query'] : '');
        
        $fp = @fsockopen(
            ($scheme === 'https' ? 'ssl://' : '') . $host,
            $port, $errno, $errstr, 2
        );
        if ($fp) {
            fwrite($fp, "GET $path HTTP/1.1\r\nHost: $host\r\nConnection: Close\r\n\r\n");
            fclose($fp);
            return;
        }
        
        // Method 3: file_get_contents with short timeout
        $ctx = stream_context_create([
            'http' => ['timeout' => 2, 'ignore_errors' => true],
            'ssl' => ['verify_peer' => false, 'verify_peer_name' => false]
        ]);
        @file_get_contents($url, false, $ctx);
    }
}
