<?php

declare(strict_types=1);

namespace App\Framework\Mail;

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

final readonly class EmailList implements \Countable, \IteratorAggregate
{
    /**
     * @var Email[]
     */
    private array $emails;

    public function __construct(Email|string ...$emails)
    {
        $this->emails = array_values(array_map(
            fn ($email) => $email instanceof Email ? $email : new Email($email),
            $emails
        ));
    }

    public static function empty(): self
    {
        return new self();
    }

    public static function fromArray(array $emails): self
    {
        return new self(...$emails);
    }

    public function add(Email|string ...$emails): self
    {
        return new self(...$this->emails, ...$emails);
    }

    public function merge(self $other): self
    {
        return new self(...$this->emails, ...$other->emails);
    }

    public function unique(): self
    {
        $unique = [];
        foreach ($this->emails as $email) {
            $key = $email->value;
            if (! isset($unique[$key])) {
                $unique[$key] = $email;
            }
        }

        return new self(...array_values($unique));
    }

    public function filter(callable $callback): self
    {
        return new self(...array_filter($this->emails, $callback));
    }

    public function map(callable $callback): array
    {
        return array_map($callback, $this->emails);
    }

    public function contains(Email|string $email): bool
    {
        $searchEmail = $email instanceof Email ? $email : new Email($email);

        foreach ($this->emails as $existing) {
            if ($existing->equals($searchEmail)) {
                return true;
            }
        }

        return false;
    }

    public function isEmpty(): bool
    {
        return empty($this->emails);
    }

    public function isNotEmpty(): bool
    {
        return ! $this->isEmpty();
    }

    public function first(): ?Email
    {
        return $this->emails[0] ?? null;
    }

    public function last(): ?Email
    {
        $count = count($this->emails);

        return $count > 0 ? $this->emails[$count - 1] : null;
    }

    public function toArray(): array
    {
        return $this->emails;
    }

    public function toStringArray(): array
    {
        return array_map(fn (Email $email) => $email->value, $this->emails);
    }

    public function toString(string $separator = ', '): string
    {
        return implode($separator, $this->toStringArray());
    }

    public function count(): int
    {
        return count($this->emails);
    }

    public function getIterator(): ArrayIterator
    {
        return new ArrayIterator($this->emails);
    }
}
