<?php
namespace EasyChat\Library\Messenger;

use EasyChat\Library\MessageTextRenderer;
use PHPMax\Domain\Message as PHPMaxMessage;

class MaxEventNormalizer {

    public const SYSTEM_EVENT_MEMBER_JOINED = 'max_member_joined';
    public const SYSTEM_EVENT_MEMBER_LEFT = 'max_member_left';
    public const SYSTEM_EVENT_GROUP_CREATED = 'max_group_created';
    public const SYSTEM_EVENT_GROUP_UPDATED = 'max_group_updated';
    private const MAX_SYSTEM_EVENT_USER_IDS = 20;

    public static function fromMessage(PHPMaxMessage $message, string $account_id = '', array $context = []): array {
        $chat_id = isset($message->chatId) ? (string)$message->chatId : '';
        if ($chat_id === '') {
            $chat_id = (string)($context['chat_id'] ?? '');
        }
        $sender = isset($message->sender) ? (string)$message->sender : '';
        if ($sender === '') {
            $sender = (string)($context['sender_id'] ?? '');
        }
        $message_id = isset($message->id) ? (string)$message->id : '';
        $direction = ($account_id !== '' && $sender !== '' && hash_equals($account_id, $sender)) ? 'out' : 'in';
        $chat_type = strtoupper(trim((string)($context['chat_type'] ?? '')));
        $forward_reference = self::forwardReference($message);
        $forwarded_message = self::forwardedMessage($message);
        $forward_context = self::forwardContext($message);
        $text = self::textWithMaxLinkElements($message);
        if ($text === '' && $forwarded_message instanceof PHPMaxMessage) {
            $text = self::textWithMaxLinkElements($forwarded_message);
        }

        return self::fromArray([
            'update_type' => 'message_new',
            'text' => $text,
            'external_user_id' => $direction === 'out' ? ($chat_id !== '' ? $chat_id : $account_id) : ($sender !== '' ? $sender : $chat_id),
            'external_chat_id' => $chat_id !== '' ? $chat_id : $sender,
            'external_message_id' => $message_id,
            'created' => isset($message->time) ? (int)$message->time : 0,
            'direction' => $direction,
            'display_name' => $direction === 'out' ? 'MAX' : '',
            'attachments' => self::attachmentsFromMessage($message),
            'metadata' => [
                'sender_id' => $sender,
                'chat_id' => $chat_id,
                'chat_type' => $chat_type,
                'message_type' => (string)($message->type ?? ''),
                'max_link_elements_normalized' => self::countSafeLinkElements($message),
                'forward_source_present' => ($forward_reference || $forwarded_message) ? 1 : 0,
                'forwarded' => $forward_context ? 1 : 0,
                'forward_source_name' => (string)($forward_context['source_name'] ?? ''),
                'forward_source_type' => (string)($forward_context['source_type'] ?? ''),
            ],
        ]);
    }

    public static function fromArray(array $payload): array {
        $external_chat_id = self::plainId((string)($payload['external_chat_id'] ?? $payload['chat_id'] ?? ''));
        $external_user_id = self::plainId((string)($payload['external_user_id'] ?? $payload['sender_id'] ?? ''));
        if ($external_user_id === '') {
            $external_user_id = $external_chat_id;
        }

        return [
            'update_type' => (string)($payload['update_type'] ?? 'message_new'),
            'text' => (string)($payload['text'] ?? ''),
            'external_user_id' => $external_user_id,
            'external_chat_id' => $external_chat_id !== '' ? $external_chat_id : $external_user_id,
            'external_message_id' => (string)($payload['external_message_id'] ?? $payload['message_id'] ?? ''),
            'created' => (int)($payload['created'] ?? 0),
            'published_at' => (string)($payload['published_at'] ?? ''),
            'display_name' => (string)($payload['display_name'] ?? ''),
            'username' => (string)($payload['username'] ?? ''),
            'profile_url' => (string)($payload['profile_url'] ?? ''),
            'avatar_url' => (string)($payload['avatar_url'] ?? ''),
            'attachments' => is_array($payload['attachments'] ?? null) ? $payload['attachments'] : [],
            'direction' => (string)($payload['direction'] ?? 'in') === 'out' ? 'out' : 'in',
            'dedupe_key' => (string)($payload['dedupe_key'] ?? $payload['external_message_id'] ?? $payload['message_id'] ?? ''),
            'metadata' => is_array($payload['metadata'] ?? null) ? $payload['metadata'] : [],
        ];
    }

    






