<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Workflow\Engine;

final class WorkflowEngineRuntime
{
    private static ?self $instance = null;

    /** @var array<string, WorkflowEngineDefinition> */
    private array $definitions = [];
    /** @var array<string, WorkflowState> */
    private array $states = [];
    /** @var WorkflowEngineResult[] */
    private array $results = [];
    private WorkflowEngineStore $store;

    private function __construct(private readonly string $basePath)
    {
        $this->store = new WorkflowEngineStore($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 register(WorkflowEngineDefinition $definition): WorkflowEngineDefinition
    {
        $this->definitions[$definition->workflowId()] = $definition;
        $this->save();
        return $definition;
    }

    public function start(string $workflowId, array $context = []): ?WorkflowState
    {
        $definition = $this->definitions[$workflowId] ?? null;
        if ($definition === null || !$definition->active()) {
            return null;
        }

        $state = WorkflowState::start(
            'engine_inst_' . substr(hash('sha256', $workflowId . json_encode($context) . microtime(true)), 0, 24),
            [$definition->startActivityId()],
            $context
        );

        $this->states[$state->instanceId()] = $state;
        $this->save();

        return $state;
    }

    public function complete(string $instanceId, string $activityId): ?WorkflowState
    {
        $state = $this->states[$instanceId] ?? null;
        if ($state === null || $state->completed()) {
            return $state;
        }

        $definition = null;
        foreach ($this->definitions as $candidate) {
            if (in_array($activityId, array_column($candidate->activities(), 'activityId'), true) || in_array($activityId, array_column($candidate->gateways(), 'gatewayId'), true)) {
                $definition = $candidate;
                break;
            }
        }

        if ($definition === null) {
            $this->results[] = WorkflowEngineResult::failed($instanceId, $activityId, 'definition not found');
            return $state->fail('definition not found');
        }

        $next = $definition->next($activityId, (array) ($state->toArray()['context'] ?? []));

        foreach ($next as $target) {
            $gateway = $definition->gateway($target);
            if ($gateway === null) {
                continue;
            }

            if ($gateway->type() === WorkflowGateway::PARALLEL || $gateway->type() === WorkflowGateway::INCLUSIVE) {
                $next = $gateway->branches();
            } elseif ($gateway->type() === WorkflowGateway::EXCLUSIVE) {
                $next = [((string) ($gateway->branches()[0] ?? ''))];
            }
            break;
        }

        $newState = $state->completeActivity($activityId, array_values(array_filter($next)));
        $this->states[$instanceId] = $newState;
        $this->results[] = WorkflowEngineResult::success($instanceId, $activityId, $next);
        $this->save();

        return $newState;
    }

    public function run(string $instanceId): int
    {
        $executed = 0;
        $guard = 0;

        while (($this->states[$instanceId] ?? null) !== null && !$this->states[$instanceId]->completed() && $guard < 50) {
            $state = $this->states[$instanceId];
            $active = $state->activeActivities();
            if ($active === []) {
                break;
            }

            foreach ($active as $activityId) {
                $this->complete($instanceId, (string) $activityId);
                $executed++;
            }

            $guard++;
        }

        return $executed;
    }

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

        $this->definitions = [];
        foreach ($snapshot->definitions() as $definition) {
            if (is_array($definition)) {
                $d = WorkflowEngineDefinition::fromArray($definition);
                $this->definitions[$d->workflowId()] = $d;
            }
        }

        $this->states = [];
        foreach ($snapshot->states() as $state) {
            if (is_array($state)) {
                $s = WorkflowState::fromArray($state);
                $this->states[$s->instanceId()] = $s;
            }
        }

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

    public function snapshot(): WorkflowEngineSnapshot
    {
        $definitions = array_map(static fn (WorkflowEngineDefinition $definition): array => $definition->toArray(), array_values($this->definitions));
        $states = array_map(static fn (WorkflowState $state): array => $state->toArray(), array_values($this->states));
        $results = array_map(static fn (WorkflowEngineResult $result): array => $result->toArray(), $this->results);

        return new WorkflowEngineSnapshot($definitions, $states, $results, WorkflowEngineStatistics::from($definitions, $states, $results), date('c'));
    }

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

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

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

    public function summary(): array
    {
        $snapshot = $this->snapshot();
        return [
            'definitions' => $snapshot->definitions(),
            'states' => $snapshot->states(),
            '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;
    }
}
