<?php
namespace EasyChat\Library\Messenger;

use CURLFile;
use EasyChat\Library\AttachmentPolicy;
use EasyChat\Library\Http\ExternalHttpClient;
use EasyChat\Library\ImageVariantService;
use EasyChat\Library\SafeRuntime;

class VkProvider implements MessengerProviderInterface {

    const API_BASE = 'https://api.vk.com';
    const PROVIDER = 'vk';
    const API_VERSION = '5.199';
    const CALLBACK_TITLE = 'EasyChat';
    private const MAX_FORWARDED_DEPTH = 5;
    private const MAX_FORWARDED_MESSAGES = 50;

    private $registry;
    private $http;

    public function __construct($registry = null, ?ExternalHttpClient $http = null) {
        $this->registry = $registry;
        $this->http = $http ?: new ExternalHttpClient($registry);
    }

    public function providerCode(): string {
        return self::PROVIDER;
    }

    


    public function getBotInfo(string $token): array {
        $result = $this->apiJson('POST', '/method/groups.getById', $token, [], 'token_check');
        if (empty($result['success'])) {
            return $result;
        }

        $response = $result['json']['response'] ?? null;
        if (is_array($response) && isset($response['groups'][0]) && is_array($response['groups'][0])) {
            $group = $response['groups'][0];
        } elseif (is_array($response) && isset($response[0]) && is_array($response[0])) {
            $group = $response[0];
        } elseif (is_array($response) && !empty($response['id'])) {
            $group = $response;
        } else {
            $group = [];
        }

        if (empty($group['id'])) {
            return ['success' => false, 'error' => 'group_not_found', 'http_code' => $result['http_code'] ?? 0, 'vk_step' => 'token_check'];
        }

        $screen_name = (string)($group['screen_name'] ?? '');

        return [
            'success' => true,
            'id' => (string)$group['id'],
            'name' => (string)($group['name'] ?? ''),
            'username' => $screen_name,
            'profile_url' => $screen_name !== '' ? 'https://vk.com/' . $screen_name : 'https://vk.com/club' . (string)$group['id'],
            'json' => $group,
        ];
    }

    



    public function ensureCommunityMessagesEnabled(string $token, string $group_id): array {
        $group_id = preg_replace('/\D+/', '', (string)$group_id);
        if ($group_id === '') {
            return ['success' => false, 'error' => 'group_id_missing', 'vk_step' => 'community_settings'];
        }

        return $this->apiJson('POST', '/method/groups.setSettings', $token, [
            'group_id' => (int)$group_id,
            'messages' => 1,
            'bots_capabilities' => 1,
        ], 'community_settings');
    }

    public function getTokenPermissions(string $token): array {
        return $this->apiJson('POST', '/method/groups.getTokenPermissions', $token, [], 'token_permissions');
    }

    public function hasTokenPermission(array $permissions, string $permission): bool {
        $permission = strtolower(trim($permission));
        if ($permission === '') {
            return false;
        }

        $items = $permissions['json']['response']['permissions'] ?? [];
        if (!is_array($items)) {
            return false;
        }

        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            $name = strtolower(trim((string)($item['name'] ?? '')));
            if (in_array($name, $this->vkPermissionAliases($permission), true)) {
                return true;
            }
        }