    public static function audioDurationSeconds(int $duration): int {
        if ($duration <= 0) {
            return 0;
        }
        return $duration > \EasyChat\Library\Attachment\AudioInspector::MAX_DURATION_SECONDS
            ? (int)ceil($duration / 1000)
            : $duration;
    }

    










    public static function controlSystemEvent(PHPMaxMessage $message): array {
        $events = [];
        $user_ids = [];
        $control_found = false;

        foreach ((array)($message->attaches ?? []) as $attach) {
            $type = strtoupper(trim((string)($attach->type ?? $attach->kind ?? '')));
            if ($type !== 'CONTROL') {
                continue;
            }

            $control_found = true;
            $extra = method_exists($attach, 'extra') ? $attach->extra() : [];
            $extra = is_array($extra) ? $extra : [];
            $event = strtolower(trim((string)($attach->event ?? $extra['event'] ?? '')));
            $event = preg_replace('/[^a-z0-9_\-]/', '', $event) ?: '';
            if ($event !== '') {
                $events[$event] = true;
            }

            $raw_user_ids = $extra['userIds'] ?? $extra['user_ids'] ?? [];
            if (isset($attach->userIds)) {
                $raw_user_ids = $attach->userIds;
            }
            if (is_object($raw_user_ids)) {
                $raw_user_ids = get_object_vars($raw_user_ids);
            }
            if (!is_array($raw_user_ids)) {
                $raw_user_ids = [$raw_user_ids];
            }

            foreach ($raw_user_ids as $raw_user_id) {
                $user_id = trim((string)$raw_user_id);
                if ($user_id === '' || !preg_match('/^\d+$/', $user_id)) {
                    continue;
                }
                $user_ids[$user_id] = $user_id;
                if (count($user_ids) >= self::MAX_SYSTEM_EVENT_USER_IDS) {
                    break 2;
                }
            }
        }

        if (!$control_found) {
            return [];
        }

        $event_name = implode('_', array_keys($events));
        if (preg_match('/(?:remove|removed|delete|deleted|kick|kicked|leave|left|ban|banned)/', $event_name)) {
            $type = self::SYSTEM_EVENT_MEMBER_LEFT;
        } elseif (preg_match('/(?:create|created|new_chat|chat_new)/', $event_name) && !$user_ids) {
            $type = self::SYSTEM_EVENT_GROUP_CREATED;
        } elseif ($user_ids || preg_match('/(?:add|added|join|joined|invite|invited|member)/', $event_name)) {
            $type = self::SYSTEM_EVENT_MEMBER_JOINED;
        } else {
            $type = self::SYSTEM_EVENT_GROUP_UPDATED;
        }

        return [
            'type' => $type,
            'user_ids' => array_values($user_ids),
            'user_count' => count($user_ids),
        ];
    }

    public static function isControlAttachment($attach): bool {
        return is_object($attach)
            && strtoupper(trim((string)($attach->type ?? $attach->kind ?? ''))) === 'CONTROL';
    }

    






