<?php

declare(strict_types=1);

namespace PhpWA\Client;

use PhpWA\Protocol\Proto\Message;

/** Converts a protocol message into the stable application-facing shape. */
final class MessageProjector
{
    /** @var MessageUnwrapper */ private $unwrapper;

    public function __construct(?MessageUnwrapper $unwrapper = null)
    {
        $this->unwrapper = $unwrapper ?? new MessageUnwrapper();
    }

    /** @return array{kind:string,text:?string,media:?array,details:?array,wrappers:list<string>} */
    public function project(Message $source): array
    {
        $unwrapped = $this->unwrapper->unwrap($source);
        $message = $unwrapped['message'];

        return [
            'kind' => $this->kind($message),
            'text' => $this->text($message),
            'media' => $this->media($message),
            'details' => $this->details($message),
            'wrappers' => $unwrapped['wrappers'],
        ];
    }

    private function kind(Message $message): string
    {
        $map = [
            'hasConversation' => 'text', 'hasExtendedTextMessage' => 'text', 'hasImageMessage' => 'image',
            'hasVideoMessage' => 'video', 'hasAudioMessage' => 'audio', 'hasDocumentMessage' => 'document',
            'hasStickerMessage' => 'sticker', 'hasContactMessage' => 'contact', 'hasContactsArrayMessage' => 'contacts',
            'hasLocationMessage' => 'location', 'hasLiveLocationMessage' => 'live_location',
            'hasReactionMessage' => 'reaction', 'hasPollCreationMessage' => 'poll', 'hasProtocolMessage' => 'protocol',
        ];
        foreach ($map as $method => $kind) {
            if (method_exists($message, $method) && $message->{$method}()) {
                return $kind;
            }
        }
        return 'unknown';
    }

    private function text(Message $message): ?string
    {
        if ($message->hasConversation()) {
            return $message->getConversation();
        }
        if ($message->hasExtendedTextMessage() && $message->getExtendedTextMessage() !== null) {
            return $message->getExtendedTextMessage()->getText();
        }
        foreach (['getImageMessage', 'getVideoMessage', 'getDocumentMessage'] as $getter) {
            if (!method_exists($message, $getter)) {
                continue;
            }
            $media = $message->{$getter}();
            if (is_object($media) && method_exists($media, 'getCaption') && $media->getCaption() !== '') {
                return $media->getCaption();
            }
        }
        return null;
    }

    /** @return array<string,mixed>|null */
    private function media(Message $message): ?array
    {
        $media = null;
        $type = null;
        $map = [
            'hasImageMessage' => ['image', 'getImageMessage'], 'hasVideoMessage' => ['video', 'getVideoMessage'],
            'hasAudioMessage' => ['audio', 'getAudioMessage'], 'hasDocumentMessage' => ['document', 'getDocumentMessage'],
            'hasStickerMessage' => ['sticker', 'getStickerMessage'],
        ];
        foreach ($map as $has => $definition) {
            if (method_exists($message, $has) && $message->{$has}()) {
                $type = $definition[0];
                $media = $message->{$definition[1]}();
                break;
            }
        }
        if (!is_object($media) || !is_string($type)) {
            return null;
        }

        $value = static function ($object, string $method, $default = null) {
            return method_exists($object, $method) ? $object->{$method}() : $default;
        };
        $mediaKey = (string) $value($media, 'getMediaKey', '');
        $sha = (string) $value($media, 'getFileSha256', '');
        $encryptedSha = (string) $value($media, 'getFileEncSha256', '');
        $directPath = (string) $value($media, 'getDirectPath', '');
        $length = $value($media, 'getFileLength', 0);
        $result = [
            'type' => $type, 'mimetype' => (string) $value($media, 'getMimetype', ''),
            'url' => (string) $value($media, 'getUrl', ''), 'direct_path' => $directPath,
            'media_key_base64' => $mediaKey === '' ? null : base64_encode($mediaKey),
            'file_sha256_base64' => $sha === '' ? null : base64_encode($sha),
            'file_enc_sha256_base64' => $encryptedSha === '' ? null : base64_encode($encryptedSha),
            'file_length' => is_int($length) || is_string($length) ? (string) $length : '0',
            'caption' => (string) $value($media, 'getCaption', ''),
            'file_name' => (string) $value($media, 'getFileName', ''),
            'width' => (int) $value($media, 'getWidth', 0), 'height' => (int) $value($media, 'getHeight', 0),
            'seconds' => (int) $value($media, 'getSeconds', 0), 'voice_note' => (bool) $value($media, 'getPtt', false),
            'animated' => (bool) $value($media, 'getIsAnimated', false),
        ];
        $result['downloadable'] = $directPath !== '' && strlen($mediaKey) === 32 && strlen($sha) === 32
            && strlen($encryptedSha) === 32 && preg_match('/\A(?:0|[1-9][0-9]*)\z/D', $result['file_length']) === 1;
        return $result;
    }

    /** @return array<string,mixed>|null */
    private function details(Message $message): ?array
    {
        if ($message->hasContactMessage() && $message->getContactMessage() !== null) {
            return ['display_name' => $message->getContactMessage()->getDisplayName(), 'vcard' => $message->getContactMessage()->getVcard()];
        }
        if ($message->hasLocationMessage() && $message->getLocationMessage() !== null) {
            $location = $message->getLocationMessage();
            return [
                'latitude' => $location->getDegreesLatitude(), 'longitude' => $location->getDegreesLongitude(),
                'name' => $location->getName(), 'address' => $location->getAddress(), 'url' => $location->getUrl(),
            ];
        }
        if ($message->hasReactionMessage() && $message->getReactionMessage() !== null) {
            $reaction = $message->getReactionMessage();
            $key = $reaction->getKey();
            return [
                'text' => $reaction->getText(), 'target_id' => $key === null ? '' : $key->getId(),
                'target_chat_jid' => $key === null ? '' : $key->getRemoteJid(),
                'target_from_me' => $key !== null && $key->getFromMe(),
            ];
        }
        return null;
    }
}
