<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Workflow\Bpmn;

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

    /** @var array<string, BpmnModel> */
    private array $models = [];
    /** @var array<string, BpmnDesignerModel> */
    private array $designerModels = [];
    private array $imports = [];
    private array $exports = [];
    private BpmnRuntimeStore $store;

    private function __construct(private readonly string $basePath)
    {
        $this->store = new BpmnRuntimeStore($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 importXml(string $xml): BpmnModel
    {
        $model = (new BpmnParser())->parse($xml);
        $validation = (new BpmnValidator())->validate($model);
        if (!$validation['ok']) {
            throw new \RuntimeException('BPMN import validation failed: ' . json_encode($validation['issues']));
        }

        $this->models[$model->processId()] = $model;
        $this->designerModels[$model->processId()] = BpmnDesignerModel::fromBpmn($model);
        $this->imports[] = ['processId' => $model->processId(), 'elements' => count($model->elements()), 'flows' => count($model->flows()), 'createdAt' => date('c')];
        $this->save();

        return $model;
    }

    public function exportXml(string $processId): string
    {
        $model = $this->models[$processId] ?? null;
        if ($model === null) {
            return '';
        }

        $xml = (new BpmnExporter())->export($model);
        $this->exports[] = ['processId' => $processId, 'bytes' => strlen($xml), 'createdAt' => date('c')];
        $this->save();

        return $xml;
    }

    public function compile(string $processId): ?\PpPhp\Core\Platform\Workflow\Engine\WorkflowEngineDefinition
    {
        $model = $this->models[$processId] ?? null;
        return $model ? (new BpmnWorkflowCompiler())->compile($model) : null;
    }

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

        $this->models = [];
        foreach ($snapshot->models() as $model) {
            if (is_array($model)) {
                $m = BpmnModel::fromArray($model);
                $this->models[$m->processId()] = $m;
            }
        }

        $this->designerModels = [];
        foreach ($snapshot->designerModels() as $designer) {
            if (is_array($designer)) {
                $d = BpmnDesignerModel::fromArray($designer);
                $this->designerModels[(string) ($designer['processId'] ?? $d->designerId())] = $d;
            }
        }

        $this->imports = $snapshot->imports();
        $this->exports = $snapshot->exports();
    }

    public function snapshot(): BpmnRuntimeSnapshot
    {
        $models = array_map(static fn (BpmnModel $model): array => $model->toArray(), array_values($this->models));
        $designers = array_map(static fn (BpmnDesignerModel $designer): array => $designer->toArray(), array_values($this->designerModels));

        return new BpmnRuntimeSnapshot(
            $models,
            $designers,
            $this->imports,
            $this->exports,
            BpmnRuntimeStatistics::from($models, $designers, $this->imports, $this->exports),
            date('c')
        );
    }

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

    public function clear(): void
    {
        $this->models = [];
        $this->designerModels = [];
        $this->imports = [];
        $this->exports = [];
        $this->store->reset();
    }

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

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