    public static function forwardReference(PHPMaxMessage $message): array {
        $link = self::forwardLink($message);
        if (!$link) {
            return [];
        }

        $chat_id = self::plainId((string)($link['chatId'] ?? $link['chat_id'] ?? ''));
        $message_id = trim((string)($link['messageId'] ?? $link['message_id'] ?? ''));
        if ($message_id === '') {
            $source = $link['message'] ?? null;
            if (is_object($source)) {
                $source = get_object_vars($source);
            }
            if (is_array($source)) {
                $message_id = trim((string)($source['id'] ?? $source['messageId'] ?? $source['message_id'] ?? ''));
            }
        }
        if ($chat_id === '' || !preg_match('/^\d+$/', $message_id)) {
            return [];
        }

        return [
            'chat_id' => (int)$chat_id,
            'message_id' => (int)$message_id,
        ];
    }

    





    public static function forwardedMessage(PHPMaxMessage $message): ?PHPMaxMessage {
        $link = self::forwardLink($message);
        $source = $link['message'] ?? null;
        if ($source instanceof PHPMaxMessage) {
            return $source;
        }
        if (is_object($source)) {
            $source = get_object_vars($source);
        }
        if (!is_array($source)) {
            return null;
        }

        if (!isset($source['chatId']) && !isset($source['chat_id'])) {
            $source['chatId'] = $link['chatId'] ?? $link['chat_id'] ?? null;
        }
        if (!isset($source['id'])) {
            $source['id'] = $link['messageId'] ?? $link['message_id'] ?? null;
        }

        try {
            return new PHPMaxMessage($source);
        } catch (\Throwable $e) {
            return null;
        }
    }

    






    public static function forwardContext(PHPMaxMessage $message): array {
        $link = self::forwardLink($message);
        if (!$link) {
            return [];
        }

        return [
            'source_name' => self::plainLabel((string)($link['chatName'] ?? $link['chat_name'] ?? '')),
            'source_type' => self::plainType((string)($link['chatAccessType'] ?? $link['chat_access_type'] ?? '')),
        ];
    }

    private static function forwardLink(PHPMaxMessage $message): array {
        $extra = method_exists($message, 'extra') ? $message->extra() : [];
        $link = is_array($extra) ? ($extra['link'] ?? null) : null;
        if (is_object($link)) {
            $link = get_object_vars($link);
        }
        if (!is_array($link)) {
            return [];
        }

        return strtoupper(trim((string)($link['type'] ?? ''))) === 'FORWARD' ? $link : [];
    }

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

