<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Event\Scheduler;

use PpPhp\Core\Platform\Event\Contracts\EventInterface;
use PpPhp\Core\Platform\Event\Scheduler\Contracts\SchedulerInterface;
use PpPhp\Core\Platform\Event\Scheduler\Runtime\SchedulerRepository;

final class EventScheduler implements SchedulerInterface
{
    private SchedulerRuntime $runtime;
    private SchedulerClock $clock;

    public function __construct(?SchedulerRuntime $runtime = null, ?SchedulerClock $clock = null)
    {
        $this->clock = $clock ?? new SchedulerClock();
        $this->runtime = $runtime ?? new SchedulerRuntime(new SchedulerRepository(), clock: $this->clock);
    }

    public function dispatchLater(EventInterface|string $event, int $seconds, array $payload = [], array $metadata = []): string
    {
        return $this->schedule($event, ScheduleExpression::delaySeconds($seconds), $payload, $metadata);
    }

    public function dispatchAfterResponse(EventInterface|string $event, array $payload = [], array $metadata = []): string
    {
        return $this->schedule($event, ScheduleExpression::afterResponse(), $payload, $metadata + ['afterResponse' => true]);
    }

    public function dispatchAt(EventInterface|string $event, string $dateTime, array $payload = [], array $metadata = []): string
    {
        return $this->schedule($event, ScheduleExpression::at($dateTime), $payload, $metadata);
    }

    public function dispatchEvery(EventInterface|string $event, string $interval, array $payload = [], array $metadata = []): string
    {
        return $this->schedule($event, ScheduleExpression::every($interval), $payload, $metadata);
    }

    public function cancel(string $id): bool
    {
        $event = $this->runtime->repository()->get($id);
        if (!$event) { return false; }
        $this->runtime->repository()->replace($event->withState(ScheduleState::CANCELLED));
        return true;
    }

    public function due(?string $now = null): array { return $this->runtime->repository()->due($now); }
    public function run(?callable $handler = null): int { return $this->runtime->run($handler); }
    public function clear(): void { $this->runtime->repository()->clear(); }
    public function snapshot(): SchedulerSnapshot { return $this->runtime->repository()->snapshot(); }
    public function restore(SchedulerSnapshot $snapshot): void { $this->runtime->repository()->restore($snapshot); }
    public function statistics(): SchedulerStatistics { return $this->runtime->statistics(); }
    public function runtime(): SchedulerRuntime { return $this->runtime; }

    private function schedule(EventInterface|string $event, ScheduleExpression $expression, array $payload = [], array $metadata = []): string
    {
        $scheduled = ScheduledEvent::fromEvent($event, $expression, $this->clock, $payload, $metadata);
        $this->runtime->repository()->add($scheduled);
        return $scheduled->id();
    }
}
