<?php

declare(strict_types=1);

require_once __DIR__ . '/SiteTypeRegistryRepository.php';
require_once dirname(__DIR__, 3) . '/system/definitions/DefinitionsPath.php';

final class MyronSiteTypeIngestionService
{
    public function __construct(
        private readonly MyronDefinitionsPath $definitionsPath,
        private readonly MyronSiteTypeRegistryRepository $siteTypes,
    ) {
    }

    /**
     * @return array{count: int, bundleIds: list<string>}
     */
    public function seedFromDefinitions(): array
    {
        $templateIds = array_fill_keys($this->loadManifestIds($this->definitionsPath->templatePaths(), 'template definition'), true);
        $records = [];
        $bundleIds = [];

        foreach ($this->definitionsPath->siteTypePaths() as $path) {
            $record = $this->decodeJsonFile($path, 'site-type definition');
            $bundleId = trim((string) ($record['id'] ?? ''));
            if ($bundleId === '') {
                throw new RuntimeException('Site-type definition `' . $path . '` is missing `id`.');
            }

            if (isset($bundleIds[$bundleId])) {
                throw new RuntimeException('Duplicate site-type bundle id `' . $bundleId . '` detected in the definitions corpus.');
            }

            $bundleIds[$bundleId] = true;
            $records[] = [
                'record' => $record,
                'sourcePath' => $path,
            ];
        }

        $savedIds = [];

        foreach ($records as $entry) {
            $record = $entry['record'];
            $bundleId = trim((string) ($record['id'] ?? ''));
            $bundleType = trim((string) ($record['bundleType'] ?? ''));

            if (!in_array($bundleType, ['primary', 'subtype'], true)) {
                throw new RuntimeException('Site-type `' . $bundleId . '` has an unsupported bundleType.');
            }

            foreach (['defaultTemplates', 'optionalTemplates'] as $field) {
                $templateRefs = $record[$field] ?? null;
                if (!is_array($templateRefs)) {
                    throw new RuntimeException('Site-type `' . $bundleId . '` must expose `' . $field . '` as a list.');
                }

                foreach ($templateRefs as $templateId) {
                    $normalizedTemplateId = trim((string) $templateId);
                    if ($normalizedTemplateId === '') {
                        throw new RuntimeException('Site-type `' . $bundleId . '` contains an empty template reference in `' . $field . '`.');
                    }

                    if (!isset($templateIds[$normalizedTemplateId])) {
                        throw new RuntimeException('Site-type `' . $bundleId . '` references unknown template `' . $normalizedTemplateId . '`.');
                    }
                }
            }

            if ($bundleType === 'subtype') {
                $parentBundles = $this->parentBundleIds($record);
                if ($parentBundles === []) {
                    throw new RuntimeException('Subtype bundle `' . $bundleId . '` must declare `parentBundle`.');
                }

                foreach ($parentBundles as $parentBundle) {
                    if (!isset($bundleIds[$parentBundle])) {
                        throw new RuntimeException('Subtype bundle `' . $bundleId . '` references unknown parent bundle `' . $parentBundle . '`.');
                    }
                }
            }

            $this->siteTypes->saveSiteType($record, $this->definitionsPath->relativeToProject($entry['sourcePath']));
            $savedIds[] = $bundleId;
        }

        $this->siteTypes->deleteMissingSiteTypes($savedIds);

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

    /**
     * @param array<string, mixed> $record
     * @return list<string>
     */
    private function parentBundleIds(array $record): array
    {
        $parents = [];
        foreach (is_array($record['parentBundles'] ?? null) ? $record['parentBundles'] : [] as $parentBundle) {
            $normalized = trim((string) $parentBundle);
            if ($normalized !== '') {
                $parents[$normalized] = true;
            }
        }

        $legacyParent = $record['parentBundle'] ?? null;
        if (is_array($legacyParent)) {
            foreach ($legacyParent as $parentBundle) {
                $normalized = trim((string) $parentBundle);
                if ($normalized !== '') {
                    $parents[$normalized] = true;
                }
            }
        } else {
            $normalized = trim((string) $legacyParent);
            if ($normalized !== '') {
                $parents[$normalized] = true;
            }
        }

        return array_keys($parents);
    }

    /**
     * @param list<string> $paths
     * @return list<string>
     */
    private function loadManifestIds(array $paths, string $label): array
    {
        $ids = [];

        foreach ($paths as $path) {
            $payload = $this->decodeJsonFile($path, $label);
            $id = trim((string) ($payload['id'] ?? ''));
            if ($id === '') {
                throw new RuntimeException('Definition `' . $path . '` is missing `id`.');
            }

            $ids[] = $id;
        }

        return $ids;
    }

    /**
     * @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;
    }
}