        return false;
    }

    private function vkPermissionAliases(string $permission): array {
        $aliases = [
             
             
             
            'groups' => ['groups', 'manage'],
        ];

        return $aliases[$permission] ?? [$permission];
    }

    




    public function getUserInfo(string $token, string $user_id): array {
        $user_id = preg_replace('/\D+/', '', (string)$user_id);
        if ($user_id === '') {
            return ['success' => false, 'error' => 'user_id_missing'];
        }

        $result = $this->apiJson('POST', '/method/users.get', $token, [
            'user_ids' => $user_id,
            'fields' => 'screen_name,photo_200,photo_100,photo_max',
            'name_case' => 'nom',
        ], 'user_profile');
        if (empty($result['success'])) {
            return $result;
        }

        $response = $result['json']['response'] ?? [];
        $user = is_array($response) && isset($response[0]) && is_array($response[0]) ? $response[0] : [];
        if (empty($user['id'])) {
            return ['success' => false, 'error' => 'user_not_found', 'http_code' => $result['http_code'] ?? 0];
        }

        $first_name = trim((string)($user['first_name'] ?? ''));
        $last_name = trim((string)($user['last_name'] ?? ''));
        $display_name = trim($first_name . ' ' . $last_name);
        $screen_name = $this->sanitizeVkScreenName((string)($user['screen_name'] ?? ''));
        if ($display_name === '') {
            $display_name = $screen_name !== '' ? $screen_name : 'VK ' . (string)$user['id'];
        }

        return [
            'success' => true,
            'id' => (string)$user['id'],
            'display_name' => $this->limitString($display_name, 120),
            'username' => $screen_name,
            'profile_url' => $screen_name !== '' ? 'https://vk.com/' . $screen_name : 'https://vk.com/id' . (string)$user['id'],
            'avatar_url' => $this->chooseUserAvatarUrl($user),
            'json' => $user,
            'http_code' => $result['http_code'],
        ];
    }

    



    public function getForwardSourceInfo(string $token, string $source_id, string $source_type = ''): array {
        $source_id = trim($source_id);
        $source_type = strtolower(trim($source_type));
        if ($source_type === 'user'
            || ($source_type !== 'community' && preg_match('/^\d+$/', $source_id) === 1)) {
            return $this->getUserInfo($token, ltrim($source_id, '-'));
        }

        if ($source_type !== 'community' && preg_match('/^-\d+$/', $source_id) !== 1) {
            return ['success' => false, 'error' => 'forward_source_id_invalid'];
        }

        $group_id = preg_replace('/\D+/', '', $source_id);
        if ($group_id === '') {
            return ['success' => false, 'error' => 'forward_source_id_invalid'];
        }
        $result = $this->apiJson('POST', '/method/groups.getById', $token, [
            'group_ids' => $group_id,
            'fields' => 'screen_name',
        ], 'forward_source_profile');
        if (empty($result['success'])) {
            return $result;
        }

        $response = $result['json']['response'] ?? [];
        $group = is_array($response) && isset($response['groups'][0]) && is_array($response['groups'][0])
            ? $response['groups'][0]
            : (is_array($response) && isset($response[0]) && is_array($response[0]) ? $response[0] : []);
        $display_name = trim((string)($group['name'] ?? ''));
        if ($display_name === '') {
            return ['success' => false, 'error' => 'forward_source_not_found', 'http_code' => $result['http_code'] ?? 0];
        }

        return [
            'success' => true,
            'id' => '-' . $group_id,
            'display_name' => $this->limitString($display_name, 120),
            'http_code' => $result['http_code'],
        ];
    }

    




    public function getMessageById(string $token, string $message_id): array {
        $message_id = preg_replace('/\D+/', '', (string)$message_id);
        if ($message_id === '') {
            return ['success' => false, 'error' => 'message_id_missing'];
        }

        $result = $this->apiJson('POST', '/method/messages.getById', $token, [
            'message_ids' => $message_id,
        ], 'message_lookup');
        if (empty($result['success'])) {
            return $result;
        }

        $items = $result['json']['response']['items'] ?? [];
        $message = is_array($items) && isset($items[0]) && is_array($items[0]) ? $items[0] : [];
        if (!$message) {
            return ['success' => false, 'error' => 'message_not_found', 'http_code' => $result['http_code'] ?? 0];
        }

        return $this->normalizedMessageResult($message, (int)$result['http_code']);
    }

    




    public function getMessageByConversationMessageId(string $token, string $peer_id, string $message_id): array {
        $peer_id = $this->normalizePeerId($peer_id);
        $message_id = preg_replace('/\D+/', '', (string)$message_id);
        if ($peer_id === '' || $message_id === '') {
            return ['success' => false, 'error' => 'conversation_message_context_missing'];
        }

        $result = $this->apiJson('POST', '/method/messages.getByConversationMessageId', $token, [
            'peer_id' => (int)$peer_id,
            'conversation_message_ids' => $message_id,
        ], 'conversation_message_lookup');
        if (empty($result['success'])) {
            return $result;
        }

        $items = $result['json']['response']['items'] ?? [];
        $message = is_array($items) && isset($items[0]) && is_array($items[0]) ? $items[0] : [];
        if (!$message) {
            return ['success' => false, 'error' => 'message_not_found', 'http_code' => $result['http_code'] ?? 0];
        }

        return $this->normalizedMessageResult($message, (int)$result['http_code']);
    }

    




    public function getMessageForEvent(string $token, array $event): array {
        $external_id = preg_replace('/\D+/', '', (string)($event['external_message_id'] ?? ''));
        $peer_id = $this->normalizePeerId((string)($event['external_chat_id'] ?? ''));
        if ($external_id !== '') {
            $exact = $this->getMessageById($token, $external_id);
            if (!empty($exact['success']) && $this->messageMatchesEvent((array)($exact['json'] ?? []), $event)) {
                return $exact;
            }

            if ($peer_id !== '') {
                $conversation = $this->getMessageByConversationMessageId($token, $peer_id, $external_id);
                if (!empty($conversation['success'])
                    && $this->messageMatchesEvent((array)($conversation['json'] ?? []), $event)) {
                    return $conversation;
                }
            }
        }

        if ($peer_id === '') {
            return ['success' => false, 'error' => 'message_lookup_context_missing'];
        }
        $history = $this->apiJson('POST', '/method/messages.getHistory', $token, [
            'peer_id' => (int)$peer_id,
            'count' => 10,
        ], 'message_history_lookup');
        if (empty($history['success'])) {
            return $history;
        }

        $items = $history['json']['response']['items'] ?? [];
        $expected_time = (int)($event['created'] ?? 0);
        $expected_out = (string)($event['direction'] ?? '') === 'out' ? 1 : 0;
        $expected_text = trim((string)($event['text'] ?? ''));
        $best = [];
        $best_score = -1;
        foreach (is_array($items) ? $items : [] as $message) {
            if (!is_array($message)) {
                continue;
            }
            $message_time = (int)($message['date'] ?? 0);
            $delta = $expected_time > 0 && $message_time > 0 ? abs($message_time - $expected_time) : 0;
            if ($expected_time > 0 && ($message_time <= 0 || $delta > 120)) {
                continue;
            }
            $score = $expected_time > 0 ? max(0, 120 - $delta) : 1;
            if ((int)($message['out'] ?? 0) === $expected_out) {
                $score += 30;
            }
            if ($expected_text !== '' && trim((string)($message['text'] ?? '')) === $expected_text) {
                $score += 40;
            }
            $normalized_attachments = $this->extractAttachments($message);
            if ($normalized_attachments) {
                $score += min(20, count($normalized_attachments));
            }
            if ($this->isForwardedMessage($message)) {
                $score += 10;
            }
            if ($score > $best_score) {
                $best = $message;
                $best_score = $score;
            }
        }
        if (!$best) {
            return ['success' => false, 'error' => 'message_not_found', 'http_code' => $history['http_code'] ?? 0];
        }

        $normalized = $this->normalizedMessageResult($best, (int)$history['http_code']);
        $normalized['resolved_message_id'] = (string)($best['id'] ?? '');
        $normalized['resolved_conversation_message_id'] = (string)($best['conversation_message_id'] ?? '');
        return $normalized;
    }

    


    public function getCallbackConfirmationCode(string $token, string $group_id): array {
        $result = $this->apiJson('POST', '/method/groups.getCallbackConfirmationCode', $token, [
            'group_id' => (int)$group_id,
        ], 'confirmation_code');
        if (empty($result['success'])) {
            return $result;
        }

        $code = (string)($result['json']['response']['code'] ?? '');
        if ($code === '') {
            return ['success' => false, 'error' => 'confirmation_code_missing', 'http_code' => $result['http_code'] ?? 0, 'vk_step' => 'confirmation_code'];
        }

        return ['success' => true, 'code' => $code, 'http_code' => $result['http_code'], 'json' => $result['json']];
    }

    public function getCallbackServers(string $token, string $group_id): array {
        $result = $this->apiJson('POST', '/method/groups.getCallbackServers', $token, [
            'group_id' => (int)$group_id,
        ], 'callback_servers');
        if (empty($result['success'])) {
            return $result;
        }

        $items = $result['json']['response']['items'] ?? [];
        return [
            'success' => true,
            'items' => is_array($items) ? $items : [],
            'http_code' => $result['http_code'],
            'json' => $result['json'],
        ];
    }

    public function setCallbackSettings(string $token, string $group_id, string $server_id): array {
        return $this->apiJson('POST', '/method/groups.setCallbackSettings', $token, [
            'group_id' => (int)$group_id,
            'server_id' => (int)$server_id,
            'api_version' => self::API_VERSION,
            'message_new' => 1,
            'message_reply' => 1,
            'message_allow' => 1,
        ], 'callback_settings');
    }

    public function getCallbackSettings(string $token, string $group_id, string $server_id): array {
        return $this->apiJson('POST', '/method/groups.getCallbackSettings', $token, [
            'group_id' => (int)$group_id,
            'server_id' => (int)$server_id,
        ], 'callback_settings_check');
    }

    public function hasIncomingMessageEvent(array $settings): bool {
        $response = is_array($settings['json']['response'] ?? null) ? $settings['json']['response'] : [];
         
         
         
         
        $events = is_array($response['events'] ?? null) ? $response['events'] : $response;
        return $this->isTruthyVkFlag($events['message_new'] ?? null)
            && $this->isTruthyVkFlag($events['message_reply'] ?? null)
            && $this->isTruthyVkFlag($events['message_allow'] ?? null);
    }

    



    public function syncWebhook(array $channel, string $token, string $webhook_url, string $secret, ?array $bot_info = null): array {
         
         
         
        $has_verified_bot_info = is_array($bot_info) && !empty($bot_info['success']);
        $me = $has_verified_bot_info ? $bot_info : $this->getBotInfo($token);
        if (empty($me['success'])) {
            return $me;
        }

        if (strpos($webhook_url, 'https://') !== 0) {
            return ['success' => false, 'error' => 'webhook_https_required'];
        }

        $credentials = json_decode((string)($channel['credentials_json'] ?? ''), true);
        if (!is_array($credentials)) {
            $credentials = [];
        }
        $group_id = preg_replace('/\D+/', '', (string)($credentials['vk_group_id'] ?? $me['id']));
        if ($group_id === '') {
            return ['success' => false, 'error' => 'group_id_missing'];
        }

        $server_id = '';
        $server_already_synced = false;
        $force_create_server = false;
        $stored_server_id = preg_replace('/\D+/', '', (string)($credentials['callback_server_id'] ?? ''));
        if ($stored_server_id !== '') {
            $this->pauseVkSetupStep();
            $edit = $this->editCallbackServer($token, $group_id, $stored_server_id, $webhook_url, $secret);
            if (!empty($edit['success'])) {
                $server_id = $stored_server_id;
                $server_already_synced = true;
                if (trim((string)($channel['webhook_synced_at'] ?? '')) !== '') {
                    $this->pauseVkSetupStep();
                    $servers = $this->getCallbackServers($token, $group_id);
                    if (!empty($servers['success']) && $this->callbackServerHasFailed($servers['items'], $stored_server_id)) {
                         
                         
                         
                        $this->pauseVkSetupStep();
                        $delete = $this->deleteCallbackServer($token, $group_id, $stored_server_id);
                        if (empty($delete['success'])) {
                            return $delete;
                        }
                        $server_id = '';
                        $server_already_synced = false;
                        $force_create_server = true;
                    } elseif (!empty($servers['rate_limited'])) {
                        return $servers;
                    }
                }
            } elseif (!empty($edit['rate_limited'])) {
                return $edit;
            }
        }

        $title_server_id = '';
        if ($server_id === '' && !$force_create_server) {
            if (!$has_verified_bot_info || $stored_server_id !== '') {
                $this->pauseVkSetupStep();
            }
            $servers = $this->getCallbackServers($token, $group_id);
            if (empty($servers['success'])) {
                return $servers;
            }

            foreach ($servers['items'] as $server) {
                if (!is_array($server)) {
                    continue;
                }
                if ((string)($server['url'] ?? '') === $webhook_url && (string)($server['id'] ?? '') !== '') {
                    $server_id = (string)$server['id'];
                    break;
                }
                if ($stored_server_id !== '' && (string)($server['id'] ?? '') === $stored_server_id) {
                    $server_id = $stored_server_id;
                    continue;
                }
                if ($title_server_id === '' && (string)($server['title'] ?? '') === self::CALLBACK_TITLE && (string)($server['id'] ?? '') !== '') {
                    $title_server_id = (string)$server['id'];
                }
            }

            if ($server_id === '' && $title_server_id !== '') {
                $server_id = $title_server_id;
            }
        }

        if ($server_id === '') {
            $this->pauseVkSetupStep();
            $add = $this->apiJson('POST', '/method/groups.addCallbackServer', $token, [
                'group_id' => (int)$group_id,
                'url' => $webhook_url,
                'title' => self::CALLBACK_TITLE,
                'secret_key' => $this->limitString($secret, 50),
            ], 'callback_server_add');
            if (empty($add['success'])) {
                return $add;
            }
            $server_id = $this->extractCallbackServerId($add['json']['response'] ?? null);
        } elseif (!$server_already_synced) {
             
             
             
             
            $this->pauseVkSetupStep();
            $edit = $this->editCallbackServer($token, $group_id, $server_id, $webhook_url, $secret);
            if (empty($edit['success'])) {
                return $edit;
            }
        }

        if ($server_id === '') {
            return ['success' => false, 'error' => 'callback_server_id_missing'];
        }

        if ($server_already_synced && trim((string)($channel['webhook_synced_at'] ?? '')) !== '') {
            $this->pauseVkSetupStep();
            $settings_check = $this->getCallbackSettings($token, $group_id, $server_id);
            if (!empty($settings_check['success']) && $this->hasIncomingMessageEvent($settings_check)) {
                return [
                    'success' => true,
                    'bot' => [
                        'user_id' => $me['id'],
                        'name' => $me['name'],
                        'username' => (string)($me['username'] ?? ''),
                        'profile_url' => (string)($me['profile_url'] ?? ''),
                    ],
                    'group_id' => $group_id,
                    'server_id' => $server_id,
                    'api_version' => self::API_VERSION,
                    'response' => $settings_check['json'] ?? [],
                    'http_code' => $settings_check['http_code'] ?? 200,
                ];
            }
            if (!empty($settings_check['rate_limited'])) {
                return $settings_check;
            }
        }

        $this->pauseVkSetupStep();
        $settings = $this->setCallbackSettings($token, $group_id, $server_id);
        if (empty($settings['success'])) {
            $settings['group_id'] = $group_id;
            $settings['server_id'] = $server_id;
            $settings['api_version'] = self::API_VERSION;
            return $settings;
        }

        return [
            'success' => true,
            'bot' => [
                'user_id' => $me['id'],
                'name' => $me['name'],
                'username' => (string)($me['username'] ?? ''),
                'profile_url' => (string)($me['profile_url'] ?? ''),
            ],
            'group_id' => $group_id,
            'server_id' => $server_id,
            'api_version' => self::API_VERSION,
            'response' => $settings['json'] ?? [],
            'http_code' => $settings['http_code'] ?? 200,
        ];
    }

    private function editCallbackServer(string $token, string $group_id, string $server_id, string $webhook_url, string $secret): array {
        return $this->apiJson('POST', '/method/groups.editCallbackServer', $token, [
            'group_id' => (int)$group_id,
            'server_id' => (int)$server_id,
            'url' => $webhook_url,
            'title' => self::CALLBACK_TITLE,
            'secret_key' => $this->limitString($secret, 50),
        ], 'callback_server_edit');
    }

    private function deleteCallbackServer(string $token, string $group_id, string $server_id): array {
        return $this->apiJson('POST', '/method/groups.deleteCallbackServer', $token, [
            'group_id' => (int)$group_id,
            'server_id' => (int)$server_id,
        ], 'callback_server_delete');
    }

    public function callbackServerHasFailed(array $servers, string $server_id): bool {
        foreach ($servers as $server) {
            if (!is_array($server) || (string)($server['id'] ?? '') !== $server_id) {
                continue;
            }
            return strtolower(trim((string)($server['status'] ?? ''))) === 'failed';
        }
        return false;
    }

    private function extractCallbackServerId($response): string {
        if (is_array($response)) {
            return preg_replace('/\D+/', '', (string)($response['server_id'] ?? $response['id'] ?? ''));
        }
        return preg_replace('/\D+/', '', (string)$response);
    }

    


    public function parseWebhook(array $payload): array {
        $update_type = (string)($payload['type'] ?? '');

        if ($update_type === 'confirmation') {
            return [
                'update_type' => 'confirmation',
                'text' => '',
                'external_user_id' => 'system',
                'external_chat_id' => 'system',
                'external_message_id' => 'confirmation',
                'display_name' => 'System',
                'username' => '',
                'profile_url' => '',
                'attachments' => [],
            ];
        }

        if ($update_type === 'message_allow') {
            $user_id = (string)($payload['object']['user_id'] ?? '');
            $event_id = (string)($payload['event_id'] ?? '');
            return [
                'update_type' => $update_type,
                'event_kind' => 'conversation_start',
                'text' => '',
                'start_payload' => '',
                'external_user_id' => $user_id,
                'external_chat_id' => $user_id,
                'external_message_id' => $event_id,
                'dedupe_key' => 'vk:start:' . ($event_id !== '' ? $event_id : hash('sha256', json_encode($payload))),
                'display_name' => 'Пользователь VK',
                'username' => '',
                'profile_url' => preg_match('/^\d+$/', $user_id) ? 'https://vk.com/id' . $user_id : '',
                'attachments' => [],
            ];
        }

        if (!in_array($update_type, ['message_new', 'message_reply'], true)) {
            return [
                'update_type' => $update_type,
                'text' => '',
                'external_user_id' => '',
                'external_chat_id' => '',
                'external_message_id' => $this->extractString($payload, [['event_id']]),
                'display_name' => '',
                'username' => '',
                'profile_url' => '',
                'attachments' => [],
            ];
        }

        $message = is_array($payload['object']['message'] ?? null)
            ? $payload['object']['message']
            : (is_array($payload['object'] ?? null) ? $payload['object'] : []);
        $is_reply = $update_type === 'message_reply';
        $text = $this->messageText($message);
        $peer_id = (string)($message['peer_id'] ?? '');
        $from_id = (string)($message['from_id'] ?? '');
         
         
         
        $external_user_id = $is_reply && $peer_id !== '' ? $peer_id : $from_id;
        $external_chat_id = (string)($message['peer_id'] ?? $external_user_id);
        $external_message_id = $this->extractString($message, [['id']]);
        if ($external_message_id === '' || $external_message_id === '0') {
            $external_message_id = $this->extractString($message, [['conversation_message_id']]);
        }
        if ($external_message_id === '' || $external_message_id === '0') {
            $external_message_id = $this->extractString($payload, [['event_id']]);
        }

        $profile_url = preg_match('/^\d+$/', $external_user_id) ? 'https://vk.com/id' . $external_user_id : '';
        $is_start = !$is_reply && preg_match('/^(?:\/start|start|начать|старт)$/ui', trim($text)) === 1;

        return [
            'update_type' => $update_type,
            'event_kind' => $is_start ? 'conversation_start' : 'message',
            'start_payload' => $is_start ? $text : '',
            'dedupe_key' => $is_start ? 'vk:start:' . (string)($payload['event_id'] ?? $external_message_id) : '',
            'text' => $text,
            'external_user_id' => $external_user_id,
            'external_chat_id' => $external_chat_id,
            'external_message_id' => $external_message_id,
            'created' => (int)($message['date'] ?? 0),
            'direction' => $is_reply ? 'out' : 'in',
            'display_name' => 'Пользователь VK',
            'username' => '',
            'profile_url' => $profile_url,
            'attachments' => $this->extractAttachments($message),
            'metadata' => $this->messageMetadata($message),
        ];
    }

    


    public function sendMessage(string $token, string $external_user_id, string $text, array $attachments = [], array $options = []): array {
        $peer_id = $this->normalizePeerId((string)($options['external_chat_id'] ?? ''));
        if ($peer_id === '') {
            $peer_id = $this->normalizePeerId($external_user_id);
        }
        if ($peer_id === '') {
            return ['success' => false, 'error' => 'recipient_missing'];
        }

        $vk_attachments = [];
        $group_id = preg_replace('/\D+/', '', (string)($options['vk_group_id'] ?? $options['group_id'] ?? ''));

        foreach ($attachments as $attachment) {
            $filename = (string)($attachment['file_name'] ?? $attachment['filename'] ?? '');
            $path = (string)($attachment['path'] ?? '');
            $mime = (string)($attachment['mime_type'] ?? $attachment['mime'] ?? '');
            $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
            if ($extension === '' && $mime !== '') {
                $extension = AttachmentPolicy::extensionForMime($mime);
            }

            if (!is_file($path)) {
                return ['success' => false, 'error' => 'attachment_file_missing'];
            }
            $is_document = AttachmentPolicy::isDocumentExtension($extension) || AttachmentPolicy::isDocumentMime($mime);
            if ($is_document) {
                $validation = AttachmentPolicy::validateDocumentFile(
                    $path,
                    $filename,
                    (int)filesize($path),
                    max(1, (int)ceil(max(1, (int)filesize($path)) / 1024 / 1024))
                );
                if (empty($validation['success'])) {
                    return ['success' => false, 'error' => 'attachment_type_unsupported'];
                }
                $document_name = $filename !== '' ? $filename : 'document.' . (string)$validation['extension'];
                $upload = $this->uploadMessageDocument($token, $peer_id, $path, $document_name, (string)$validation['mime'], $group_id);
            } else {
                if (!in_array($extension, AttachmentPolicy::allowedImageExtensions(true), true)) {
                    return ['success' => false, 'error' => 'attachment_type_unsupported'];
                }

                $upload_mime = $mime !== '' ? $mime : AttachmentPolicy::mimeForExtension($extension);
                $upload = $this->uploadMessagePhoto($token, $peer_id, $path, $filename, $upload_mime, $group_id);
                if (empty($upload['success']) && in_array($extension, ['webp', 'bmp'], true)) {
                    $fallback = (new ImageVariantService())->createJpegFallback($path, $filename);
                    if (!empty($fallback['success'])) {
                        $retry = $this->uploadMessagePhoto($token, $peer_id, $fallback['path'], $fallback['filename'], $fallback['mime'], $group_id);
                        if (!empty($fallback['temporary']) && !empty($fallback['path'])) {
                            SafeRuntime::unlink((string)$fallback['path']);
                        }
                        if (!empty($retry['success'])) {
                            $upload = $retry;
                        }
                    }
                }
            }
            if (empty($upload['success'])) {
                return $upload;
            }

            $vk_attachments[] = $upload['attachment'];
        }

        $body = [
            'peer_id' => (int)$peer_id,
            'message' => $this->limitString($text, 4000),
            'random_id' => $this->deterministicRandomId($options),
        ];
        if ($vk_attachments) {
            $body['attachment'] = implode(',', $vk_attachments);
        }

        if ($body['message'] === '' && !$vk_attachments) {
            return ['success' => false, 'error' => 'message_empty'];
        }
        if ($body['message'] === '') {
            unset($body['message']);
        }

        $result = $this->apiJson('POST', '/method/messages.send', $token, $body, 'messages_send');
        if (empty($result['success'])) {
            return $result;
        }

        $external_message_id = (string)($result['json']['response'] ?? '');

        return [
            'success' => true,
            'http_code' => $result['http_code'],
            'external_message_id' => $external_message_id,
            'json' => $result['json'],
        ];
    }

    private function uploadMessageDocument(string $token, string $peer_id, string $path, string $filename, string $mime, string $group_id = ''): array {
        $body = [
            'peer_id' => (int)$peer_id,
            'type' => 'doc',
        ];
        if ($group_id !== '') {
             
             
             
            $body['group_id'] = (int)$group_id;
        }

        $upload_server = $this->apiJson('POST', '/method/docs.getMessagesUploadServer', $token, $body, 'docs_upload_server');
        if (empty($upload_server['success'])) {
            return $upload_server;
        }

        $upload_url = (string)($upload_server['json']['response']['upload_url'] ?? '');
        if ($upload_url === '') {
            return ['success' => false, 'error' => 'upload_url_missing', 'http_code' => $upload_server['http_code'] ?? 0, 'vk_step' => 'docs_upload_server'];
        }

        $upload = $this->postMessageDocumentUpload($upload_url, $path, $filename, $mime);
        if (empty($upload['success'])) {
            return $this->withVkStep($upload, 'docs_upload');
        }

        $file = (string)($upload['payload']['file'] ?? '');
        if ($file === '') {
            return ['success' => false, 'error' => 'upload_payload_invalid', 'http_code' => $upload['http_code'] ?? 0, 'vk_step' => 'docs_upload'];
        }

        $save = $this->apiJson('POST', '/method/docs.save', $token, [
            'file' => $file,
            'title' => $this->limitString($filename !== '' ? basename($filename) : 'document', 128),
        ], 'docs_save');
        if (empty($save['success'])) {
            return $save;
        }

        $doc = $this->extractSavedDocument($save['json']['response'] ?? null);
        if (empty($doc['owner_id']) || empty($doc['id'])) {
            return ['success' => false, 'error' => 'saved_document_invalid', 'http_code' => $save['http_code'] ?? 0, 'vk_step' => 'docs_save'];
        }

        $attachment = 'doc' . (string)$doc['owner_id'] . '_' . (string)$doc['id'];
        if (!empty($doc['access_key'])) {
            $attachment .= '_' . (string)$doc['access_key'];
        }

        return [
            'success' => true,
            'attachment' => $attachment,
            'http_code' => $save['http_code'],
            'json' => $save['json'],
        ];
    }

    private function postMessageDocumentUpload(string $upload_url, string $path, string $filename, string $mime): array {
        $response = $this->http->request('POST', $upload_url, [
            'post_fields' => ['file' => new CURLFile($path, $mime, $filename !== '' ? $filename : 'document')],
            'connect_timeout' => 4,
            'timeout' => 30,
            'trust_profile' => self::PROVIDER,
        ]);
        $http_code = (int)($response['http_code'] ?? 0);
        $raw = $response['raw'];
        if (empty($response['transport_ok']) || $http_code < 200 || $http_code >= 300 || $raw === '') {
            return [
                'success' => false,
                'error' => 'upload_failed',
                'http_code' => $http_code,
                'details' => $this->transportDetails($response),
            ];
        }

        $payload = json_decode((string)$raw, true);
        if (!is_array($payload)) {
            return ['success' => false, 'error' => 'upload_bad_json', 'http_code' => $http_code];
        }
        if (isset($payload['error'])) {
            return ['success' => false, 'error' => 'upload_api_error', 'http_code' => $http_code];
        }

        return ['success' => true, 'payload' => $payload, 'http_code' => $http_code];
    }

    private function extractSavedDocument($response): array {
        if (!is_array($response)) {
            return [];
        }
        if (isset($response['doc']) && is_array($response['doc'])) {
            return $response['doc'];
        }
        if (isset($response[0]) && is_array($response[0])) {
            if (isset($response[0]['doc']) && is_array($response[0]['doc'])) {
                return $response[0]['doc'];
            }
            return $response[0];
        }
        if (!empty($response['id']) && !empty($response['owner_id'])) {
            return $response;
        }
        return [];
    }

    private function uploadMessagePhoto(string $token, string $peer_id, string $path, string $filename, string $mime, string $group_id = ''): array {
        $body = [
            'peer_id' => (int)$peer_id,
        ];
        if ($group_id !== '') {
            $body['group_id'] = (int)$group_id;
        }

        $upload_server = $this->apiJson('POST', '/method/photos.getMessagesUploadServer', $token, $body, 'photos_upload_server');
        if (empty($upload_server['success'])) {
            return $upload_server;
        }

        $upload_url = (string)($upload_server['json']['response']['upload_url'] ?? '');
        if ($upload_url === '') {
            return ['success' => false, 'error' => 'upload_url_missing', 'http_code' => $upload_server['http_code'] ?? 0, 'vk_step' => 'photos_upload_server'];
        }

        $preferred_field = $this->messagePhotoUploadField($upload_url);
        $fields = array_values(array_unique([$preferred_field, $preferred_field === 'photo' ? 'file1' : 'photo']));
        $upload = [];
        $normalized = [];
        foreach ($fields as $field_name) {
            $upload = $this->postMessagePhotoUpload($upload_url, $field_name, $path, $filename, $mime);
            if (empty($upload['success'])) {
                continue;
            }
            $normalized = $this->normalizeMessagePhotoUploadPayload($upload['payload'], (int)$upload['http_code']);
            if (!empty($normalized['success'])) {
                break;
            }
            if (($normalized['error'] ?? '') === 'vk_bulk_upload_payload_unsupported') {
                return $this->withVkStep($normalized, 'photos_upload');
            }
        }

        if (empty($upload['success'])) {
            return $this->withVkStep($upload ?: ['success' => false, 'error' => 'upload_failed', 'http_code' => 0], 'photos_upload');
        }
        if (empty($normalized['success'])) {
            return $this->withVkStep($normalized ?: ['success' => false, 'error' => 'upload_payload_invalid', 'http_code' => $upload['http_code'] ?? 0], 'photos_upload');
        }

        $save = $this->apiJson('POST', '/method/photos.saveMessagesPhoto', $token, [
            'photo' => (string)$normalized['photo'],
            'server' => (int)$normalized['server'],
            'hash' => (string)$normalized['hash'],
        ], 'photos_save');
        if (empty($save['success'])) {
            return $save;
        }

        $photo = $save['json']['response'][0] ?? [];
        if (!is_array($photo) || empty($photo['owner_id']) || empty($photo['id'])) {
            return ['success' => false, 'error' => 'saved_photo_invalid', 'http_code' => $save['http_code'] ?? 0, 'vk_step' => 'photos_save'];
        }

        $attachment = 'photo' . (string)$photo['owner_id'] . '_' . (string)$photo['id'];
        if (!empty($photo['access_key'])) {
            $attachment .= '_' . (string)$photo['access_key'];
        }

        return [
            'success' => true,
            'attachment' => $attachment,
            'http_code' => $save['http_code'],
            'json' => $save['json'],
        ];
    }

    private function messagePhotoUploadField(string $upload_url): string {
        $path = strtolower((string)(parse_url($upload_url, PHP_URL_PATH) ?: ''));
        return strpos($path, '/v2/bulk_upload') !== false || strpos($path, 'bulk_upload') !== false ? 'file1' : 'photo';
    }

    private function postMessagePhotoUpload(string $upload_url, string $field_name, string $path, string $filename, string $mime): array {
        $response = $this->http->request('POST', $upload_url, [
            'post_fields' => [$field_name => new CURLFile($path, $mime, $filename)],
            'connect_timeout' => 4,
            'timeout' => 20,
            'trust_profile' => self::PROVIDER,
        ]);
        $http_code = (int)($response['http_code'] ?? 0);
        $raw = $response['raw'];
        if (empty($response['transport_ok']) || $http_code < 200 || $http_code >= 300 || $raw === '') {
            return [
                'success' => false,
                'error' => 'upload_failed',
                'http_code' => $http_code,
                'details' => $this->transportDetails($response),
            ];
        }

        $payload = json_decode((string)$raw, true);
        if (!is_array($payload)) {
            return ['success' => false, 'error' => 'upload_bad_json', 'http_code' => $http_code];
        }
        if (isset($payload['error'])) {
            return ['success' => false, 'error' => 'upload_api_error', 'http_code' => $http_code];
        }

        return ['success' => true, 'payload' => $payload, 'http_code' => $http_code];
    }

    private function normalizeMessagePhotoUploadPayload(array $payload, int $http_code): array {
        if (!empty($payload['photo']) && !empty($payload['server']) && !empty($payload['hash'])) {
            return [
                'success' => true,
                'photo' => (string)$payload['photo'],
                'server' => (int)$payload['server'],
                'hash' => (string)$payload['hash'],
            ];
        }

        $bulk_file = $payload['files']['file1'] ?? null;
        if (is_array($bulk_file) && (!empty($bulk_file['sha']) || !empty($bulk_file['secret']))) {
             
             
             
             
            return [
                'success' => false,
                'error' => 'vk_bulk_upload_payload_unsupported',
                'retryable' => true,
                'http_code' => $http_code,
            ];
        }

        return ['success' => false, 'error' => 'upload_payload_invalid', 'http_code' => $http_code];
    }

    


    private function apiJson(string $method, string $path, string $token, array $body, string $vk_step = ''): array {
        $body['access_token'] = trim($token);
        $body['v'] = self::API_VERSION;

        $response = $this->http->request($method, self::API_BASE . $path, [
            'headers' => [
                'Accept: application/json',
                'Content-Type: application/x-www-form-urlencoded',
            ],
            'body' => http_build_query($body),
            'connect_timeout' => 4,
            'timeout' => 12,
            'trust_profile' => self::PROVIDER,
        ]);
        $raw = $response['raw'];
        $http_code = (int)($response['http_code'] ?? 0);

        if (empty($response['transport_ok']) || $raw === '') {
            return $this->withVkStep([
                'success' => false,
                'error' => 'api_request_failed',
                'http_code' => $http_code,
                'details' => $this->transportDetails($response),
            ], $vk_step);
        }

        $json = json_decode((string)$raw, true);
        if (!is_array($json)) {
            return $this->withVkStep(['success' => false, 'error' => 'api_bad_json', 'http_code' => $http_code], $vk_step);
        }

        if ($http_code < 200 || $http_code >= 300) {
            $error = [
                'success' => false,
                'error' => 'api_http_error',
                'http_code' => $http_code,
                'json' => $json,
            ];
            if ($http_code === 429) {
                $error['error'] = 'vk_api_rate_limited';
                $error['rate_limited'] = true;
            }
            return $this->withVkStep($error, $vk_step);
        }

        if (isset($json['error'])) {
            $error_code = (string)($json['error']['error_code'] ?? '');
            $error = [
                'success' => false,
                'error' => $error_code !== '' ? 'vk_api_error_' . $error_code : 'api_error',
                'error_message' => (string)($json['error']['error_msg'] ?? ''),
                'http_code' => $http_code,
                'json' => $json,
            ];
            if ($error_code === '29') {
                $error['rate_limited'] = true;
            }
            return $this->withVkStep($error, $vk_step);
        }

        return ['success' => true, 'http_code' => $http_code, 'json' => $json];
    }

    private function withVkStep(array $result, string $vk_step): array {
        if ($vk_step !== '') {
            $result['vk_step'] = $vk_step;
        }
        return $result;
    }

    private function isTruthyVkFlag($value): bool {
        if (is_bool($value)) {
            return $value;
        }
        if (is_int($value) || is_float($value)) {
            return (int)$value === 1;
        }
        $value = strtolower(trim((string)$value));
        return in_array($value, ['1', 'true', 'yes', 'on'], true);
    }

    private function chooseUserAvatarUrl(array $user): string {
        foreach (['photo_200', 'photo_max', 'photo_100'] as $field) {
            $url = trim((string)($user[$field] ?? ''));
            if ($url === '' || strpos($url, '/images/camera_') !== false) {
                continue;
            }
            if ($this->isAllowedPhotoUrl($url)) {
                return $url;
            }
        }
        return '';
    }

    private function extractAttachments(array $message): array {
        $attachments = [];
        $visited = 0;
        $this->collectMessageAttachments($message, $attachments, 0, $visited);

        $unique = [];
        $result = [];
        foreach ($attachments as $attachment) {
            $fingerprint = $this->attachmentFingerprint($attachment);
            if ($fingerprint !== '' && isset($unique[$fingerprint])) {
                continue;
            }
            if ($fingerprint !== '') {
                $unique[$fingerprint] = true;
            }
            $result[] = $attachment;
        }

        return $result;
    }

    private function collectMessageAttachments(array $message, array &$attachments, int $depth, int &$visited): void {
        if ($depth > self::MAX_FORWARDED_DEPTH || $visited >= self::MAX_FORWARDED_MESSAGES) {
            return;
        }
        $visited++;

        foreach ($this->extractDirectAttachments($message) as $attachment) {
            $attachments[] = $attachment;
        }

        $forwarded = is_array($message['fwd_messages'] ?? null) ? $message['fwd_messages'] : [];
        foreach ($forwarded as $forwarded_message) {
            if (!is_array($forwarded_message)) {
                continue;
            }
            $this->collectMessageAttachments($forwarded_message, $attachments, $depth + 1, $visited);
            if ($visited >= self::MAX_FORWARDED_MESSAGES) {
                break;
            }
        }
    }

    private function extractDirectAttachments(array $message): array {
        $items = is_array($message['attachments'] ?? null) ? $message['attachments'] : [];
        $attachments = [];

        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            $type = strtolower((string)($item['type'] ?? ''));
            if ($type === 'doc') {
                $doc = is_array($item['doc'] ?? null) ? $item['doc'] : [];
                $attachment = $this->extractDocumentAttachment($doc);
                $attachments[] = $attachment ?: ['type' => 'doc', 'unsupported' => true];
                continue;
            }

            if ($type !== 'photo') {
                $attachments[] = ['type' => $type ?: 'unknown', 'unsupported' => true];
                continue;
            }

            $photo = is_array($item['photo'] ?? null) ? $item['photo'] : [];
            $url = $this->chooseBestPhotoUrl($photo);
            if ($url === '') {
                $attachments[] = ['type' => 'photo', 'unsupported' => true];
                continue;
            }

            $attachments[] = [
                'type' => 'image',
                'url' => $url,
                'payload_hash' => substr(hash('sha256', json_encode($photo, JSON_UNESCAPED_UNICODE)), 0, 16),
            ];
        }

        return $attachments;
    }

    private function attachmentFingerprint(array $attachment): string {
        $payload_hash = trim((string)($attachment['payload_hash'] ?? ''));
        if ($payload_hash !== '') {
            return 'payload:' . $payload_hash;
        }
        $url = trim((string)($attachment['url'] ?? ''));
        if ($url !== '') {
            return 'url:' . strtolower((string)($attachment['type'] ?? '')) . ':' . $url;
        }
        return '';
    }

    private function normalizedMessageResult(array $message, int $http_code): array {
        return [
            'success' => true,
            'http_code' => $http_code,
            'text' => $this->messageText($message),
            'attachments' => $this->extractAttachments($message),
            'metadata' => $this->messageMetadata($message),
            'json' => $message,
        ];
    }

    private function messageText(array $message): string {
        $text = trim((string)($message['text'] ?? ''));
        if ($text !== '') {
            return $text;
        }

        $parts = [];
        $visited = 0;
        $this->collectForwardedText($message, $parts, 0, $visited);
        $text = trim(implode("\n", array_values(array_unique($parts))));
        return $this->limitString($text, 4000);
    }

    private function collectForwardedText(array $message, array &$parts, int $depth, int &$visited): void {
        if ($depth > self::MAX_FORWARDED_DEPTH || $visited >= self::MAX_FORWARDED_MESSAGES) {
            return;
        }
        $forwarded = is_array($message['fwd_messages'] ?? null) ? $message['fwd_messages'] : [];
        foreach ($forwarded as $forwarded_message) {
            if (!is_array($forwarded_message) || $visited >= self::MAX_FORWARDED_MESSAGES) {
                continue;
            }
            $visited++;
            $text = trim((string)($forwarded_message['text'] ?? ''));
            if ($text !== '') {
                $parts[] = $this->limitString($text, 4000);
            }
            $this->collectForwardedText($forwarded_message, $parts, $depth + 1, $visited);
        }
    }

    private function messageMetadata(array $message): array {
        $forwarded = is_array($message['fwd_messages'] ?? null) ? $message['fwd_messages'] : [];
        if (!$forwarded) {
            return [];
        }

        $source = [];
        foreach ($forwarded as $candidate) {
            if (is_array($candidate)) {
                $source = $candidate;
                break;
            }
        }
        $source_id = trim((string)($source['from_id'] ?? ''));
        $source_type = 'unknown';
        if (preg_match('/^-\d+$/', $source_id) === 1) {
            $source_type = 'community';
        } elseif (preg_match('/^\d+$/', $source_id) === 1) {
            $source_type = 'user';
        }

        return [
            'forwarded' => true,
            'forward_source_id' => $source_id,
            'forward_source_type' => $source_type,
            'forward_source_name' => $this->limitString((string)($source['from_name'] ?? ''), 120),
        ];
    }

    private function isForwardedMessage(array $message): bool {
        return !empty($message['fwd_messages']) && is_array($message['fwd_messages']);
    }

    private function messageMatchesEvent(array $message, array $event): bool {
        if (!$message) {
            return false;
        }

        $matched = false;
        $expected_peer = $this->normalizePeerId((string)($event['external_chat_id'] ?? ''));
        $actual_peer = $this->normalizePeerId((string)($message['peer_id'] ?? ''));
        if ($expected_peer !== '' && $actual_peer !== '') {
            if (!hash_equals($expected_peer, $actual_peer)) {
                return false;
            }
            $matched = true;
        }

        $expected_time = (int)($event['created'] ?? 0);
        $actual_time = (int)($message['date'] ?? 0);
        if ($expected_time > 0 && $actual_time > 0) {
            if (abs($expected_time - $actual_time) > 120) {
                return false;
            }
            $matched = true;
        }

        if (array_key_exists('out', $message) && isset($event['direction'])) {
            $expected_out = (string)$event['direction'] === 'out' ? 1 : 0;
            if ((int)$message['out'] !== $expected_out) {
                return false;
            }
            $matched = true;
        }

        $expected_text = trim((string)($event['text'] ?? ''));
        if ($expected_text !== '') {
            if (!hash_equals($expected_text, $this->messageText($message))) {
                return false;
            }
            $matched = true;
        }

        return $matched;
    }

    private function extractDocumentAttachment(array $doc): array {
        $extension = strtolower(trim((string)($doc['ext'] ?? '')));
        $title = trim((string)($doc['title'] ?? ''));
        $url = trim((string)($doc['url'] ?? ''));

        if ($extension === '' && $title !== '') {
            $extension = strtolower(pathinfo($title, PATHINFO_EXTENSION));
        }
        if ($url === '' || !$this->isAllowedPhotoUrl($url)) {
            return [];
        }
        if (!in_array($extension, AttachmentPolicy::allowedAttachmentExtensions(true), true)) {
            return [];
        }

        $payload_hash = substr(hash('sha256', json_encode([
            'id' => $doc['id'] ?? '',
            'owner_id' => $doc['owner_id'] ?? '',
            'access_key' => $doc['access_key'] ?? '',
            'size' => $doc['size'] ?? '',
            'url' => $url,
        ], JSON_UNESCAPED_UNICODE)), 0, 16);

        return [
            'type' => AttachmentPolicy::isImageExtension($extension, true) ? 'image' : 'document',
            'kind' => AttachmentPolicy::isImageExtension($extension, true) ? 'image' : 'document',
            'url' => $url,
            'file_name' => $title !== '' ? $title : (AttachmentPolicy::isDocumentExtension($extension) ? 'document.' . $extension : 'image.' . $extension),
            'mime_type' => AttachmentPolicy::mimeForExtension($extension),
            'size' => max(0, (int)($doc['size'] ?? 0)),
            'payload_hash' => $payload_hash,
        ];
    }

    private function chooseBestPhotoUrl(array $photo): string {
        $best_url = '';
        $best_area = 0;
        $sizes = is_array($photo['sizes'] ?? null) ? $photo['sizes'] : [];

        foreach ($sizes as $size) {
            if (!is_array($size)) {
                continue;
            }
            $url = (string)($size['url'] ?? '');
            if ($url === '' || !$this->isAllowedPhotoUrl($url)) {
                continue;
            }
            $area = max(1, (int)($size['width'] ?? 0)) * max(1, (int)($size['height'] ?? 0));
            if ($area >= $best_area) {
                $best_area = $area;
                $best_url = $url;
            }
        }

        if ($best_url === '' && !empty($photo['orig_photo']['url']) && $this->isAllowedPhotoUrl((string)$photo['orig_photo']['url'])) {
            $best_url = (string)$photo['orig_photo']['url'];
        }

        return $best_url;
    }

    private function isAllowedPhotoUrl(string $url): bool {
        $parts = parse_url($url);
        if (!is_array($parts)) {
            return false;
        }
        $scheme = strtolower((string)($parts['scheme'] ?? ''));
        $host = strtolower(trim((string)($parts['host'] ?? ''), ". \t\n\r\0\x0B"));
        if ($scheme !== 'https' || $host === '') {
            return false;
        }

        $allowed_exact = ['vk.com', 'vk.ru', 'vk-cdn.net', 'userapi.com'];
        if (in_array($host, $allowed_exact, true)) {
            return true;
        }

        foreach (['.vk.com', '.vk.ru', '.vk-cdn.net', '.userapi.com'] as $suffix) {
            if (substr($host, -strlen($suffix)) === $suffix) {
                return true;
            }
        }

        return false;
    }

    private function extractString(array $data, array $paths): string {
        foreach ($paths as $path) {
            $cursor = $data;
            foreach ($path as $part) {
                if (!is_array($cursor) || !array_key_exists($part, $cursor)) {
                    $cursor = null;
                    break;
                }
                $cursor = $cursor[$part];
            }
            if (is_scalar($cursor) && trim((string)$cursor) !== '') {
                return trim((string)$cursor);
            }
        }
        return '';
    }

    private function normalizePeerId(string $value): string {
        $value = trim($value);
        return preg_match('/^-?\d+$/', $value) ? $value : '';
    }

    private function sanitizeVkScreenName(string $value): string {
        $value = trim($value);
        return preg_match('/^[A-Za-z0-9_.-]{1,64}$/', $value) ? $value : '';
    }

    private function limitString(string $value, int $limit): string {
        if ($limit <= 0) {
            return '';
        }
        if (function_exists('mb_substr')) {
            return mb_substr($value, 0, $limit, 'UTF-8');
        }
        return substr($value, 0, $limit);
    }

    private function pauseVkSetupStep(): void {
        usleep(1000000);
    }

    private function deterministicRandomId(array $options): int {
        $seed = (string)($options['dedupe_key'] ?? '');
        if ($seed === '') {
            $seed = (string)($options['message_id'] ?? '');
        }
        if ($seed === '') {
            $seed = bin2hex(random_bytes(8));
        }
        $id = (int)(sprintf('%u', crc32($seed)) % 2147483647);
        return $id > 0 ? $id : 1;
    }

    private function transportDetails(array $response): string {
        if (!empty($response['tls_fallback_used'])) {
            return 'tls_fallback';
        }
        if (!empty($response['curl_error'])) {
            return 'curl';
        }
        if (($response['raw'] ?? null) === '') {
            return 'empty';
        }
        return '';
    }
}