    private static function plainLabel(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 static function plainType(string $value): string {
        $value = strtoupper(preg_replace('/[^A-Z0-9_\-]/i', '', trim($value)));
        return substr($value, 0, 32);
    }

    private static function attachmentsFromMessage(PHPMaxMessage $message): array {
        $items = [];
        foreach ((array)($message->attaches ?? []) as $attach) {
            $type = strtolower((string)($attach->type ?? $attach->kind ?? ''));
            $file_id = (string)($attach->id ?? $attach->fileId ?? $attach->audioId ?? '');
            if ($file_id === '' && empty($attach->url) && empty($attach->baseUrl)) {
                continue;
            }
            $kind = ($type === 'photo' || $type === 'image') ? 'image' : ($type === 'audio' || $type === 'voice' ? 'audio' : 'document');
            $items[] = [
                'type' => $kind,
                'file_id' => $file_id,
                'file_name' => (string)($attach->name ?? $attach->filename ?? ''),
                'url' => (string)($attach->url ?? $attach->baseUrl ?? ''),
                'duration' => self::audioDurationSeconds((int)($attach->duration ?? 0)),
                'waveform' => (string)($attach->wave ?? ''),
                'payload_hash' => hash('sha256', $file_id . ':' . $type . ':' . (string)($attach->url ?? '')),
            ];
        }
        return $items;
    }

    private static function textWithMaxLinkElements(PHPMaxMessage $message): string {
        $text = (string)($message->text ?? '');
        if ($text === '') {
            return '';
        }

        $ranges = self::safeLinkRanges($message, $text);
        if (!$ranges) {
            return $text;
        }

        usort($ranges, static function (array $left, array $right): int {
            return $right['byte_start'] <=> $left['byte_start'];
        });

        foreach ($ranges as $range) {
            $label = substr($text, $range['byte_start'], $range['byte_end'] - $range['byte_start']);
            if ($label === '') {
                continue;
            }
            $text = substr($text, 0, $range['byte_start'])
                . '[' . self::escapeMarkdownLinkLabel($label) . '](' . $range['url'] . ')'
                . substr($text, $range['byte_end']);
        }

        return $text;
    }

    private static function countSafeLinkElements(PHPMaxMessage $message): int {
        return count(self::safeLinkRanges($message, (string)($message->text ?? '')));
    }

    private static function safeLinkRanges(PHPMaxMessage $message, string $text): array {
        $ranges = [];
        $occupied = [];

        foreach ((array)($message->elements ?? []) as $element) {
            $type = strtoupper((string)($element->type ?? ''));
            if ($type !== 'LINK') {
                continue;
            }

            $url = (string)($element->attributes->url ?? '');
            if (!MessageTextRenderer::isSafeHttpUrl($url)) {
                continue;
            }

            $from = (int)($element->from ?? -1);
            $length = (int)($element->length ?? 0);
            if ($from < 0 || $length <= 0) {
                continue;
            }

            $byte_start = self::utf16CodeUnitOffsetToByteOffset($text, $from);
            $byte_end = self::utf16CodeUnitOffsetToByteOffset($text, $from + $length);
            if ($byte_start === null || $byte_end === null || $byte_end <= $byte_start) {
                continue;
            }

            $overlaps = false;
            foreach ($occupied as $range) {
                if ($byte_start < $range['end'] && $byte_end > $range['start']) {
                    $overlaps = true;
                    break;
                }
            }
            if ($overlaps) {
                continue;
            }

            $ranges[] = [
                'byte_start' => $byte_start,
                'byte_end' => $byte_end,
                'url' => html_entity_decode(trim($url), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
            ];
            $occupied[] = ['start' => $byte_start, 'end' => $byte_end];
        }

        return $ranges;
    }

    private static function utf16CodeUnitOffsetToByteOffset(string $text, int $target): ?int {
        if ($target < 0) {
            return null;
        }
        if ($target === 0) {
            return 0;
        }

        if (preg_match_all('/./us', $text, $matches, PREG_OFFSET_CAPTURE) !== false) {
            $units = 0;
            foreach ($matches[0] as $match) {
                $char = (string)$match[0];
                $byte_offset = (int)$match[1];
                if ($units === $target) {
                    return $byte_offset;
                }
                $units += self::utf16CodeUnitLength($char);
                if ($units === $target) {
                    return $byte_offset + strlen($char);
                }
                if ($units > $target) {
                    return null;
                }
            }

            return $units === $target ? strlen($text) : null;
        }

        return $target <= strlen($text) ? $target : null;
    }

    private static function utf16CodeUnitLength(string $char): int {
        return self::unicodeCodePoint($char) > 0xFFFF ? 2 : 1;
    }

    private static function unicodeCodePoint(string $char): int {
        $bytes = array_map('ord', str_split($char));
        $count = count($bytes);
        if ($count === 1) {
            return $bytes[0];
        }
        if ($count === 2) {
            return (($bytes[0] & 0x1F) << 6) | ($bytes[1] & 0x3F);
        }
        if ($count === 3) {
            return (($bytes[0] & 0x0F) << 12) | (($bytes[1] & 0x3F) << 6) | ($bytes[2] & 0x3F);
        }
        if ($count === 4) {
            return (($bytes[0] & 0x07) << 18) | (($bytes[1] & 0x3F) << 12) | (($bytes[2] & 0x3F) << 6) | ($bytes[3] & 0x3F);
        }

        return 0;
    }

    private static function escapeMarkdownLinkLabel(string $label): string {
        return str_replace(['\\', '[', ']'], ['\\\\', '\\[', '\\]'], $label);
    }
}
