<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Workflow;

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

    private WorkflowRegistry $registry;
    /** @var array<string, WorkflowInstance> */
    private array $instances = [];
    /** @var array<string, WorkflowTask> */
    private array $tasks = [];
    /** @var WorkflowExecutionResult[] */
    private array $executions = [];
    private WorkflowStore $store;

    private function __construct(private readonly string $basePath)
    {
        $this->registry = new WorkflowRegistry();
        $this->store = new WorkflowStore($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(WorkflowDefinition $definition): WorkflowDefinition
    {
        $this->registry->register($definition);
        $this->save();

        return $definition;
    }

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

        $instance = WorkflowInstance::start($definition, $context);
        $this->instances[$instance->instanceId()] = $instance;

        $activity = $definition->activity($instance->currentActivityId());
        if ($activity !== null) {
            $task = WorkflowTask::create($instance, $activity);
            $this->tasks[$task->taskId()] = $task;
        }

        $this->save();
        return $instance;
    }

    public function run(string $instanceId): int
    {
        $instance = $this->instances[$instanceId] ?? null;
        if ($instance === null || $instance->isCompleted() || $instance->status() === 'failed') {
            return 0;
        }

        $definition = $this->registry->get($instance->workflowId());
        if ($definition === null) {
            return 0;
        }

        $executed = 0;

        while (!$instance->isCompleted() && $instance->currentActivityId() !== '') {
            $activity = $definition->activity($instance->currentActivityId());
            if ($activity === null) {
                $this->executions[] = WorkflowExecutionResult::failed($instance, $instance->currentActivityId(), 'activity not found');
                $instance = $instance->fail('activity not found');
                $this->instances[$instance->instanceId()] = $instance;
                $this->save();
                return $executed;
            }

            if ($activity->type() === 'human') {
                $this->save();
                return $executed;
            }

            $this->executions[] = WorkflowExecutionResult::success($instance, $activity);
            $next = $definition->nextActivityId($activity->activityId());
            $instance = $instance->advance($activity->activityId(), $next);
            $this->instances[$instance->instanceId()] = $instance;
            $executed++;

            if (!$instance->isCompleted() && $instance->currentActivityId() !== '') {
                $nextActivity = $definition->activity($instance->currentActivityId());
                if ($nextActivity !== null) {
                    $task = WorkflowTask::create($instance, $nextActivity);
                    $this->tasks[$task->taskId()] = $task;
                }
            }
        }

        $this->save();
        return $executed;
    }

    public function completeTask(string $taskId): bool
    {
        $task = $this->tasks[$taskId] ?? null;
        if ($task === null) {
            return false;
        }

        $this->tasks[$taskId] = $task->complete();
        $instance = $this->instances[$task->instanceId()] ?? null;
        $definition = $instance ? $this->registry->get($instance->workflowId()) : null;

        if ($instance !== null && $definition !== null) {
            $activity = $definition->activity($task->activityId());
            if ($activity !== null) {
                $this->executions[] = WorkflowExecutionResult::success($instance, $activity);
                $next = $definition->nextActivityId($activity->activityId());
                $this->instances[$instance->instanceId()] = $instance->advance($activity->activityId(), $next);
            }
        }

        $this->save();
        return true;
    }

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

        if ($snapshot->definitions() !== []) {
            $this->registry = WorkflowRegistry::fromArray($snapshot->definitions());
        }

        $this->instances = [];
        foreach ($snapshot->instances() as $instance) {
            if (is_array($instance)) {
                $i = WorkflowInstance::fromArray($instance);
                $this->instances[$i->instanceId()] = $i;
            }
        }

        $this->tasks = [];
        foreach ($snapshot->tasks() as $task) {
            if (is_array($task)) {
                $t = WorkflowTask::fromArray($task);
                $this->tasks[$t->taskId()] = $t;
            }
        }

        $this->executions = [];
        foreach ($snapshot->executions() as $execution) {
            if (is_array($execution)) {
                $this->executions[] = WorkflowExecutionResult::fromArray($execution);
            }
        }
    }

    public function snapshot(): WorkflowRuntimeSnapshot
    {
        $definitions = $this->registry->toArray();
        $instances = array_map(static fn (WorkflowInstance $instance): array => $instance->toArray(), array_values($this->instances));
        $tasks = array_map(static fn (WorkflowTask $task): array => $task->toArray(), array_values($this->tasks));
        $executions = array_map(static fn (WorkflowExecutionResult $execution): array => $execution->toArray(), $this->executions);

        return new WorkflowRuntimeSnapshot(
            $definitions,
            $instances,
            $tasks,
            $executions,
            WorkflowStatistics::from($definitions, $instances, $tasks, $executions),
            date('c')
        );
    }

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

    public function clear(): void
    {
        $this->registry = new WorkflowRegistry();
        $this->instances = [];
        $this->tasks = [];
        $this->executions = [];
        $this->store->reset();
    }

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

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

        return [
            'definitions' => $snapshot->definitions(),
            'instances' => $snapshot->instances(),
            'tasks' => $snapshot->tasks(),
            'executions' => $snapshot->executions(),
            'statistics' => $snapshot->statistics(),
            'persisted' => $this->store->exists(),
            'stateFile' => $this->store->stateFile(),
        ];
    }

    private static function detectBasePath(): string
    {
        $candidate = dirname(__DIR__, 3);
        $real = realpath($candidate);
        return $real !== false ? $real : $candidate;
    }
}
