<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Event\Sourcing\EventStore;

final class EventStream
{
    /** @var StoredEvent[] */
    private array $events = [];

    public static function fromArray(array $items): self
    {
        $stream = new self();
        foreach ($items as $item) {
            if (is_array($item)) {
                $event = StoredEvent::fromArray($item);
                $stream->events[$event->sequence()] = $event;
            }
        }
        ksort($stream->events);
        return $stream;
    }

    public function append(string $aggregateId, string $aggregateType, string $eventType, array $payload = [], array $metadata = []): StoredEvent
    {
        $version = $this->nextAggregateVersion($aggregateId);
        $event = StoredEvent::record(
            $this->nextSequence(),
            $aggregateId,
            $aggregateType,
            $version,
            $eventType,
            $payload,
            $metadata
        );

        $this->events[$event->sequence()] = $event;
        return $event;
    }

    public function byAggregate(string $aggregateId): array
    {
        return array_values(array_filter($this->events, static fn (StoredEvent $event): bool => $event->aggregateId() === $aggregateId));
    }

    public function byType(string $aggregateType): array
    {
        return array_values(array_filter($this->events, static fn (StoredEvent $event): bool => $event->aggregateType() === $aggregateType));
    }

    public function all(): array
    {
        return array_values($this->events);
    }

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

    public function nextSequence(): int
    {
        return $this->events === [] ? 1 : max(array_keys($this->events)) + 1;
    }

    public function nextAggregateVersion(string $aggregateId): int
    {
        $version = 0;
        foreach ($this->events as $event) {
            if ($event->aggregateId() === $aggregateId) {
                $version = max($version, $event->aggregateVersion());
            }
        }
        return $version + 1;
    }

    public function toArray(): array
    {
        return array_map(static fn (StoredEvent $event): array => $event->toArray(), $this->all());
    }
}
