<?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 MaxBotProvider implements MessengerProviderInterface {

    const API_BASE = 'https://platform-api2.max.ru';
    const PROVIDER = 'max-bot';
    const TRUST_PROFILE = 'max';

    private $http;

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

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

    public function getBotInfo(string $token): array {
        return $this->apiJson('GET', '/me', $token);
    }

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

        $body = [
            'url' => $webhook_url,
            'update_types' => ['message_created', 'bot_started'],
            'secret' => $secret,
        ];
        $sync = $this->apiJson('POST', '/subscriptions', $token, $body);
        if (empty($sync['success'])) {
            return $sync;
        }

        return [
            'success' => true,
            'bot' => $me['json'],
            'response' => $sync['json'],
            'http_code' => $sync['http_code'],
        ];
    }

    public function parseWebhook(array $payload): array {
        $update_type = $this->extractString($payload, [
            ['update_type'], ['type'], ['event'], ['chat_event', 'type'],
        ]);

        $text = $this->extractString($payload, [
            ['message', 'body', 'text'],
            ['body', 'text'],
            ['message', 'text'],
            ['text'],
            ['chat_event', 'payload', 'text'],
        ]);
        $forwarded_message = $this->forwardedMessage($payload);
        if ($text === '' && $forwarded_message) {
            $text = $this->extractString($forwarded_message, [
                ['body', 'text'], ['text'],
            ]);
        }

        $external_user_id = $this->normalizeUserId($this->extractString($payload, [
            ['message', 'sender', 'user_id'],
            ['sender', 'user_id'],
            ['user', 'user_id'],
            ['message', 'recipient', 'user_id'],
            ['chat_event', 'user', 'user_id'],
        ]));
        $external_chat_id = $this->normalizeChatId($this->extractString($payload, [
            ['message', 'recipient', 'chat_id'],
            ['recipient', 'chat_id'],
            ['message', 'chat_id'],
            ['chat', 'chat_id'],
            ['chat_id'],
        ]));
        if ($external_chat_id === '') {
            $external_chat_id = $external_user_id;
        }

        $display_name = $this->extractDisplayName($payload, [
            ['message', 'sender'],
            ['sender'],
            ['user'],
            ['chat_event', 'user'],
        ]);

        $username = $this->extractString($payload, [
            ['message', 'sender', 'username'],
            ['sender', 'username'],
            ['user', 'username'],
            ['chat_event', 'user', 'username'],
        ]);

        $avatar_url = $this->extractAvatarUrl($payload);

        $external_message_id = $this->extractString($payload, [
            ['message', 'body', 'mid'],
            ['message', 'mid'],
            ['body', 'mid'],
            ['message_id'],
            ['mid'],
        ]);

        $attachments = $this->extractAttachments($payload);
        $metadata = $this->forwardMetadata($payload, $forwarded_message);
        $provider_internal = [];
        if (!empty($metadata['forwarded']) && !$forwarded_message) {
            $forward_reference_id = $this->forwardReferenceId($this->forwardLink($payload));
            if ($forward_reference_id !== '') {
                 
                 
                $provider_internal['forward_reference_id'] = $forward_reference_id;
            }
        }
        $safe_username = preg_match('/^[A-Za-z0-9_.-]{1,64}$/', ltrim($username, '@')) ? ltrim($username, '@') : '';
        $profile_url = $safe_username !== '' ? 'https://max.ru/u/' . $safe_username : '';
        $created = $this->extractTimestamp($payload, [
            ['message', 'body', 'created'],
            ['message', 'body', 'created_at'],
            ['message', 'created'],
            ['message', 'created_at'],
            ['message', 'timestamp'],
            ['body', 'created'],
            ['body', 'created_at'],
            ['chat_event', 'created'],
            ['chat_event', 'created_at'],
            ['timestamp'],
            ['created'],
            ['created_at'],
        ]);
        $published_at = $this->extractString($payload, [
            ['message', 'body', 'published_at'],
            ['message', 'published_at'],
            ['body', 'published_at'],
            ['chat_event', 'published_at'],
            ['published_at'],
        ]);
        if ($created <= 0) {
             
             
             
             
            $published_at = $published_at !== '' ? $published_at : $this->extractString($payload, [
                ['message', 'body', 'created'],
                ['message', 'body', 'created_at'],
                ['message', 'created'],
                ['message', 'created_at'],
                ['message', 'timestamp'],
                ['body', 'created'],
                ['body', 'created_at'],
                ['chat_event', 'created'],
                ['chat_event', 'created_at'],
                ['timestamp'],
                ['created'],
                ['created_at'],
            ]);
        }

        $provider_event_id = $this->extractString($payload, [['update_id'], ['event_id'], ['id']]);
        $start_dedupe_key = '';
        if ($update_type === 'bot_started') {
            if ($provider_event_id !== '') {
                $start_dedupe_key = 'max:start:event:' . $provider_event_id;
            } elseif ($created > 0 || $published_at !== '') {
                $start_dedupe_key = 'max:start:' . hash('sha256', $external_chat_id . ':' . $external_user_id . ':' . $created . ':' . $published_at);
            }
        }

        return [
            'update_type' => $update_type,
            'event_kind' => $update_type === 'bot_started' ? 'conversation_start' : 'message',
            'start_payload' => $update_type === 'bot_started' ? $text : '',
            'dedupe_key' => $start_dedupe_key,
            'text' => $text,
            'external_user_id' => $external_user_id,
            'external_chat_id' => $external_chat_id,
            'external_message_id' => $external_message_id,
            'created' => $created,
            'published_at' => $published_at,
            'display_name' => $display_name,
            'username' => $username,
            'profile_url' => $profile_url,
            'avatar_url' => $avatar_url,
            'attachments' => $attachments,
            'metadata' => $metadata,
            '_provider_internal' => $provider_internal,
        ];
    }

    




    public function hydrateForwardedEvent(string $token, array $event): array {
        $metadata = is_array($event['metadata'] ?? null) ? $event['metadata'] : [];
        $provider_internal = is_array($event['_provider_internal'] ?? null) ? $event['_provider_internal'] : [];
        if (empty($metadata['forwarded']) || !$provider_internal) {
            return ['success' => true, 'event' => $event, 'lookup' => false];
        }

        $message_id = trim((string)($provider_internal['forward_reference_id'] ?? ''));
        if (!preg_match('/^[A-Za-z0-9_\-]{1,128}$/', $message_id)) {
            return ['success' => true, 'event' => $event, 'lookup' => false];
        }

        $result = $this->apiJson('GET', '/messages/' . rawurlencode($message_id), $token);
        if (empty($result['success'])) {
            return [
                'success' => false,
                'event' => $event,
                'error' => (string)($result['error'] ?? 'forward_lookup_failed'),
                'http_code' => (int)($result['http_code'] ?? 0),
            ];
        }

        $json = is_array($result['json'] ?? null) ? $result['json'] : [];
        $message = is_array($json['message'] ?? null) ? $json['message'] : $json;
        if (!$message) {
            return ['success' => false, 'event' => $event, 'error' => 'forward_message_missing', 'http_code' => (int)($result['http_code'] ?? 0)];
        }

        if (trim((string)($event['text'] ?? '')) === '') {
            $event['text'] = $this->extractString($message, [['body', 'text'], ['text']]);
        }
        $current_attachments = is_array($event['attachments'] ?? null) ? $event['attachments'] : [];
        $event['attachments'] = $this->mergeAttachments($current_attachments, $this->extractAttachments(['message' => $message]));

        if (trim((string)($metadata['forward_source_name'] ?? '')) === '') {
            $metadata['forward_source_name'] = $this->extractDisplayName($message, [['sender']]);
        }
        $event['metadata'] = $metadata;
        unset($event['_provider_internal']);

        return ['success' => true, 'event' => $event, 'lookup' => true];
    }

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

        $max_attachments = [];
        $has_file_attachment = false;
        $has_image_attachment = false;
        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'];
                }
                $has_file_attachment = true;
                $upload = $this->uploadMedia($token, 'file', $path, $filename, (string)$validation['mime']);
            } else {
                if (!AttachmentPolicy::isMaxImageExtension($extension)) {
                    return ['success' => false, 'error' => 'attachment_type_unsupported'];
                }
                $has_image_attachment = true;
                $upload = $this->uploadImage($token, $path, $filename, $mime ?: AttachmentPolicy::mimeForExtension($extension));
                if (empty($upload['success']) && $extension === 'webp') {
                    $fallback = (new ImageVariantService())->createJpegFallback($path, $filename);
                    if (!empty($fallback['success'])) {
                        $retry = $this->uploadImage($token, $fallback['path'], $fallback['filename'], $fallback['mime']);
                        if (!empty($fallback['temporary']) && !empty($fallback['path'])) {
                            SafeRuntime::unlink((string)$fallback['path']);
                        }
                        if (!empty($retry['success'])) {
                            $upload = $retry;
                        }
                    }
                }
            }
            if (empty($upload['success'])) {
                return $upload;
            }
            $max_attachments[] = [
                'type' => $is_document ? 'file' : 'image',
                'payload' => $upload['payload'] ?? [],
            ];
        }

        if ($has_file_attachment && $has_image_attachment) {
            return ['success' => false, 'error' => 'attachment_type_unsupported'];
        }

        $body = [
            'text' => mb_substr($text, 0, 4000, 'UTF-8'),
            'notify' => true,
        ];
        if (($options['text_format'] ?? '') === 'markdown') {
            $body['format'] = 'markdown';
        }
        if ($max_attachments) {
            $body['attachments'] = $max_attachments;
        }

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

        $message_path = '/messages?user_id=' . rawurlencode($user_id);
        $result = $this->apiJson('POST', $message_path, $token, $body);
        if (empty($result['success']) && $max_attachments && $this->isAttachmentNotReady($result)) {
             
             
             
             
            for ($attempt = 0; $attempt < 2 && empty($result['success']) && $this->isAttachmentNotReady($result); $attempt++) {
                usleep(600000);
                $result = $this->apiJson('POST', $message_path, $token, $body);
            }
        }
        if (empty($result['success'])) {
            return $result;
        }

        $message = is_array($result['json']['message'] ?? null) ? $result['json']['message'] : [];
        $external_message_id = $this->extractString($message, [
            ['body', 'mid'], ['mid'], ['message_id'],
        ]);

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

    private function uploadImage(string $token, string $path, string $filename, string $mime): array {
        return $this->uploadMedia($token, 'image', $path, $filename, $mime);
    }

    private function uploadMedia(string $token, string $type, string $path, string $filename, string $mime): array {
        $type = $type === 'file' ? 'file' : 'image';
        $upload = $this->apiJson('POST', '/uploads?type=' . $type, $token);
        if (empty($upload['success'])) {
            return $upload;
        }

        $upload_url = (string)($upload['json']['url'] ?? '');
        $initial_token = (string)($upload['json']['token'] ?? '');
        if ($upload_url === '') {
            return ['success' => false, 'error' => 'upload_url_missing', 'http_code' => $upload['http_code'] ?? 0];
        }

        $curl_file = new CURLFile($path, $mime, $filename);
        $response = $this->http->request('POST', $upload_url, [
            'post_fields' => ['data' => $curl_file],
            'connect_timeout' => 4,
            'timeout' => 20,
            'trust_profile' => self::TRUST_PROFILE,
        ]);
        $raw = $response['raw'];
        $http_code = (int)($response['http_code'] ?? 0);

        if (empty($response['transport_ok']) || $http_code < 200 || $http_code >= 300) {
            return [
                'success' => false,
                'error' => 'upload_failed',
                'http_code' => $http_code,
                'details' => $this->transportDetails($response),
            ];
        }

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

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

    private function isAttachmentNotReady(array $result): bool {
        $error = strtolower(trim((string)($result['error'] ?? '')));
        if ($error === 'attachment.not.ready' || $error === 'attachment_not_ready') {
            return true;
        }

        $json = is_array($result['json'] ?? null) ? $result['json'] : [];
        $code = strtolower(trim($this->extractString($json, [
            ['code'], ['error'], ['error', 'code'], ['error', 'message'], ['message'],
        ])));
        return $code === 'attachment.not.ready' || $code === 'attachment_not_ready';
    }

    private function apiJson(string $method, string $path, string $token, ?array $body = null): array {
        $headers = [
            'Accept: application/json',
            $this->authorizationHeader($token),
        ];
        $request = [
            'headers' => $headers,
            'connect_timeout' => 4,
            'timeout' => 12,
            'trust_profile' => self::TRUST_PROFILE,
        ];
        if ($body !== null) {
            $headers[] = 'Content-Type: application/json';
            $request['headers'] = $headers;
            $request['body'] = json_encode($body, JSON_UNESCAPED_UNICODE);
        }

        $response = $this->http->request($method, self::API_BASE . $path, $request);
        $raw = $response['raw'];
        $http_code = (int)($response['http_code'] ?? 0);

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

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

        if ($http_code < 200 || $http_code >= 300) {
            $code = $this->extractString($json, [['code'], ['error', 'code'], ['error']]);
            return [
                'success' => false,
                'error' => $code !== '' ? $code : 'api_http_error',
                'http_code' => $http_code,
                'json' => $json,
            ];
        }

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

    private function authorizationHeader(string $token): string {
        $token = trim($token);
        if (preg_match('/^Bearer\s+/i', $token)) {
            $token = trim((string)preg_replace('/^Bearer\s+/i', '', $token));
        }
        return 'Authorization: ' . $token;
    }

    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 '';
    }

    private function extractAttachments(array $payload): array {
        $candidate_groups = [];
        $body = $payload['message']['body'] ?? $payload['body'] ?? null;
        if (is_array($body) && isset($body['attachments']) && is_array($body['attachments'])) {
            $candidate_groups[] = $body['attachments'];
        } elseif (isset($payload['message']['attachments']) && is_array($payload['message']['attachments'])) {
            $candidate_groups[] = $payload['message']['attachments'];
        } elseif (isset($payload['attachments']) && is_array($payload['attachments'])) {
            $candidate_groups[] = $payload['attachments'];
        }

        $forwarded_message = $this->forwardedMessage($payload);
        if ($forwarded_message) {
            $forwarded_body = is_array($forwarded_message['body'] ?? null) ? $forwarded_message['body'] : [];
            if (isset($forwarded_body['attachments']) && is_array($forwarded_body['attachments'])) {
                $candidate_groups[] = $forwarded_body['attachments'];
            } elseif (isset($forwarded_message['attachments']) && is_array($forwarded_message['attachments'])) {
                $candidate_groups[] = $forwarded_message['attachments'];
            }
        }

        $attachments = [];
        $seen = [];
        $candidates = [];
        foreach ($candidate_groups as $group) {
            foreach ($group as $candidate) {
                $candidates[] = $candidate;
            }
        }
        foreach ($candidates as $item) {
            if (!is_array($item)) {
                continue;
            }
            $type = strtolower((string)($item['type'] ?? ''));
            $payload_item = is_array($item['payload'] ?? null) ? $item['payload'] : [];
            if ($type === 'audio' || $type === 'voice') {
                $url = $this->extractString($payload_item, [
                    ['url'], ['download_url'], ['file_url'], ['link'], ['audio', 'url'], ['audio', 'download_url'],
                    ['media', 'url'], ['media', 'download_url'], ['file', 'url'], ['file', 'download_url'],
                ]);
                $file_name = $this->extractString($payload_item, [
                    ['filename'], ['file_name'], ['name'], ['audio', 'filename'], ['audio', 'file_name'],
                    ['media', 'filename'], ['media', 'file_name'], ['file', 'filename'], ['file', 'file_name'],
                ]);
                $mime = strtolower($this->extractString($payload_item, [
                    ['mime_type'], ['mime'], ['content_type'], ['audio', 'mime_type'], ['audio', 'mime'],
                    ['media', 'mime_type'], ['media', 'mime'], ['file', 'mime_type'], ['file', 'mime'],
                ]));
                $duration = $this->extractTimestamp($payload_item, [
                    ['duration'], ['duration_seconds'], ['audio', 'duration'], ['media', 'duration'],
                ]);
                $attachment = [
                    'type' => 'audio',
                    'url' => $url,
                    'file_name' => $file_name !== '' ? $file_name : 'max-voice.m4a',
                    'mime_type' => $mime,
                    'duration' => max(0, $duration),
                    'file_size' => max(0, (int)($payload_item['size'] ?? $payload_item['file_size'] ?? 0)),
                    'waveform' => (string)($payload_item['wave'] ?? $payload_item['waveform'] ?? ''),
                    'payload_hash' => hash('sha256', 'audio:' . json_encode($payload_item, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)),
                ];
                $this->appendUniqueAttachment($attachments, $seen, $attachment);
                continue;
            }
            if ($type !== 'image') {
                if ($type === 'file') {
                    $url = $this->extractString($payload_item, [
                        ['url'], ['download_url'], ['file_url'], ['link'], ['attachment_url'], ['media', 'url'], ['media', 'download_url'],
                        ['file', 'url'], ['file', 'download_url'],
                    ]);
                    $file_name = $this->extractString($payload_item, [
                        ['filename'], ['file_name'], ['name'], ['title'], ['media', 'filename'], ['media', 'file_name'], ['media', 'name'],
                        ['file', 'filename'], ['file', 'file_name'], ['file', 'name'],
                    ]);
                    $mime = strtolower($this->extractString($payload_item, [
                        ['mime_type'], ['mime'], ['content_type'], ['media', 'mime_type'], ['media', 'mime'],
                        ['file', 'mime_type'], ['file', 'mime'], ['file', 'content_type'],
                    ]));
                    if (AttachmentPolicy::isAudioMime($mime) || AttachmentPolicy::isAudioExtension(strtolower(pathinfo($file_name, PATHINFO_EXTENSION)))) {
                        $attachment = [
                            'type' => 'audio',
                            'url' => $url,
                            'file_name' => $file_name !== '' ? $file_name : 'max-voice.m4a',
                            'mime_type' => $mime,
                            'duration' => max(0, (int)($payload_item['duration'] ?? 0)),
                            'file_size' => max(0, (int)($payload_item['size'] ?? $payload_item['file_size'] ?? 0)),
                            'waveform' => (string)($payload_item['wave'] ?? $payload_item['waveform'] ?? ''),
                            'payload_hash' => hash('sha256', 'audio-file:' . json_encode($payload_item, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)),
                        ];
                        $this->appendUniqueAttachment($attachments, $seen, $attachment);
                        continue;
                    }
                    if ($file_name === '' && $url !== '') {
                        $file_name = basename((string)(parse_url($url, PHP_URL_PATH) ?: ''));
                    }
                    $extension = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
                    if ($extension === '' && $mime !== '') {
                        $extension = AttachmentPolicy::extensionForMime($mime);
                    }
                    if (AttachmentPolicy::isDocumentExtension($extension) || AttachmentPolicy::isDocumentMime($mime)) {
                        $fallback_extension = AttachmentPolicy::isDocumentExtension($extension) ? $extension : AttachmentPolicy::extensionForMime($mime);
                        $attachment = [
                            'type' => 'document',
                            'url' => $url,
                            'file_name' => $file_name !== '' ? $file_name : ('max-document' . ($fallback_extension !== '' ? '.' . $fallback_extension : '')),
                            'mime_type' => $mime,
                            'token' => (string)($payload_item['token'] ?? ''),
                            'payload_hash' => substr(hash('sha256', json_encode($payload_item, JSON_UNESCAPED_UNICODE)), 0, 16),
                        ];
                        $this->appendUniqueAttachment($attachments, $seen, $attachment);
                        continue;
                    }
                }
                $attachments[] = ['type' => $type ?: 'unknown', 'unsupported' => true];
                continue;
            }
            $url = $this->extractString($payload_item, [
                ['url'], ['download_url'], ['image_url'], ['photo', 'url'], ['photos', 'url'],
                ['media', 'url'], ['media', 'download_url'], ['image', 'url'], ['image', 'download_url'],
            ]);
            $attachment = [
                'type' => 'image',
                'url' => $url,
                'token' => (string)($payload_item['token'] ?? ''),
                'payload_hash' => substr(hash('sha256', json_encode($payload_item, JSON_UNESCAPED_UNICODE)), 0, 16),
            ];
            $this->appendUniqueAttachment($attachments, $seen, $attachment);
        }
        return $attachments;
    }

    private function forwardMetadata(array $payload, array $forwarded_message): array {
        $link = $this->forwardLink($payload);
        if (!$link) {
            return [];
        }

        $source_name = $this->extractString($link, [['chat_name'], ['chatName'], ['title']]);
        if ($source_name === '' && $forwarded_message) {
            $source_name = $this->extractDisplayName($forwarded_message, [['sender']]);
            if ($source_name === '') {
                $source_name = $this->extractString($forwarded_message, [['recipient', 'title']]);
            }
        }
        if ($source_name === '') {
            $source_name = $this->extractDisplayName($link, [['sender']]);
        }

        $source_type = $this->extractString($link, [['chat_access_type'], ['chatAccessType']]);
        if ($source_type === '' && $forwarded_message) {
            $source_type = $this->extractString($forwarded_message, [['recipient', 'chat_type'], ['recipient', 'type']]);
        }
        if ($source_type === '' && ($this->extractString($link, [['sender', 'user_id']]) !== '' || $this->extractString($forwarded_message, [['sender', 'user_id']]) !== '')) {
            $source_type = 'user';
        }

        return [
            'forwarded' => 1,
            'forward_source_name' => $this->plainForwardLabel($source_name),
            'forward_source_type' => $this->plainForwardType($source_type),
        ];
    }

    private function forwardLink(array $payload): array {
        $message = is_array($payload['message'] ?? null) ? $payload['message'] : $payload;
        $link = is_array($message['link'] ?? null) ? $message['link'] : [];
        return strtoupper(trim((string)($link['type'] ?? ''))) === 'FORWARD' ? $link : [];
    }

    private function forwardedMessage(array $payload): array {
        $link = $this->forwardLink($payload);
        if (!$link) {
            return [];
        }
        foreach (['message', 'linked_message', 'forwarded_message'] as $key) {
            if (is_array($link[$key] ?? null)) {
                return $link[$key];
            }
        }
        return [];
    }

    private function forwardReferenceId(array $link): string {
        $value = $this->extractString($link, [
            ['mid'], ['message_id'], ['messageId'],
            ['message', 'body', 'mid'], ['message', 'mid'], ['message', 'message_id'], ['message', 'messageId'],
        ]);
        return preg_match('/^[A-Za-z0-9_\-]{1,128}$/', $value) ? $value : '';
    }

    private function mergeAttachments(array $current, array $additional): array {
        $merged = [];
        $seen = [];
        foreach (array_merge($current, $additional) as $attachment) {
            if (is_array($attachment)) {
                $this->appendUniqueAttachment($merged, $seen, $attachment);
            }
        }
        return $merged;
    }

    private function appendUniqueAttachment(array &$attachments, array &$seen, array $attachment): void {
        $hash = trim((string)($attachment['payload_hash'] ?? ''));
        if ($hash === '') {
            $hash = substr(hash('sha256', json_encode($attachment, JSON_UNESCAPED_UNICODE)), 0, 16);
            $attachment['payload_hash'] = $hash;
        }
        if (isset($seen[$hash])) {
            return;
        }
        $seen[$hash] = true;
        $attachments[] = $attachment;
    }

    private function plainForwardLabel(string $value): string {
        $value = html_entity_decode(strip_tags($value), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $value = trim((string)preg_replace('/\s+/u', ' ', $value));
        if ($value === '') {
            return '';
        }
        return function_exists('mb_substr') ? mb_substr($value, 0, 120, 'UTF-8') : substr($value, 0, 120);
    }

    private function plainForwardType(string $value): string {
        return substr(strtolower(preg_replace('/[^a-z0-9_\-]/i', '', trim($value))), 0, 32);
    }

    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 extractTimestamp(array $data, array $paths): int {
        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)) {
                continue;
            }

            $value = trim((string)$cursor);
            if ($value === '') {
                continue;
            }
            if (preg_match('/^\d+$/', $value)) {
                $timestamp = (int)$value;
                if ($timestamp > 999999999999) {
                    $timestamp = (int)floor($timestamp / 1000);
                }
                if ($timestamp > 0) {
                    return $timestamp;
                }
            }

             
             
             
        }
        return 0;
    }

    private function extractDisplayName(array $payload, array $paths): string {
        foreach ($paths as $path) {
            $user = $this->extractArray($payload, $path);
            if (!$user) {
                continue;
            }

            $name = $this->extractString($user, [['name']]);
            if ($name !== '') {
                return $name;
            }

            $full_name = trim($this->extractString($user, [['first_name']]) . ' ' . $this->extractString($user, [['last_name']]));
            if ($full_name !== '') {
                return $full_name;
            }
        }

        return '';
    }

    private function extractAvatarUrl(array $payload): string {
        foreach ([
            ['message', 'sender'],
            ['sender'],
            ['user'],
            ['chat_event', 'user'],
        ] as $path) {
            $user = $this->extractArray($payload, $path);
            if (!$user) {
                continue;
            }

            $url = $this->extractString($user, [
                ['full_avatar_url'],
                ['avatar_url'],
                ['photo_url'],
                ['picture_url'],
                ['avatar'],
                ['photo'],
            ]);
            if ($url !== '') {
                return $url;
            }

            $nested_url = $this->extractStringFromNestedImage($user, ['avatar', 'photo', 'picture']);
            if ($nested_url !== '') {
                return $nested_url;
            }
        }

        return '';
    }

    private function extractStringFromNestedImage(array $data, array $keys): string {
        foreach ($keys as $key) {
            if (!isset($data[$key]) || !is_array($data[$key])) {
                continue;
            }
            $url = $this->extractString($data[$key], [
                ['full_avatar_url'],
                ['avatar_url'],
                ['url'],
                ['download_url'],
                ['image_url'],
            ]);
            if ($url !== '') {
                return $url;
            }
        }
        return '';
    }

    private function extractArray(array $data, array $path): array {
        $cursor = $data;
        foreach ($path as $part) {
            if (!is_array($cursor) || !array_key_exists($part, $cursor)) {
                return [];
            }
            $cursor = $cursor[$part];
        }
        return is_array($cursor) ? $cursor : [];
    }

    private function normalizeUserId(string $value): string {
        return preg_replace('/\D+/', '', $value);
    }

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