<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Workflow\Timer;

final class WorkflowTimerRuntime
{
    private static ?self $instance = null;
    /** @var array<string, WorkflowTimer> */
    private array $timers = [];
    /** @var WorkflowTimerResult[] */
    private array $results = [];
    private WorkflowTimerStore $store;

    private function __construct(private readonly string $basePath)
    {
        $this->store = new WorkflowTimerStore($basePath);
        $this->hydrate();
    }

    public static function instance(?string $basePath = null): self
    {
        if (self::$instance === null) {
            $basePath ??= self::detectBasePath();
            self::$instance = new self($basePath);
        }

        return self::$instance;
    }

    public static function resetInstance(?string $basePath = null): self
    {
        $basePath ??= self::detectBasePath();
        self::$instance = new self($basePath);

        return self::$instance;
    }

    public function schedule(WorkflowTimer $timer): WorkflowTimer
    {
        $this->timers[$timer->timerId()] = $timer;
        $this->save();

        return $timer;
    }

    public function fireDue(?int $now = null): int
    {
        $fired = 0;

        foreach ($this->timers as $timerId => $timer) {
            if (!$timer->isDue($now)) {
                continue;
            }

            $this->timers[$timerId] = $timer->fire();
            $this->results[] = WorkflowTimerResult::createFired($timer);
            $fired++;
        }

        $this->save();

        return $fired;
    }

    public function cancel(string $timerId): bool
    {
        $timer = $this->timers[$timerId] ?? null;
        if ($timer === null || $timer->status() !== 'scheduled') {
            return false;
        }

        $this->timers[$timerId] = $timer->cancel();
        $this->save();

        return true;
    }

    public function hydrate(): void
    {
        $snapshot = $this->store->load();

        $this->timers = [];
        foreach ($snapshot->timers() as $timer) {
            if (is_array($timer)) {
                $t = WorkflowTimer::fromArray($timer);
                $this->timers[$t->timerId()] = $t;
            }
        }

        $this->results = [];
        foreach ($snapshot->results() as $result) {
            if (is_array($result)) {
                $this->results[] = WorkflowTimerResult::fromArray($result);
            }
        }
    }

    public function snapshot(): WorkflowTimerSnapshot
    {
        $timers = array_map(static fn (WorkflowTimer $timer): array => $timer->toArray(), array_values($this->timers));
        $results = array_map(static fn (WorkflowTimerResult $result): array => $result->toArray(), $this->results);

        return new WorkflowTimerSnapshot($timers, $results, WorkflowTimerStatistics::from($timers, $results), date('c'));
    }

    public function save(): WorkflowTimerSnapshot
    {
        return $this->store->save($this->snapshot());
    }

    public function clear(): void
    {
        $this->timers = [];
        $this->results = [];
        $this->store->reset();
    }

    public function store(): WorkflowTimerStore
    {
        return $this->store;
    }

    public function summary(): array
    {
        $snapshot = $this->snapshot();

        return [
            'timers' => $snapshot->timers(),
            'results' => $snapshot->results(),
            'statistics' => $snapshot->statistics(),
            'persisted' => $this->store->exists(),
            'stateFile' => $this->store->stateFile(),
        ];
    }

    private static function detectBasePath(): string
    {
        $candidate = dirname(__DIR__, 4);
        $real = realpath($candidate);

        return $real !== false ? $real : $candidate;
    }
}
