<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Workflow\HumanTask;

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

    /** @var array<string, HumanTaskRecord> */
    private array $tasks = [];
    /** @var HumanTaskAssignment[] */
    private array $assignments = [];
    private HumanTaskStore $store;

    private function __construct(private readonly string $basePath)
    {
        $this->store = new HumanTaskStore($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 createTask(HumanTaskRecord $task): HumanTaskRecord
    {
        $this->tasks[$task->taskId()] = $task;
        $this->save();
        return $task;
    }

    public function assign(string $taskId, string $assignee): bool
    {
        $task = $this->tasks[$taskId] ?? null;
        if ($task === null || $task->isCompleted()) {
            $this->assignments[] = HumanTaskAssignment::create($taskId, $assignee, 'assign', false, 'task unavailable');
            $this->save();
            return false;
        }

        $this->tasks[$taskId] = $task->assign($assignee);
        $this->assignments[] = HumanTaskAssignment::create($taskId, $assignee, 'assign', true, 'task assigned');
        $this->save();
        return true;
    }

    public function claim(string $taskId, string $userId, array $groups = []): bool
    {
        $task = $this->tasks[$taskId] ?? null;
        if ($task === null || !$task->visibleTo($userId, $groups) || $task->isCompleted()) {
            $this->assignments[] = HumanTaskAssignment::create($taskId, $userId, 'claim', false, 'task not claimable');
            $this->save();
            return false;
        }

        $this->tasks[$taskId] = $task->claim($userId);
        $this->assignments[] = HumanTaskAssignment::create($taskId, $userId, 'claim', true, 'task claimed');
        $this->save();
        return true;
    }

    public function complete(string $taskId, array $payload = []): bool
    {
        $task = $this->tasks[$taskId] ?? null;
        if ($task === null || $task->isCompleted()) {
            $this->save();
            return false;
        }

        $this->tasks[$taskId] = $task->complete($payload);
        $this->save();
        return true;
    }

    public function inbox(string $userId, array $groups = []): array
    {
        return array_map(static fn (HumanTaskRecord $task): array => $task->toArray(), (new HumanTaskInbox(array_values($this->tasks)))->forUser($userId, $groups));
    }

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

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

        $this->assignments = [];
        foreach ($snapshot->assignments() as $assignment) {
            if (is_array($assignment)) {
                $this->assignments[] = HumanTaskAssignment::fromArray($assignment);
            }
        }
    }

    public function snapshot(): HumanTaskSnapshot
    {
        $tasks = array_map(static fn (HumanTaskRecord $task): array => $task->toArray(), array_values($this->tasks));
        $assignments = array_map(static fn (HumanTaskAssignment $assignment): array => $assignment->toArray(), $this->assignments);

        return new HumanTaskSnapshot($tasks, $assignments, HumanTaskStatistics::from($tasks, $assignments), date('c'));
    }

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

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

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

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

        return [
            'tasks' => $snapshot->tasks(),
            'assignments' => $snapshot->assignments(),
            '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;
    }
}
