<?php

declare(strict_types=1);

namespace PhpWA\Media;

use InvalidArgumentException;
use PhpWA\Protocol\Proto\Message;
use PhpWA\Protocol\Proto\Message\AudioMessage;
use PhpWA\Protocol\Proto\Message\DocumentMessage;
use PhpWA\Protocol\Proto\Message\ImageMessage;
use PhpWA\Protocol\Proto\Message\StickerMessage;
use PhpWA\Protocol\Proto\Message\VideoMessage;

final class MediaMessageFactory
{
    /** @var callable */ private $clock;
    public function __construct(?callable $clock = null) { $this->clock = $clock ?? static function (): int { return time(); }; }

    public function image(MediaUploadResult $upload, string $mimetype, ?string $caption = null): Message
    {
        $this->type($upload, MediaType::IMAGE); $media = new ImageMessage(); $this->common($media, $upload, $mimetype);
        if ($caption !== null) { $media->setCaption($this->caption($caption)); }
        return (new Message())->setImageMessage($media);
    }
    public function video(MediaUploadResult $upload, string $mimetype, ?string $caption = null): Message
    {
        $this->type($upload, MediaType::VIDEO); $media = new VideoMessage(); $this->common($media, $upload, $mimetype);
        if ($caption !== null) { $media->setCaption($this->caption($caption)); }
        return (new Message())->setVideoMessage($media);
    }
    public function audio(MediaUploadResult $upload, string $mimetype, bool $voiceNote = false): Message
    {
        $this->type($upload, MediaType::AUDIO); $media = new AudioMessage(); $this->common($media, $upload, $mimetype);
        if ($voiceNote) { $media->setPtt(true); }
        return (new Message())->setAudioMessage($media);
    }
    public function document(MediaUploadResult $upload, string $mimetype, string $fileName, ?string $caption = null): Message
    {
        $this->type($upload, MediaType::DOCUMENT);
        if ($fileName === '' || strlen($fileName) > 255 || preg_match('//u', $fileName) !== 1
            || preg_match('/[\x00-\x1f\x7f]/', $fileName) === 1
            || strpos($fileName, '/') !== false || strpos($fileName, '\\') !== false) {
            throw new InvalidArgumentException('WhatsApp document filename is invalid.');
        }
        $media = new DocumentMessage(); $this->common($media, $upload, $mimetype); $media->setFileName($fileName);
        if ($caption !== null) { $media->setCaption($this->caption($caption)); }
        return (new Message())->setDocumentMessage($media);
    }
    public function sticker(MediaUploadResult $upload, string $mimetype = 'image/webp'): Message
    {
        $this->type($upload, MediaType::STICKER); $media = new StickerMessage(); $this->common($media, $upload, $mimetype);
        return (new Message())->setStickerMessage($media);
    }

    /** @param object $message */
    private function common($message, MediaUploadResult $upload, string $mimetype): void
    {
        if (preg_match('/\A[A-Za-z0-9!#$&^_.+\/-]{1,255}\z/D', $mimetype) !== 1) { throw new InvalidArgumentException('Media MIME type is invalid.'); }
        $encrypted = $upload->encrypted(); $now = ($this->clock)();
        if (!is_int($now) || $now < 0) { throw new InvalidArgumentException('Media message clock is invalid.'); }
        $message->setUrl($upload->url()); $message->setDirectPath($upload->directPath()); $message->setMimetype($mimetype);
        $message->setMediaKey($encrypted->mediaKey()); $message->setFileSha256($encrypted->fileSha256());
        $message->setFileEncSha256($encrypted->fileEncSha256()); $message->setFileLength($encrypted->fileLength());
        $message->setMediaKeyTimestamp($now);
    }
    private function caption(string $caption): string
    {
        if (strlen($caption) > 65536 || preg_match('//u', $caption) !== 1) { throw new InvalidArgumentException('Media caption is invalid.'); }
        return $caption;
    }
    private function type(MediaUploadResult $upload, string $expected): void
    {
        if ($upload->encrypted()->type() !== $expected) { throw new InvalidArgumentException('Uploaded media type does not match the message type.'); }
    }
}
