<?php

declare(strict_types=1);

namespace App\Framework\Mail;

use App\Domain\Common\ValueObject\Email;
use InvalidArgumentException;

final readonly class Message
{
    public function __construct(
        public Email $from,
        public string $subject,
        public string $body = '',
        public string $htmlBody = '',
        public Priority $priority = Priority::NORMAL,
        public EmailList $to = new EmailList(),
        public EmailList $cc = new EmailList(),
        public EmailList $bcc = new EmailList(),
        /** @var Attachment[] */
        public array $attachments = [],
        public array $headers = [],
        public ?Email $replyTo = null,
    ) {
        if (empty($body) && empty($htmlBody)) {
            throw new InvalidArgumentException('Either body or HTML body is required');
        }

        if ($to->isEmpty()) {
            throw new InvalidArgumentException('At least one recipient is required');
        }
    }

    public function hasHtmlBody(): bool
    {
        return ! empty($this->htmlBody);
    }

    public function hasAttachments(): bool
    {
        return ! empty($this->attachments);
    }

    public function hasReplyTo(): bool
    {
        return $this->replyTo !== null;
    }

    public function getAllRecipients(): EmailList
    {
        return $this->to->merge($this->cc)->merge($this->bcc)->unique();
    }

    public function withTo(Email|string ...$recipients): self
    {
        return new self(
            from: $this->from,
            subject: $this->subject,
            body: $this->body,
            htmlBody: $this->htmlBody,
            priority: $this->priority,
            to: new EmailList(...$recipients),
            cc: $this->cc,
            bcc: $this->bcc,
            attachments: $this->attachments,
            headers: $this->headers,
            replyTo: $this->replyTo,
        );
    }

    public function withCc(Email|string ...$recipients): self
    {
        return new self(
            from: $this->from,
            subject: $this->subject,
            body: $this->body,
            htmlBody: $this->htmlBody,
            priority: $this->priority,
            to: $this->to,
            cc: new EmailList(...$recipients),
            bcc: $this->bcc,
            attachments: $this->attachments,
            headers: $this->headers,
            replyTo: $this->replyTo,
        );
    }

    public function withBcc(Email|string ...$recipients): self
    {
        return new self(
            from: $this->from,
            subject: $this->subject,
            body: $this->body,
            htmlBody: $this->htmlBody,
            priority: $this->priority,
            to: $this->to,
            cc: $this->cc,
            bcc: new EmailList(...$recipients),
            attachments: $this->attachments,
            headers: $this->headers,
            replyTo: $this->replyTo,
        );
    }

    public function withAttachment(Attachment $attachment): self
    {
        return new self(
            from: $this->from,
            subject: $this->subject,
            body: $this->body,
            htmlBody: $this->htmlBody,
            priority: $this->priority,
            to: $this->to,
            cc: $this->cc,
            bcc: $this->bcc,
            attachments: [...$this->attachments, $attachment],
            headers: $this->headers,
            replyTo: $this->replyTo,
        );
    }

    public function withHeader(string $name, string $value): self
    {
        return new self(
            from: $this->from,
            subject: $this->subject,
            body: $this->body,
            htmlBody: $this->htmlBody,
            priority: $this->priority,
            to: $this->to,
            cc: $this->cc,
            bcc: $this->bcc,
            attachments: $this->attachments,
            headers: [...$this->headers, $name => $value],
            replyTo: $this->replyTo,
        );
    }

    public function withSubject(string $subject): self
    {
        return new self(
            from: $this->from,
            subject: $subject,
            body: $this->body,
            htmlBody: $this->htmlBody,
            priority: $this->priority,
            to: $this->to,
            cc: $this->cc,
            bcc: $this->bcc,
            attachments: $this->attachments,
            headers: $this->headers,
            replyTo: $this->replyTo,
        );
    }

    public function withReplyTo(Email|string $replyTo): self
    {
        return new self(
            from: $this->from,
            subject: $this->subject,
            body: $this->body,
            htmlBody: $this->htmlBody,
            priority: $this->priority,
            to: $this->to,
            cc: $this->cc,
            bcc: $this->bcc,
            attachments: $this->attachments,
            headers: $this->headers,
            replyTo: $replyTo instanceof Email ? $replyTo : new Email($replyTo),
        );
    }
}
