<?php
// whatsapp-bot/bot.php - WhatsApp Bot Webhook Handler (wifi2fm.com API)
// Handles only: شراء البطاقات (buy cards) flow.

declare(strict_types=1);

// ─────────────────────────────────────────────────────────────
// Configuration
// ─────────────────────────────────────────────────────────────

function loadBotConfig(): array
{
    $configPath = __DIR__ . '/bot.config.json';
    if (!is_file($configPath)) {
        throw new RuntimeException("Missing bot config: {$configPath}");
    }

    $contents = file_get_contents($configPath);
    if ($contents === false) {
        throw new RuntimeException("Failed to read bot config: {$configPath}");
    }

    $decoded = json_decode($contents, true);
    if (!is_array($decoded)) {
        throw new RuntimeException("Invalid bot config JSON: {$configPath}");
    }

    return $decoded;
}

function validateBotConfig(array $config): void
{
    $instanceId  = trim((string) ($config['instance_id']  ?? ''));
    $accessToken = trim((string) ($config['access_token'] ?? ''));

    if ($instanceId === '' || $accessToken === '') {
        throw new RuntimeException('Missing wifi2fm credentials: set instance_id and access_token in whatsapp-bot/bot.config.json');
    }
}

function validateIncomingWebhookSecret(array $config, array $payload): bool
{
    $expectedSecret = trim((string) ($config['webhook_secret'] ?? ''));
    if ($expectedSecret === '') {
        return true;
    }

    $headerSecret  = trim((string) ($_SERVER['HTTP_X_WEBHOOK_SECRET'] ?? $_SERVER['HTTP_X_WIFI2FM_WEBHOOK_SECRET'] ?? ''));
    $querySecret   = trim((string) ($_GET['secret'] ?? ''));
    $payloadSecret = trim((string) ($payload['webhook_secret'] ?? $payload['secret'] ?? ''));

    foreach ([$headerSecret, $querySecret, $payloadSecret] as $provided) {
        if ($provided !== '' && hash_equals($expectedSecret, $provided)) {
            return true;
        }
    }

    return false;
}

function toWebhookBool(mixed $value): bool
{
    if (is_bool($value))  return $value;
    if (is_int($value) || is_float($value)) return (int) $value !== 0;
    if (is_string($value)) {
        $n = strtolower(trim($value));
        if (in_array($n, ['1','true','yes','on'], true))          return true;
        if (in_array($n, ['0','false','no','off',''], true))      return false;
    }
    return (bool) $value;
}

function extractBaileysText(array $messageNode): string
{
    if (isset($messageNode['conversation']) && is_string($messageNode['conversation']))
        return $messageNode['conversation'];
    if (isset($messageNode['extendedTextMessage']['text']) && is_string($messageNode['extendedTextMessage']['text']))
        return $messageNode['extendedTextMessage']['text'];
    if (isset($messageNode['imageMessage']['caption']) && is_string($messageNode['imageMessage']['caption']))
        return $messageNode['imageMessage']['caption'];
    if (isset($messageNode['videoMessage']['caption']) && is_string($messageNode['videoMessage']['caption']))
        return $messageNode['videoMessage']['caption'];
    return '';
}

function isGroupJid(string $jid): bool
{
    return str_contains($jid, '@g.us') || str_contains($jid, '@broadcast') || str_contains($jid, '@newsletter');
}

function isMessageUpdateEvent(array $payload): bool
{
    $data      = (array) ($payload['data'] ?? []);
    $eventType = (string) ($payload['event_type'] ?? $payload['event'] ?? $payload['type']
        ?? $data['event'] ?? $data['event_type'] ?? '');
    return in_array($eventType, ['messages.update', 'message.update'], true);
}

// ─────────────────────────────────────────────────────────────
// Payload Normalisation
// ─────────────────────────────────────────────────────────────

function normalizeIncomingWebhookPayload(array $payload): ?array
{
    $data      = (array) ($payload['data'] ?? []);
    $eventType = (string) ($data['event'] ?? $data['event_type']
        ?? $payload['event_type'] ?? $payload['event'] ?? $payload['type'] ?? '');

    if (in_array($eventType, [
        'test','ping','connection','connection.update',
        'contacts.update','contacts.upsert',
        'groups.update','group-participants.update',
        'chats.update','chats.upsert','presence.update',
    ], true) || !empty($payload['test'])) {
        return null;
    }

    if (in_array($eventType, ['messages.upsert','message.upsert'], true)) {
        $innerData = (array) ($data['data'] ?? []);
        if (isset($innerData['messages']) && is_array($innerData['messages']))
            return parseBaileysUpsert($eventType, $innerData);
        if (isset($data['messages']) && is_array($data['messages']))
            return parseBaileysUpsert($eventType, $data);
        return null;
    }

    if (in_array($eventType, ['received_message','webhook_message_received','message_received'], true)) {
        $messageObj = (array) ($data['message'] ?? []);
        if ($messageObj !== [])
            return parseReceivedMessage($eventType, $messageObj);
        if (isset($data['from']) && is_string($data['from']) && $data['from'] !== '')
            return parseLegacyClassicFormat($eventType, $data);
        return null;
    }

    $innerData = (array) ($data['data'] ?? []);
    if (isset($innerData['messages']) && is_array($innerData['messages']) && !empty($innerData['messages']))
        return parseBaileysUpsert($eventType ?: 'messages.upsert', $innerData);
    if (isset($data['messages']) && is_array($data['messages']) && !empty($data['messages']))
        return parseBaileysUpsert($eventType ?: 'messages.upsert', $data);

    if (isset($data['message']) && is_array($data['message'])) {
        $messageObj = (array) $data['message'];
        if (isset($messageObj['message_key']) || isset($messageObj['body_message']))
            return parseReceivedMessage($eventType ?: 'received_message', $messageObj);
    }

    if (isset($data['from']) && is_string($data['from']) && $data['from'] !== '')
        return parseLegacyClassicFormat($eventType ?: 'message_received', $data);

    if (isset($payload['key']['remoteJid']) || isset($payload['message'])) {
        $key         = (array) ($payload['key']     ?? []);
        $messageNode = (array) ($payload['message'] ?? []);
        $remoteJid   = (string) ($key['remoteJid'] ?? '');
        $senderPhone = str_replace(['@c.us','@s.whatsapp.net'], '', $remoteJid);

        $msgType = 'chat'; $imageUrl = ''; $imageBase64 = ''; $mediaKey = ''; $directPath = '';
        if (isset($messageNode['imageMessage'])) {
            $msgType = 'image';
            $im = (array) $messageNode['imageMessage'];
            $imageUrl    = (string) ($im['url']           ?? '');
            $imageBase64 = (string) ($im['jpegThumbnail'] ?? '');
            $mediaKey    = (string) ($im['mediaKey']      ?? '');
            $directPath  = (string) ($im['directPath']    ?? '');
        } elseif (isset($messageNode['documentMessage'])) {
            $msgType = 'document';
        }

        return [
            'event_type'   => $eventType ?: 'messages.upsert',
            'from_me'      => toWebhookBool($key['fromMe'] ?? false),
            'sender_phone' => $senderPhone,
            'remote_jid'   => $remoteJid,
            'id'           => (string) ($key['id'] ?? ''),
            'msg_type'     => $msgType,
            'body'         => extractBaileysText($messageNode),
            'caption'      => '',
            'image_url'    => $imageUrl,
            'image_base64' => $imageBase64,
            'media_key'    => $mediaKey,
            'direct_path'  => $directPath,
            'contact_name' => (string) ($payload['pushName'] ?? ''),
        ];
    }

    return null;
}

