<?php

declare(strict_types=1);

require_once __DIR__ . '/ComponentRegistryRepository.php';
require_once dirname(__DIR__, 3) . '/system/definitions/DefinitionsPath.php';
require_once dirname(__DIR__, 2) . '/identity/RoleScopeProjector.php';

final class MyronComponentRegistryIngestionService
{
    public function __construct(
        private readonly MyronDefinitionsPath $definitionsPath,
        private readonly MyronComponentRegistryRepository $components,
    ) {
    }

    /**
     * @return array{count: int, componentIds: list<string>}
     */
    public function seedFromDefinitions(): array
    {
        $records = [];
        $componentIds = [];

        foreach ([
            ['path' => $this->definitionsPath->componentsAtomsPath(), 'key' => 'atoms', 'classification' => 'atom'],
            ['path' => $this->definitionsPath->componentsPrimitivesPath(), 'key' => 'primitives', 'classification' => 'primitive'],
        ] as $source) {
            $payload = $this->decodeJsonFile($source['path'], 'component registry payload');
            $items = $payload[$source['key']] ?? null;
            if (!is_array($items)) {
                throw new RuntimeException('Expected `' . $source['key'] . '` list in `' . $source['path'] . '`.');
            }

            foreach ($items as $index => $record) {
                if (!is_array($record)) {
                    throw new RuntimeException('Component record `' . $source['key'] . '[' . $index . ']` must decode to an object.');
                }

                $componentId = trim((string) ($record['id'] ?? ''));
                if ($componentId === '') {
                    throw new RuntimeException('Component record `' . $source['key'] . '[' . $index . ']` is missing `id`.');
                }

                if (isset($componentIds[$componentId])) {
                    throw new RuntimeException('Duplicate component id `' . $componentId . '` detected in the definitions corpus.');
                }

                if (trim((string) ($record['classification'] ?? '')) !== $source['classification']) {
                    throw new RuntimeException(
                        'Component `' . $componentId . '` must declare classification `' . $source['classification'] . '` in `' . $source['path'] . '`.'
                    );
                }

                $componentIds[$componentId] = true;
                $records[] = [
                    'record' => $record,
                    'sourcePath' => $source['path'],
                ];
            }
        }

        foreach ($records as $entry) {
            $componentId = trim((string) ($entry['record']['id'] ?? ''));
            $dependencies = $entry['record']['composedOf'] ?? [];
            if (!is_array($dependencies)) {
                throw new RuntimeException('Component `' . $componentId . '` must expose `composedOf` as a list.');
            }

            foreach ($dependencies as $dependency) {
                $dependencyId = trim((string) $dependency);
                if ($dependencyId === '') {
                    throw new RuntimeException('Component `' . $componentId . '` contains an empty dependency reference.');
                }

                if (!isset($componentIds[$dependencyId])) {
                    throw new RuntimeException('Component `' . $componentId . '` references missing dependency `' . $dependencyId . '`.');
                }
            }
        }

        $savedIds = [];

        foreach ($records as $entry) {
            $record = $entry['record'];
            $roleScope = $record['roleScope'] ?? null;
            if ($roleScope !== null && !is_array($roleScope)) {
                throw new RuntimeException('Component `' . (string) ($record['id'] ?? '') . '` has an invalid roleScope payload.');
            }

            $this->components->saveComponent(
                $record,
                $this->definitionsPath->relativeToProject($entry['sourcePath']),
                $roleScope === null ? null : MyronRoleScopeProjector::projectRoleScope($roleScope)
            );
            $savedIds[] = (string) $record['id'];
        }

        $this->components->deleteMissingComponents($savedIds);

        return [
            'count' => count($savedIds),
            'componentIds' => $savedIds,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function decodeJsonFile(string $path, string $label): array
    {
        $raw = file_get_contents($path);
        if (!is_string($raw)) {
            throw new RuntimeException('Failed to read ' . $label . ' at `' . $path . '`.');
        }

        $decoded = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
        if (!is_array($decoded)) {
            throw new RuntimeException('Expected ' . $label . ' at `' . $path . '` to decode into an object.');
        }

        return $decoded;
    }
}