function parseBaileysUpsert(string $eventType, array $data): ?array
{
    $messages    = (array) ($data['messages'] ?? []);
    $first       = (array) ($messages[0]      ?? []);
    $key         = (array) ($first['key']     ?? []);
    $messageNode = (array) ($first['message'] ?? []);

    if ($first === [] || $key === []) return null;

    $remoteJid   = (string) ($key['remoteJid'] ?? '');
    $senderPhone = str_replace(['@c.us','@s.whatsapp.net'], '', $remoteJid);

    $participant = (string) ($key['participant'] ?? $key['participantAlt'] ?? '');
    if ($participant !== '' && isGroupJid($remoteJid))
        $senderPhone = str_replace(['@c.us','@s.whatsapp.net'], '', $participant);

    $msgType = 'chat'; $imageUrl = ''; $imageBase64 = ''; $mediaKey = ''; $directPath = '';
    if (isset($messageNode['imageMessage'])) {
        $msgType = 'image';
        $im = (array) $messageNode['imageMessage'];
        $imageUrl    = (string) ($im['url']           ?? '');
        $imageBase64 = (string) ($im['jpegThumbnail'] ?? '');
        $mediaKey    = (string) ($im['mediaKey']      ?? '');
        $directPath  = (string) ($im['directPath']    ?? '');
    } elseif (isset($messageNode['documentMessage'])) {
        $msgType = 'document';
    }

    return [
        'event_type'   => $eventType,
        'from_me'      => toWebhookBool($key['fromMe'] ?? false),
        'sender_phone' => $senderPhone,
        'remote_jid'   => $remoteJid,
        'id'           => (string) ($key['id'] ?? ''),
        'msg_type'     => $msgType,
        'body'         => extractBaileysText($messageNode),
        'caption'      => '',
        'image_url'    => $imageUrl,
        'image_base64' => $imageBase64,
        'media_key'    => $mediaKey,
        'direct_path'  => $directPath,
        'contact_name' => (string) ($first['pushName'] ?? $data['pushName'] ?? ''),
    ];
}

function parseReceivedMessage(string $eventType, array $messageObj): ?array
{
    $bodyMessage = (array) ($messageObj['body_message'] ?? []);
    $messageKey  = (array) ($messageObj['message_key']  ?? []);
    $pushName    = (string) ($messageObj['push_name'] ?? $messageObj['pushName'] ?? '');

    $remoteJid   = (string) ($messageKey['remoteJid'] ?? '');
    $senderPhone = str_replace(['@c.us','@s.whatsapp.net'], '', $remoteJid);

    $participant = (string) ($messageKey['participant'] ?? $messageKey['participantAlt'] ?? '');
    if ($participant !== '' && isGroupJid($remoteJid))
        $senderPhone = str_replace(['@c.us','@s.whatsapp.net'], '', $participant);

    if (isGroupJid($senderPhone . '@g.us') || $senderPhone === '') {
        $fromContact = (string) ($messageObj['from_contact'] ?? '');
        if ($fromContact !== '' && !isGroupJid($fromContact . '@g.us'))
            $senderPhone = str_replace(['@c.us','@s.whatsapp.net'], '', $fromContact);
    }

    $content     = (string) ($bodyMessage['content'] ?? '');
    $msgTypeRaw  = strtolower((string) ($bodyMessage['type'] ?? 'textMessage'));

    $msgType = 'chat'; $imageUrl = ''; $imageBase64 = ''; $mediaKey = ''; $directPath = '';

    if (in_array($msgTypeRaw, ['imagemessage','image'], true)) {
        $msgType    = 'image';
        $innerMsgs  = (array) ($bodyMessage['messages'] ?? []);
        $imgMsg     = (array) ($innerMsgs['imageMessage'] ?? []);
        $imageUrl    = (string) ($imgMsg['url']           ?? '');
        $imageBase64 = (string) ($imgMsg['jpegThumbnail'] ?? '');
        $mediaKey    = (string) ($imgMsg['mediaKey']      ?? '');
        $directPath  = (string) ($imgMsg['directPath']    ?? '');
        if ($content === '' && isset($imgMsg['caption'])) $content = (string) $imgMsg['caption'];

        if (isset($messageObj['media']) && is_string($messageObj['media']) && strlen($messageObj['media']) > 500)
            $imageBase64 = $messageObj['media'];
        elseif (isset($bodyMessage['media']) && is_string($bodyMessage['media']) && strlen($bodyMessage['media']) > 500)
            $imageBase64 = $bodyMessage['media'];
        elseif (isset($messageObj['base64']) && is_string($messageObj['base64']) && strlen($messageObj['base64']) > 500)
            $imageBase64 = $messageObj['base64'];
    } elseif (in_array($msgTypeRaw, ['documentmessage','document'], true)) {
        $msgType = 'document';
    }

    if ($senderPhone === '') return null;

    return [
        'event_type'   => $eventType,
        'from_me'      => toWebhookBool($messageKey['fromMe'] ?? false),
        'sender_phone' => $senderPhone,
        'remote_jid'   => $remoteJid,
        'id'           => (string) ($messageKey['id'] ?? ''),
        'msg_type'     => $msgType,
        'body'         => $content,
        'caption'      => '',
        'image_url'    => $imageUrl,
        'image_base64' => $imageBase64,
        'media_key'    => $mediaKey,
        'direct_path'  => $directPath,
        'contact_name' => $pushName,
    ];
}

function parseLegacyClassicFormat(string $eventType, array $data): ?array
{
    $fromRaw     = (string) ($data['from'] ?? '');
    $senderPhone = str_replace(['@c.us','@s.whatsapp.net'], '', $fromRaw);
    return [
        'event_type'   => $eventType,
        'from_me'      => toWebhookBool($data['fromMe'] ?? false),
        'sender_phone' => $senderPhone,
        'remote_jid'   => $fromRaw,
        'id'           => (string) ($data['id']      ?? ''),
        'msg_type'     => (string) ($data['type']    ?? 'chat'),
        'body'         => (string) ($data['body']    ?? ''),
        'caption'      => (string) ($data['caption'] ?? ''),
        'image_url'    => (string) ($data['body']    ?? ''),
        'image_base64' => (string) ($data['media']   ?? ''),
        'media_key'    => '',
        'direct_path'  => '',
        'contact_name' => (string) ($data['pushName'] ?? $data['notifyName'] ?? ''),
    ];
}

// ─────────────────────────────────────────────────────────────
// Local Store
// ─────────────────────────────────────────────────────────────

function localStorePath(): string { return __DIR__ . '/storage/bot_state.json'; }

function loadLocalStore(): array
{
    $path = localStorePath();
    if (!is_file($path)) return ['conversations' => [], 'visits' => [], 'orders' => []];
    $raw = file_get_contents($path);
    if ($raw === false || $raw === '') return ['conversations' => [], 'visits' => [], 'orders' => []];
    $decoded = json_decode($raw, true);
    if (!is_array($decoded)) return ['conversations' => [], 'visits' => [], 'orders' => []];
    $decoded['conversations'] = is_array($decoded['conversations'] ?? null) ? $decoded['conversations'] : [];
    $decoded['visits']        = is_array($decoded['visits']        ?? null) ? $decoded['visits']        : [];
    $decoded['orders']        = is_array($decoded['orders']        ?? null) ? $decoded['orders']        : [];
    return $decoded;
}

function saveLocalStore(array $store): void
{
    $dir = __DIR__ . '/storage';
    if (!is_dir($dir)) @mkdir($dir, 0775, true);
    @file_put_contents(localStorePath(), json_encode($store, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}

function trackUserVisitLocal(string $chatId): array
{
    $store  = loadLocalStore();
    $visits = (array) ($store['visits'] ?? []);
    $now    = date('c');

    if (!isset($visits[$chatId]) || !is_array($visits[$chatId])) {
        $visits[$chatId] = ['first_seen_at' => $now, 'last_seen_at' => $now, 'visit_count' => 1];
        $store['visits'] = $visits;
        saveLocalStore($store);
        return ['is_first_time' => true, 'last_seen_at' => null, 'visit_count' => 1];
    }

    $lastSeen    = (string) ($visits[$chatId]['last_seen_at'] ?? '');
    $visitCount  = (int)    ($visits[$chatId]['visit_count']  ?? 0) + 1;
    $visits[$chatId]['last_seen_at'] = $now;
    $visits[$chatId]['visit_count']  = $visitCount;
    $store['visits'] = $visits;
    saveLocalStore($store);
    return ['is_first_time' => false, 'last_seen_at' => $lastSeen, 'visit_count' => $visitCount];
}

function getConversationLocal(string $chatId): ?array
{
    $store = loadLocalStore();
    $conv  = (array) ($store['conversations'][$chatId] ?? []);
    if ($conv === []) return null;
    return [
        'state'   => (string) ($conv['state']   ?? ''),
        'context' => is_array($conv['context'] ?? null) ? $conv['context'] : [],
    ];
}

function saveConversationLocal(string $chatId, string $state, array $context): void
{
    $store = loadLocalStore();
    $store['conversations'][$chatId] = ['state' => $state, 'context' => $context, 'updated_at' => date('c')];
    saveLocalStore($store);
}

function clearConversationLocal(string $chatId): void
{
    $store = loadLocalStore();
    unset($store['conversations'][$chatId]);
    saveLocalStore($store);
}

function resolveMenuSelectionLocal(string $text, array $context): ?string
{
    $pending = (array) ($context['_pending_menu'] ?? []);
    if ($pending === []) return null;

    if (ctype_digit($text)) {
        $idx = (int) $text - 1;
        if (isset($pending[$idx]['id'])) return (string) $pending[$idx]['id'];
    }

    foreach ($pending as $item) {
        $title = trim((string) ($item['title'] ?? ''));
        if ($title !== '' && mb_strtolower($title) === mb_strtolower(trim($text)))
            return (string) ($item['id'] ?? '');
    }

    return null;
}

// ─────────────────────────────────────────────────────────────
// Options
// ─────────────────────────────────────────────────────────────

function localOptionsPath(): string { return __DIR__ . '/options.json'; }

function loadLocalOptions(): array
{
    $default = [
        'voucher_categories' => [],
        'transfer_data'      => ['title' => 'بيانات التحويل', 'lines' => [], 'qr_image_url' => ''],
    ];
    $path = localOptionsPath();
    if (!is_file($path)) return $default;
    $raw = file_get_contents($path);
    if ($raw === false || trim($raw) === '') return $default;
    $decoded = json_decode($raw, true);
    if (!is_array($decoded)) return $default;
    $decoded['voucher_categories'] = is_array($decoded['voucher_categories'] ?? null) ? $decoded['voucher_categories'] : [];
    return $decoded;
}

// ─────────────────────────────────────────────────────────────
// Database / Order Store
// ─────────────────────────────────────────────────────────────

function orderSyncDb(array $config): ?PDO
{
    static $pdo = null;
    if ($pdo instanceof PDO) return $pdo;
    $name = trim((string) ($config['db_name'] ?? ''));
    if ($name === '') return null;

    $pdo = new PDO(
        "mysql:host={$config['db_host']};port={$config['db_port']};dbname={$name};charset=utf8mb4",
        (string) ($config['db_user'] ?? 'root'),
        (string) ($config['db_pass'] ?? ''),
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false]
    );
    return $pdo;
}

function ensureOrderSyncTables(PDO $pdo): void
{
    static $ensured = false;
    if ($ensured) return;

    $pdo->exec("CREATE TABLE IF NOT EXISTS `bot_server_orders` (
        `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        `order_no` VARCHAR(64) NOT NULL,
        `source_chat_id` VARCHAR(40) NOT NULL,
        `source_server_id` VARCHAR(64) NOT NULL,
        `target_server_id` VARCHAR(64) NOT NULL,
        `payload_json` JSON NOT NULL,
        `api_result_json` JSON NULL,
        `sync_status` ENUM('new','fetched','synced') NOT NULL DEFAULT 'new',
        `fetched_at` DATETIME NULL,
        `synced_at` DATETIME NULL,
        `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`),
        UNIQUE KEY `uq_order_no` (`order_no`),
        KEY `idx_target_status` (`target_server_id`, `sync_status`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $ensured = true;
}

/**
 * Block a new order until the previous one has been explicitly accepted or rejected.
 *
 * Decision is detected by:
 *   1. A `bot_order_decision` (or legacy `bot_order_desicon`) table row matching the order_no, OR
 *   2. The order's sync_status being 'synced' (set externally when decision is delivered).
 */
function getPendingOrderForChat(array $config, string $chatId): ?array
{
    try {
        $pdo = orderSyncDb($config);
        if ($pdo instanceof PDO) {
            ensureOrderSyncTables($pdo);

            $decisionTable = null;
            foreach (['bot_order_decision', 'bot_order_desicon'] as $tbl) {
                if (!empty($pdo->query("SHOW TABLES LIKE '{$tbl}'")->fetchAll())) {
                    $decisionTable = $tbl;
                    break;
                }
            }

            if ($decisionTable !== null) {
                $stmt = $pdo->prepare(
                    "SELECT o.`order_no`, o.`payload_json`
                     FROM `bot_server_orders` o
                     LEFT JOIN `{$decisionTable}` d ON d.`order_no` = o.`order_no`
                     WHERE o.`source_chat_id` = ?
                       AND d.`id` IS NULL
                       AND o.`sync_status` != 'synced'
                     ORDER BY o.`id` DESC LIMIT 1"
                );
            } else {
                $stmt = $pdo->prepare(
                    "SELECT `order_no`, `payload_json`
                     FROM `bot_server_orders`
                     WHERE `source_chat_id` = ?
                       AND `sync_status` IN ('new','fetched')
                     ORDER BY `id` DESC LIMIT 1"
                );
            }

            $stmt->execute([$chatId]);
            $row = $stmt->fetch();
            if (is_array($row)) {
                $payload = json_decode((string) ($row['payload_json'] ?? '{}'), true);
                if (!is_array($payload)) $payload = [];
                return ['order_no' => (string) ($row['order_no'] ?? ''), 'customer_name' => trim((string) ($payload['customer_name'] ?? ''))];
            }
            return null;
        }
    } catch (Throwable $e) {}

    // Fallback: local JSON store
    $store  = loadLocalStore();
    $orders = is_array($store['orders'] ?? null) ? $store['orders'] : [];
    foreach (array_reverse($orders) as $order) {
        if (!is_array($order)) continue;
        if ((string) ($order['chat_id'] ?? '') !== $chatId) continue;
        if (in_array(strtolower(trim((string) ($order['status'] ?? 'pending'))), ['pending','new'], true)) {
            return ['order_no' => (string) ($order['order_no'] ?? ''), 'customer_name' => trim((string) ($order['customer_name'] ?? ''))];
        }
    }
    return null;
}

function localOrderStoreAdd(array $config, array $order, array $apiResult = []): void
{
    try {
        $pdo = orderSyncDb($config);
        if ($pdo instanceof PDO) {
            ensureOrderSyncTables($pdo);
            $sourceServerId = trim((string) ($config['server_id']                ?? 'default-whatsapp-server'));
            $targetServerId = trim((string) ($config['orders_target_server_id'] ?? $sourceServerId));
            $stmt = $pdo->prepare(
                'INSERT INTO `bot_server_orders` (`order_no`,`source_chat_id`,`source_server_id`,`target_server_id`,`payload_json`,`api_result_json`,`sync_status`)
                 VALUES (?,?,?,?,?,?,?)
                 ON DUPLICATE KEY UPDATE `payload_json` = VALUES(`payload_json`), `api_result_json` = VALUES(`api_result_json`), `target_server_id` = VALUES(`target_server_id`)'
            );
            $stmt->execute([
                (string) ($order['order_no'] ?? ''),
                (string) ($order['chat_id']  ?? ''),
                $sourceServerId,
                $targetServerId,
                (string) json_encode($order,     JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
                (string) json_encode($apiResult, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
                'new',
            ]);
            return;
        }
    } catch (Throwable $e) {}

    $store           = loadLocalStore();
    $orders          = is_array($store['orders'] ?? null) ? $store['orders'] : [];
    $orders[]        = $order;
    $store['orders'] = $orders;
    saveLocalStore($store);
}

function localOrdersByChat(array $config, string $chatId): array
{
    try {
        $pdo = orderSyncDb($config);
        if ($pdo instanceof PDO) {
            ensureOrderSyncTables($pdo);
            $stmt = $pdo->prepare('SELECT `payload_json` FROM `bot_server_orders` WHERE `source_chat_id` = ? ORDER BY `id` DESC LIMIT 50');
            $stmt->execute([$chatId]);
            $orders = [];
            foreach ($stmt->fetchAll() as $row) {
                $decoded = json_decode((string) ($row['payload_json'] ?? '{}'), true);
                if (is_array($decoded)) $orders[] = $decoded;
            }
            return $orders;
        }
    } catch (Throwable $e) {}

    $store    = loadLocalStore();
    $filtered = [];
    foreach (array_reverse(is_array($store['orders'] ?? null) ? $store['orders'] : []) as $order) {
        if (is_array($order) && (string) ($order['chat_id'] ?? '') === $chatId) $filtered[] = $order;
    }
    return $filtered;
}

function formatLocalOrdersMessage(array $config, string $chatId): string
{
    $orders = localOrdersByChat($config, $chatId);
    if ($orders === []) return '📦 لا توجد طلبات شراء مسجلة حتى الآن.';

    $lines = ['📦 آخر طلبات الشراء الخاصة بك:'];
    foreach (array_slice($orders, 0, 10) as $idx => $order) {
        $lines[] = sprintf('%d) %s | %s x%d | %s',
            $idx + 1,
            (string) ($order['order_no']     ?? ('LOCAL-' . ($idx + 1))),
            (string) ($order['voucher_name'] ?? '-'),
            (int)    ($order['voucher_count'] ?? 0),
            (string) ($order['status']        ?? 'pending')
        );
    }
    return implode("\n", $lines);
}

function localOrderNumber(): string
{
    return 'ORD-' . date('Ymd-His') . '-' . strtoupper(substr(bin2hex(random_bytes(3)), 0, 6));
}

function submitNewOrderApi(array $config, array $order): array
{
    $endpoint = normalizeHttpEndpoint((string) ($config['api_new_order'] ?? ''));
    if ($endpoint === '') return ['status' => 'skipped', 'message' => 'api_new_order is not configured'];
    if (!function_exists('curl_init')) return ['status' => 'error', 'message' => 'cURL extension is not available'];

    $payload = [
        'order_no'          => (string)  ($order['order_no']           ?? ''),
        'server_id'         => (string)  ($config['server_id']         ?? ''),
        'server_token'      => (string)  ($config['server_token']      ?? ''),
        'target_server_id'  => (string)  ($config['orders_target_server_id'] ?? ''),
        'chat_id'           => (string)  ($order['chat_id']            ?? ''),
        'customer_name'     => (string)  ($order['customer_name']      ?? ''),
        'customer_mobile'   => (string)  ($order['customer_mobile']    ?? ''),
        'voucher_id'        => (string)  ($order['voucher_id']         ?? ''),
        'voucher_name'      => (string)  ($order['voucher_name']       ?? ''),
        'voucher_price'     => (float)   ($order['voucher_price']      ?? 0),
        'voucher_currency'  => (string)  ($order['voucher_currency']   ?? 'ILS'),
        'voucher_count'     => (int)     ($order['voucher_count']      ?? 0),
        'receipt'           => [
            'message_id'  => (string) ($order['receipt_message_id']   ?? ''),
            'media_url'   => (string) ($order['receipt_media_url']    ?? ''),
            'media_base64'=> (string) ($order['receipt_media_base64'] ?? ''),
        ],
        'receipt_message_id'   => (string) ($order['receipt_message_id']   ?? ''),
        'receipt_media_url'    => (string) ($order['receipt_media_url']    ?? ''),
        'receipt_media_base64' => (string) ($order['receipt_media_base64'] ?? ''),
        'note'       => (string) ($order['note']       ?? ''),
        'created_at' => (string) ($order['created_at'] ?? date('c')),
    ];

    $body     = (string) json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $attempts = [];

    for ($attempt = 1; $attempt <= 2; $attempt++) {
        $ch = curl_init($endpoint);
        curl_setopt_array($ch, [
            CURLOPT_POST            => true,
            CURLOPT_RETURNTRANSFER  => true,
            CURLOPT_TIMEOUT         => 30,
            CURLOPT_CONNECTTIMEOUT  => 10,
            CURLOPT_IPRESOLVE       => CURL_IPRESOLVE_V4,
            CURLOPT_HTTPHEADER      => [
                'Content-Type: application/json',
                'X-Server-ID: '    . (string) ($config['server_id']    ?? ''),
                'X-Server-Token: ' . (string) ($config['server_token'] ?? ''),
            ],
            CURLOPT_POSTFIELDS => $body,
        ]);
        $response  = curl_exec($ch);
        $httpCode  = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlErrno = (int) curl_errno($ch);
        $curlError = (string) curl_error($ch);
        curl_close($ch);

        $attempts[] = ['attempt' => $attempt, 'http_code' => $httpCode, 'curl_errno' => $curlErrno, 'curl_error' => $curlError];

        if ($response !== false) {
            $decoded = json_decode((string) $response, true);
            if (!is_array($decoded)) return ['status' => 'success', 'http_code' => $httpCode, 'raw' => mb_substr((string) $response, 0, 500), 'request' => ['endpoint' => $endpoint, 'payload' => $payload], 'attempts' => $attempts];
            $decoded['http_code'] = $httpCode;
            $decoded['request']   = ['endpoint' => $endpoint, 'payload' => $payload];
            $decoded['attempts']  = $attempts;
            return $decoded;
        }
        if (!in_array($curlErrno, [CURLE_COULDNT_CONNECT, CURLE_OPERATION_TIMEDOUT], true)) break;
    }

    $last    = end($attempts);
    $lastMsg = (string) ($last['curl_error'] ?? 'failed to submit');
    return ['status' => 'error', 'message' => $lastMsg, 'request' => ['endpoint' => $endpoint, 'payload' => $payload], 'attempts' => $attempts];
}

function normalizeHttpEndpoint(string $endpoint): string
{
    $endpoint = trim($endpoint);
    if ($endpoint === '') return '';
    if (strpos($endpoint, '//') === 0) return 'http:' . $endpoint;
    if (!preg_match('/^https?:\/\//i', $endpoint)) return 'http://' . $endpoint;
    return $endpoint;
}

// ─────────────────────────────────────────────────────────────
// Transfer Data / QR Helpers
// ─────────────────────────────────────────────────────────────

function transferDataTextLocal(array $options): string
{
    $transferData = (array) ($options['transfer_data'] ?? []);
    $title        = trim((string) ($transferData['title'] ?? '📌 بيانات التحويل'));
    $lines        = (array) ($transferData['lines'] ?? []);

    if ($lines === []) $lines = ['اسم المستلم: غير محدد', 'رقم الحساب: غير محدد', 'البنك: غير محدد'];

    $cleanLines = [];
    foreach ($lines as $line) {
        $l = trim((string) $line);
        if ($l !== '') $cleanLines[] = $l;
    }
    return implode("\n", array_merge(['', $title], $cleanLines));
}

function transferQrImageUrlLocal(array $options): string
{
    $td = (array) ($options['transfer_data'] ?? []);
    return trim((string) ($td['qr_image_url'] ?? $td['qr_url'] ?? $td['barcode_image_url'] ?? $td['image_url'] ?? ''));
}

// ─────────────────────────────────────────────────────────────
// Voucher Helpers
// ─────────────────────────────────────────────────────────────

function buildVoucherTitle(array $voucher): string
{
    $name     = trim((string) ($voucher['name']     ?? 'فئة غير معروفة'));
    $price    = (string) ($voucher['price']    ?? '0');
    $currency = trim((string) ($voucher['currency'] ?? 'ILS'));
    return '🎫 ' . $name . ' - ' . $price . ' ' . $currency;
}

function toWesternDigits(string $value): string
{
    return strtr($value, [
        '٠'=>'0','١'=>'1','٢'=>'2','٣'=>'3','٤'=>'4','٥'=>'5','٦'=>'6','٧'=>'7','٨'=>'8','٩'=>'9',
        '۰'=>'0','۱'=>'1','۲'=>'2','۳'=>'3','۴'=>'4','۵'=>'5','۶'=>'6','۷'=>'7','۸'=>'8','۹'=>'9',
    ]);
}

function parsePositiveInteger(string $text): ?int
{
    $n = preg_replace('/\s+/u', '', toWesternDigits(trim($text))) ?? '';
    if ($n === '' || !ctype_digit($n)) return null;
    return (int) $n;
}

// ─────────────────────────────────────────────────────────────
// Main Bot Menu
// ─────────────────────────────────────────────────────────────

function localMainMenuButtons(): array
{
    return [
        ['id' => 'action_buy_cards', 'title' => '🛒 شراء البطاقات - بنكي'],
        ['id' => 'action_orders',    'title' => '📦 طلبات الشراء'],
    ];
}

function mainMenuResponse(): array
{
    return [
        'text'    => '📋 القائمة الرئيسية:',
        'type'    => 'interactive_buttons',
        'buttons' => localMainMenuButtons(),
    ];
}

function cancelledResponse(): array
{
    return [
        'text'    => '❌ تم الإلغاء. تم العودة للقائمة الرئيسية.',
        'type'    => 'interactive_buttons',
        'buttons' => localMainMenuButtons(),
    ];
}

// ─────────────────────────────────────────────────────────────
// Core Bot Logic  —  شراء البطاقات only
// ─────────────────────────────────────────────────────────────

function handleBotUpdateLocal(array $config, array $messageData, array $userIdentity): array
{
    $chatId  = (string) ($userIdentity['whatsapp_id'] ?? '');
    $text    = trim((string) ($messageData['body'] ?? ''));
    $msgType = (string) ($messageData['type'] ?? 'chat');

    $visitInfo = trackUserVisitLocal($chatId);

    // Cancel / back
    if (isCancelText($text)) {
        clearConversationLocal($chatId);
        return cancelledResponse();
    }

    $stateRow  = getConversationLocal($chatId);
    $isGreeting = isStartText($text);

    // First visit or greeting with no active state → welcome
    if (($visitInfo['is_first_time'] ?? false) === true || ($stateRow === null && $isGreeting)) {
        clearConversationLocal($chatId);
        $welcome = trim((string) ($config['welcome_message'] ?? ''));
        if ($welcome === '') $welcome = "أهلاً وسهلاً بك 👋\nمرحباً بك في خدمة واتساب.";
        return ['type' => 'interactive_buttons', 'text' => $welcome, 'buttons' => localMainMenuButtons()];
    }

    $state   = (string) ($stateRow['state']   ?? '');
    $context = (array)  ($stateRow['context'] ?? []);

    // Resolve numbered / titled menu selection
    $resolved = resolveMenuSelectionLocal($text, $context);
    if ($resolved !== null) $text = $resolved;

    $options = loadLocalOptions();

    // ── View orders ──────────────────────────────────────────
    if ($text === 'action_orders' || canonicalizeText($text) === canonicalizeText('طلبات الشراء')) {
        return ['text' => formatLocalOrdersMessage($config, $chatId)];
    }

    // ── Start buy flow ───────────────────────────────────────
    if ($text === 'action_buy_cards' || $text === 'action_main_menu' || str_contains(canonicalizeText($text), canonicalizeText('شراء'))) {

        // Block if a previous order is still awaiting accept/reject decision
        $pendingOrder = getPendingOrderForChat($config, $chatId);
        if (is_array($pendingOrder)) {
            $pendingName = trim((string) ($pendingOrder['customer_name'] ?? ''));
            if ($pendingName === '') $pendingName = 'عميلنا الكريم';
            return ['text' => implode("\n", [
                '👤 ' . $pendingName,
                '⏳ الرجاء الانتظار حتى يتم قبول أو رفض طلبك السابق أولاً.',
                '',
                '0️⃣ للعودة إلى القائمة الرئيسية',
            ])];
        }

        $vouchers = array_filter((array) ($options['voucher_categories'] ?? []), fn($v) => is_array($v) && ($v['id'] ?? '') !== '');
        $vouchers = array_values($vouchers);

        if ($vouchers === []) return ['text' => '⚠️ لا توجد فئات بطاقات معرفة حالياً في options.json.'];

        $buttons = [];
        foreach ($vouchers as $voucher) {
            $buttons[] = ['id' => 'voucher:' . (string) ($voucher['id'] ?? ''), 'title' => buildVoucherTitle($voucher)];
        }

        saveConversationLocal($chatId, 'order_select_voucher', ['voucher_candidates' => $vouchers]);
        return ['type' => 'interactive_buttons', 'text' => 'اختر فئة البطاقة والسعر:', 'buttons' => $buttons];
    }

    // ── Step: select voucher ─────────────────────────────────
    if ($state === 'order_select_voucher') {
        if (!str_starts_with($text, 'voucher:'))
            return ['text' => textWithCancel('⚠️ اختر فئة البطاقة من القائمة.')];

        $voucherId = substr($text, strlen('voucher:'));
        $selected  = null;
        foreach ((array) ($context['voucher_candidates'] ?? []) as $voucher) {
            if ((string) ($voucher['id'] ?? '') === $voucherId) { $selected = $voucher; break; }
        }

        if (!is_array($selected)) return ['text' => textWithCancel('⚠️ الفئة المحددة غير صحيحة.')];

        $context['voucher_id']       = (string) ($selected['id']       ?? '');
        $context['voucher_name']     = (string) ($selected['name']      ?? '');
        $context['voucher_price']    = (string) ($selected['price']     ?? '0');
        $context['voucher_currency'] = (string) ($selected['currency']  ?? 'ILS');
        unset($context['voucher_candidates'], $context['_pending_menu']);

        saveConversationLocal($chatId, 'order_enter_quantity', $context);
        return ['text' => textWithCancel('🔢 كم عدد البطاقات المطلوبة؟')];
    }

    // ── Step: quantity ───────────────────────────────────────
    if ($state === 'order_enter_quantity') {
        $quantity = parsePositiveInteger($text);
        if ($quantity === null || $quantity <= 0 || $quantity > 100)
            return ['text' => textWithCancel('⚠️ أدخل عدد صحيح من 1 إلى 100.')];

        $context['voucher_count'] = $quantity;
        saveConversationLocal($chatId, 'order_enter_customer_name', $context);
        return ['text' => textWithCancel('👤 الرجاء كتابة اسم صاحب الحساب البنكي.')];
    }

    // ── Step: customer name ──────────────────────────────────
    if ($state === 'order_enter_customer_name') {
        if ($text === '' || mb_strlen($text) < 3)
            return ['text' => textWithCancel('⚠️ الرجاء كتابة اسم صاحب الحساب البنكي بشكل صحيح.')];

        $context['customer_name']     = $text;
        $whatsappNumber               = normalizeMobile($chatId) ?? $chatId;
        $context['customer_mobile']   = $whatsappNumber;
        $context['customer_whatsapp'] = $whatsappNumber;

        saveConversationLocal($chatId, 'order_wait_receipt', $context);

        $caption = textWithCancel(transferDataTextLocal($options) . "\n\n📸 الرجاء التحويل ورفع صورة الإشعار");
        $qrUrl   = transferQrImageUrlLocal($options);

        if ($qrUrl !== '') {
            return ['type' => 'image', 'image_url' => $qrUrl, 'caption' => $caption];
        }
        return ['text' => $caption];
    }

    // ── Step: receipt image ──────────────────────────────────
    if ($state === 'order_wait_receipt') {
        if ($msgType !== 'image')
            return ['text' => textWithCancel('⚠️ الرجاء إرسال اشعار التحويل كصورة فقط.')];

        $receiptMsgId      = (string) ($messageData['id']            ?? '');
        $receiptImageUrl   = (string) ($messageData['_image_url']    ?? '');
        $receiptBase64     = (string) ($messageData['_image_base64'] ?? '');
        $receiptMediaKey   = (string) ($messageData['_media_key']    ?? '');
        $receiptDirectPath = (string) ($messageData['_direct_path']  ?? '');

        $receiptImageData = downloadFullImage($config, $receiptMsgId, $receiptImageUrl, $receiptBase64, $receiptMediaKey, $receiptDirectPath);
        $receiptSavedUrl  = null;
        if ($receiptImageData !== null) {
            $receiptSavedUrl = saveImageToFile($receiptImageData, __DIR__ . '/../www/uploads/receipts', '/uploads/receipts');
        }

        $context['receipt_message_id']   = $receiptMsgId;
        $context['receipt_media_url']    = $receiptSavedUrl ?? $receiptImageUrl;
        $context['receipt_media_base64'] = $receiptSavedUrl !== null ? '' : $receiptBase64;
        $context['note']                 = '';

        $order = [
            'order_no'             => localOrderNumber(),
            'chat_id'              => $chatId,
            'customer_name'        => (string) ($context['customer_name']      ?? ''),
            'customer_mobile'      => (string) ($context['customer_mobile']    ?? ''),
            'voucher_id'           => (string) ($context['voucher_id']         ?? ''),
            'voucher_name'         => (string) ($context['voucher_name']       ?? ''),
            'voucher_price'        => (string) ($context['voucher_price']      ?? '0'),
            'voucher_currency'     => (string) ($context['voucher_currency']   ?? 'ILS'),
            'voucher_count'        => (int)    ($context['voucher_count']      ?? 0),
            'receipt_message_id'   => (string) ($context['receipt_message_id']   ?? ''),
            'receipt_media_url'    => (string) ($context['receipt_media_url']    ?? ''),
            'receipt_media_base64' => (string) ($context['receipt_media_base64'] ?? ''),
            'note'                 => '',
            'status'               => 'pending',
            'created_at'           => date('c'),
        ];

        $apiResult = submitNewOrderApi($config, $order);
        $order['api_result'] = $apiResult;
        localOrderStoreAdd($config, $order, $apiResult);
        clearConversationLocal($chatId);

        return [
            'type'    => 'interactive_buttons',
            'text'    => '✅ تم استلام طلبك رقم ' . $order['order_no'] . "\nطلبك الآن قيد المراجعة — الرجاء الانتظار حتى يتم قبوله أو رفضه قبل تقديم طلب جديد.",
            'buttons' => [
                ['id' => 'action_main_menu', 'title' => '🏠 العودة للقائمة الرئيسية'],
                ['id' => 'action_orders',    'title' => '📦 عرض طلبات الشراء'],
            ],
        ];
    }

    // Anything unrecognised → main menu
    return mainMenuResponse();
}

// ─────────────────────────────────────────────────────────────
// Send Response
// ─────────────────────────────────────────────────────────────

function sendBotResponseLocal(array $config, string $recipientPhone, array $response): void
{
    $text         = (string) ($response['text'] ?? '');
    $responseType = (string) ($response['type'] ?? 'text');

    if ($responseType === 'image') {
        $imageUrl = (string) ($response['image_url'] ?? '');
        $caption  = (string) ($response['caption']   ?? $text);
        if ($imageUrl !== '') { sendWhatsAppImageMessage($config, $recipientPhone, $imageUrl, $caption); return; }
        if ($caption  !== '') { sendWhatsAppTextMessage($config, $recipientPhone, $caption); }
        return;
    }

    if (in_array($responseType, ['interactive_buttons','interactive_list'], true)) {
        $options = [];
        foreach ((array) ($response['buttons'] ?? []) as $btn) {
            $options[] = ['title' => (string) ($btn['title'] ?? ''), 'id' => (string) ($btn['id'] ?? '')];
        }

        $isMainMenu = (array_column($options, 'id') === array_column(localMainMenuButtons(), 'id'));
        $menuText   = buildNumberedMenu($text, $options, !$isMainMenu);
        sendWhatsAppTextMessage($config, $recipientPhone, $menuText);

        $current        = getConversationLocal($recipientPhone);
        $currentState   = (string) ($current['state']   ?? '');
        $currentContext = (array)  ($current['context'] ?? []);
        $currentContext['_pending_menu'] = $options;
        saveConversationLocal($recipientPhone, $currentState !== '' ? $currentState : '_menu_pending', $currentContext);
        return;
    }

    sendWhatsAppTextMessage($config, $recipientPhone, $text);
}

// ─────────────────────────────────────────────────────────────
// wifi2fm API Helpers
// ─────────────────────────────────────────────────────────────

function whatsappApiSend(array $config, array $params): array
{
    if (!function_exists('curl_init')) return ['status' => 'error', 'message' => 'cURL not available'];

    $baseUrl = rtrim((string) ($config['api_base_url'] ?? 'https://wifi2fm.com'), '/');
    $url     = "{$baseUrl}/api/send";
    $params['instance_id']  = (string) ($config['instance_id']  ?? '');
    $params['access_token'] = (string) ($config['access_token'] ?? '');

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_HTTPHEADER     => ['Content-Type: application/x-www-form-urlencoded'],
        CURLOPT_POSTFIELDS     => http_build_query($params),
    ]);
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($response === false) return ['status' => 'error', 'message' => 'WhatsApp API unreachable'];
    $decoded = json_decode($response, true);
    if (!is_array($decoded)) return ['status' => 'error', 'message' => 'Invalid response', 'http_code' => $httpCode, 'raw' => $response];
    return $decoded;
}

function sendWhatsAppTextMessage(array $config, string $recipientPhone, string $text): void
{
    whatsappApiSend($config, ['number' => $recipientPhone, 'type' => 'text', 'message' => $text]);
}

function sendWhatsAppImageMessage(array $config, string $recipientPhone, string $imageUrl, string $caption = ''): void
{
    $params = ['number' => $recipientPhone, 'type' => 'media', 'media_url' => $imageUrl];
    if ($caption !== '') $params['message'] = $caption;
    whatsappApiSend($config, $params);
}

// ─────────────────────────────────────────────────────────────
// Image Download / Decrypt / Save
// ─────────────────────────────────────────────────────────────

function detectImageFormat(?string $data): ?string
{
    if ($data === null || strlen($data) < 8) return null;
    if (substr($data, 0, 3) === "\xFF\xD8\xFF")             return 'jpeg';
    if (substr($data, 0, 8) === "\x89PNG\r\n\x1A\n")        return 'png';
    if (substr($data, 0, 4) === 'RIFF' && substr($data, 8, 4) === 'WEBP') return 'webp';
    if (substr($data, 0, 6) === 'GIF87a' || substr($data, 0, 6) === 'GIF89a') return 'gif';
    return null;
}

function imageFormatToExtension(string $format): string
{
    return match ($format) { 'jpeg'=>'.jpg','png'=>'.png','webp'=>'.webp','gif'=>'.gif', default=>'.jpg' };
}

function validateImageData(?string $data, string $source): ?string
{
    if ($data === null || $data === '') return null;
    return detectImageFormat($data) !== null ? $data : null;
}

function downloadFromUrl(string $url): ?string
{
    if ($url === '') return null;
    $ch = curl_init($url);
    curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>30, CURLOPT_FOLLOWLOCATION=>true, CURLOPT_MAXREDIRS=>5, CURLOPT_USERAGENT=>'WhatsAppBot/1.0']);
    $data     = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($data === false || $httpCode !== 200 || strlen($data) < 100) return null;
    return $data;
}

function decryptWhatsAppMedia(string $encryptedData, string $mediaKeyB64, string $mediaType = 'image'): ?string
{
    $clean = str_replace(['-','_'],['+','/'], $mediaKeyB64);
    $pad   = strlen($clean) % 4;
    if ($pad > 0) $clean .= str_repeat('=', 4 - $pad);
    $mediaKey = base64_decode($clean, true);
    if ($mediaKey === false || strlen($mediaKey) !== 32) return null;

    $info = ['image'=>'WhatsApp Image Keys','video'=>'WhatsApp Video Keys','audio'=>'WhatsApp Audio Keys','document'=>'WhatsApp Document Keys','sticker'=>'WhatsApp Image Keys'][$mediaType] ?? 'WhatsApp Image Keys';
    $expanded   = hkdfExpand($mediaKey, $info, 112);
    if ($expanded === null) return null;

    $iv        = substr($expanded,  0, 16);
    $cipherKey = substr($expanded, 16, 32);
    $macKey    = substr($expanded, 48, 32);

    if (strlen($encryptedData) < 10) return null;
    $encBody   = substr($encryptedData, 0, -10);
    $decrypted = openssl_decrypt($encBody, 'aes-256-cbc', $cipherKey, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
    if ($decrypted === false) return null;

    $padLen = ord($decrypted[strlen($decrypted) - 1]);
    if ($padLen > 0 && $padLen <= 16) {
        if (substr($decrypted, -$padLen) === str_repeat(chr($padLen), $padLen))
            $decrypted = substr($decrypted, 0, -$padLen);
    }
    return $decrypted;
}

function hkdfExpand(string $key, string $info, int $length): ?string
{
    $prk = hash_hmac('sha256', $key, str_repeat("\x00", 32), true);
    $okm = ''; $t = '';
    for ($i = 1; $i <= (int) ceil($length / 32); $i++) {
        $t    = hash_hmac('sha256', $t . $info . chr($i), $prk, true);
        $okm .= $t;
    }
    return substr($okm, 0, $length);
}

function buildWhatsAppMediaUrl(string $directPath): string
{
    return $directPath !== '' ? 'https://mmg.whatsapp.net' . $directPath : '';
}

function downloadFullImage(array $config, string $messageId, string $imageUrl, string $imageBase64, string $mediaKey = '', string $directPath = ''): ?string
{
    if ($mediaKey !== '') {
        foreach (array_filter([buildWhatsAppMediaUrl($directPath), $imageUrl]) as $cdnUrl) {
            $enc = downloadFromUrl($cdnUrl);
            if ($enc !== null) {
                $dec = decryptWhatsAppMedia($enc, $mediaKey, 'image');
                if ($dec !== null && validateImageData($dec, 'decrypted') !== null) return $dec;
            }
        }
    }

    if ($imageUrl !== '') {
        $data = downloadFromUrl($imageUrl);
        if ($data !== null && validateImageData($data, 'direct') !== null) return $data;
    }

    if ($messageId !== '') {
        $data = downloadMediaViaApi($config, $messageId);
        if ($data !== null) return $data;
    }

    if ($imageBase64 !== '') {
        $data = base64_decode($imageBase64, true) ?: null;
        return validateImageData($data, 'thumbnail');
    }

    return null;
}

function downloadMediaViaApi(array $config, string $messageId): ?string
{
    if ($messageId === '') return null;
    $baseUrl     = rtrim((string) ($config['api_base_url'] ?? 'https://wifi2fm.com'), '/');
    $instanceId  = (string) ($config['instance_id']  ?? '');
    $accessToken = (string) ($config['access_token'] ?? '');
    if ($instanceId === '' || $accessToken === '') return null;

    $params   = ['instance_id' => $instanceId, 'access_token' => $accessToken, 'message_id' => $messageId];
    $response = downloadFromUrl($baseUrl . '/api/getMedia?' . http_build_query($params));

    if ($response === null) {
        $ch = curl_init($baseUrl . '/api/downloadMedia');
        curl_setopt_array($ch, [CURLOPT_POST=>true, CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>60, CURLOPT_HTTPHEADER=>['Content-Type: application/x-www-form-urlencoded'], CURLOPT_POSTFIELDS=>http_build_query($params)]);
        $response = curl_exec($ch);
        $code     = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        if ($response === false || $code !== 200) return null;
    }

    if (validateImageData($response, 'api') !== null) return $response;

    $json = json_decode($response, true);
    if (is_array($json)) {
        $b64 = (string) ($json['data'] ?? $json['media'] ?? $json['base64'] ?? $json['file'] ?? '');
        if ($b64 !== '') {
            if (str_contains($b64, ',')) $b64 = substr($b64, strpos($b64, ',') + 1);
            $decoded = base64_decode($b64, true);
            if ($decoded && validateImageData($decoded, 'apiBase64') !== null) return $decoded;
        }
        $dlUrl = (string) ($json['url'] ?? $json['download_url'] ?? $json['media_url'] ?? $json['link'] ?? '');
        if ($dlUrl !== '') {
            $dl = downloadFromUrl($dlUrl);
            if ($dl !== null) return validateImageData($dl, 'apiUrl');
        }
    }
    return null;
}

function saveImageToFile(string $imageData, string $uploadDir, string $publicBasePath): ?string
{
    if ($imageData === '') return null;
    $format = detectImageFormat($imageData);
    if ($format === null) return null;
    if (!is_dir($uploadDir) && !@mkdir($uploadDir, 0775, true) && !is_dir($uploadDir)) return null;
    if (!is_writable($uploadDir)) return null;

    $filename = uuidV4() . imageFormatToExtension($format);
    $fullPath = rtrim($uploadDir, '/\\') . DIRECTORY_SEPARATOR . $filename;
    return @file_put_contents($fullPath, $imageData) !== false
        ? rtrim($publicBasePath, '/') . '/' . $filename
        : null;
}

// ─────────────────────────────────────────────────────────────
// Shared Helpers
// ─────────────────────────────────────────────────────────────

function normalizeMobile(string $mobile): ?string
{
    $digits = preg_replace('/\D+/', '', $mobile);
    if (!is_string($digits) || strlen($digits) < 9 || strlen($digits) > 15) return null;
    if (str_starts_with($digits, '970')) return '+' . $digits;
    if (str_starts_with($digits, '0'))   return $digits;
    return '0' . $digits;
}

function uuidV4(): string
{
    $data    = random_bytes(16);
    $data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
    $data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

function canonicalizeText(string $text): string
{
    $text = mb_strtolower(trim($text));
    $text = preg_replace('/[^\p{Arabic}\p{L}\p{N}\/ ]+/u', ' ', $text) ?? $text;
    $text = preg_replace('/\s+/u', ' ', $text)                          ?? $text;
    return trim($text);
}

function isStartText(string $text): bool
{
    $c = canonicalizeText($text);
    return in_array($c, ['/start','مرحبا','ابدأ','start','hi','hello'], true);
}

function isCancelText(string $text): bool
{
    return in_array(canonicalizeText($text), [
        'الغاء','إلغاء','الغي','cancel','رجوع','عودة',
        'القائمة الرئيسية','القائمه الرئيسيه','الرئيسية','الرئيسيه',
        '0','خروج','exit','back','menu',
    ], true);
}

function textWithCancel(string $prompt): string
{
    return $prompt . "\n\n📌 أرسل 0️⃣ للإلغاء والعودة للقائمة الرئيسية.";
}

// ─────────────────────────────────────────────────────────────
// Menu Helpers
// ─────────────────────────────────────────────────────────────

function menuNumberIcon(int $num): string
{
    return ['0'=>'0️⃣','1'=>'1️⃣','2'=>'2️⃣','3'=>'3️⃣','4'=>'4️⃣','5'=>'5️⃣',
            '6'=>'6️⃣','7'=>'7️⃣','8'=>'8️⃣','9'=>'9️⃣','10'=>'🔟'][(string)$num] ?? (string) $num;
}

function ensureMenuOptionIcon(string $title): string
{
    $trimmed = trim($title);
    if ($trimmed === '') return '🔹 خيار';
    foreach (['💸','🛠️','🛒','🔍','📋','✅','❌','🔹','📦','🎫'] as $prefix) {
        if (str_starts_with($trimmed, $prefix)) return $trimmed;
    }
    return '🔹 ' . $trimmed;
}

function buildNumberedMenu(string $headerText, array $options, bool $showCancel = true): string
{
    $lines = [$headerText, ''];
    foreach ($options as $i => $option) {
        $lines[] = menuNumberIcon($i + 1) . ' ' . ensureMenuOptionIcon((string) ($option['title'] ?? $option));
    }
    if ($showCancel) { $lines[] = ''; $lines[] = menuNumberIcon(0) . ' ❌ إلغاء / القائمة الرئيسية'; }
    $lines[] = '';
    $lines[] = 'أرسل الرقم للمتابعة 👆';
    return implode("\n", $lines);
}

// ─────────────────────────────────────────────────────────────
// Webhook Entry Point
// ─────────────────────────────────────────────────────────────

function processWebhookPayload(array $config): void
{
    $rawBody = file_get_contents('php://input');
    if ($rawBody === false || $rawBody === '') { http_response_code(200); echo 'OK'; return; }

    $payload = json_decode($rawBody, true);
    if (!is_array($payload)) { http_response_code(200); echo 'OK'; return; }

    if (!validateIncomingWebhookSecret($config, $payload)) { http_response_code(403); echo 'Forbidden'; return; }
    if (isMessageUpdateEvent($payload)) { http_response_code(200); echo 'OK'; return; }

    $normalized = normalizeIncomingWebhookPayload($payload);
    if (!is_array($normalized)) { http_response_code(200); echo 'OK'; return; }
    if (($normalized['from_me'] ?? false) === true) { http_response_code(200); echo 'OK'; return; }

    $remoteJid = (string) ($normalized['remote_jid'] ?? '');
    if (isGroupJid($remoteJid)) { http_response_code(200); echo 'OK'; return; }

    // Deduplication
    $msgId = (string) ($normalized['id'] ?? '');
    if ($msgId !== '') {
        $dedupDir  = __DIR__ . '/storage/dedup';
        if (!is_dir($dedupDir)) @mkdir($dedupDir, 0775, true);
        $dedupFile = $dedupDir . '/' . md5($msgId) . '.lock';
        if (file_exists($dedupFile) && (time() - (int) @filemtime($dedupFile)) < 30) { http_response_code(200); echo 'OK'; return; }
        @file_put_contents($dedupFile, time());
        foreach (@glob($dedupDir . '/*.lock') ?: [] as $f) {
            if (time() - (int) @filemtime($f) > 60) @unlink($f);
        }
    }

    $senderPhone = (string) ($normalized['sender_phone'] ?? '');
    if ($senderPhone === '') { http_response_code(200); echo 'OK'; return; }

    $msgType = (string) ($normalized['msg_type'] ?? 'chat');
    $msgBody = (string) ($normalized['body']     ?? '');

    $messageData = [
        'from' => $senderPhone,
        'id'   => (string) ($normalized['id'] ?? ''),
        'type' => $msgType,
        'body' => '',
    ];

    if ($msgType === 'chat') {
        $messageData['body'] = $msgBody;
    } elseif (in_array($msgType, ['image','sticker'], true)) {
        $messageData['_image_url']    = (string) ($normalized['image_url']    ?? '');
        $messageData['_image_base64'] = (string) ($normalized['image_base64'] ?? '');
        $messageData['_media_key']    = (string) ($normalized['media_key']    ?? '');
        $messageData['_direct_path']  = (string) ($normalized['direct_path']  ?? '');
        $messageData['body']          = (string) ($normalized['caption']      ?? $msgBody);
    } else {
        $messageData['body'] = $msgBody;
    }

    $userIdentity = [
        'display_name' => ($normalized['contact_name'] ?? '') !== '' ? $normalized['contact_name'] : $senderPhone,
        'whatsapp_id'  => $senderPhone,
        'phone'        => $senderPhone,
    ];

    $response = handleBotUpdateLocal($config, $messageData, $userIdentity);
    sendBotResponseLocal($config, $senderPhone, $response);

    http_response_code(200);
    echo 'OK';
}

// ─────────────────────────────────────────────────────────────
// Bootstrap
// ─────────────────────────────────────────────────────────────

try {
    $config = loadBotConfig();
    validateBotConfig($config);

    if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'GET') {
        http_response_code(200);
        echo 'WhatsApp Bot Webhook Active';
    } else {
        processWebhookPayload($config);
    }
} catch (Throwable $e) {
    http_response_code(500);
    echo 'Internal Server Error';
    exit(1);
}