<?php

declare(strict_types=1);

final class MyronSiteSetupSelectionService
{
    /**
     * @var array<string, array<string, mixed>>|null
     */
    private ?array $templatesById = null;

    public function __construct(
        private readonly PDO $pdo,
        private readonly MyronSiteTypeRegistryRepository $siteTypes,
        private readonly MyronTemplateDefinitionRepository $templates,
        private readonly ?MyronEntitlementPresentationPolicy $entitlementPresentation = null,
    ) {
    }

    /**
     * @return array<string, mixed>|null
     */
    public function getSelectionManifest(): ?array
    {
        $statement = $this->pdo->query('SELECT selection_manifest_json FROM onboarding_state WHERE id = 1 LIMIT 1');
        $json = $statement ? $statement->fetchColumn() : false;
        if (!is_string($json) || trim($json) === '') {
            return null;
        }

        $decoded = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
        return is_array($decoded) ? $decoded : null;
    }

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    public function normalizeSelectionManifest(array $input): array
    {
        $primaryBundleId = trim((string) ($input['primary_bundle_id'] ?? ''));
        $primary = $this->siteTypes->findSiteTypeById($primaryBundleId);
        if (!is_array($primary)) {
            throw new InvalidArgumentException('Primary bundle `' . $primaryBundleId . '` does not exist.');
        }
        if ((string) ($primary['bundleType'] ?? '') !== 'primary') {
            throw new InvalidArgumentException('Primary bundle `' . $primaryBundleId . '` must be a primary site type.');
        }
        $this->entitlementPresentation?->assertSiteTypeAllowed($primary);

        $selectDefaultTemplates = ($input['select_default_templates'] ?? false) === true
            || (string) ($input['select_default_templates'] ?? '') === '1'
            || ($input['include_descendant_defaults'] ?? false) === true
            || (string) ($input['include_descendant_defaults'] ?? '') === '1';

        $includedBundleIds = $this->normalizeIds(is_array($input['included_bundle_ids'] ?? null) ? $input['included_bundle_ids'] : []);
        $includedBundleIds[$primaryBundleId] = true;
        $includedBundleIds = array_keys($includedBundleIds);
        sort($includedBundleIds, SORT_STRING);

        $includedById = array_fill_keys($includedBundleIds, true);
        $includedRecords = [];
        foreach ($includedBundleIds as $bundleId) {
            $record = $this->siteTypes->findSiteTypeById($bundleId);
            if (!is_array($record)) {
                throw new InvalidArgumentException('Bundle `' . $bundleId . '` does not exist.');
            }
            $this->entitlementPresentation?->assertSiteTypeAllowed($record);
            $includedRecords[$bundleId] = $record;
        }

        foreach ($includedRecords as $bundleId => $record) {
            if ((string) ($record['bundleType'] ?? '') !== 'subtype') {
                continue;
            }

            $parents = $this->parentBundleIds($record);
            if ($parents === [] || count(array_intersect_key(array_fill_keys($parents, true), $includedById)) === 0) {
                throw new InvalidArgumentException('Subtype bundle `' . $bundleId . '` requires active parent bundle `' . implode('` or `', $parents) . '`.');
            }
        }

        $cells = $this->templateCellsForRecords($includedRecords, $primaryBundleId);
        $cellsByKey = [];
        $cellsByTemplateId = [];
        foreach ($cells as $cell) {
            $key = (string) $cell['key'];
            $templateId = (string) $cell['templateId'];
            $cellsByKey[$key] = $cell;
            $cellsByTemplateId[$templateId][] = $cell;
        }

        $requestedByKey = [];
        foreach (is_array($input['selected_template_keys'] ?? null) ? $input['selected_template_keys'] : [] as $rawKey) {
            $key = $this->normalizeSelectionKey((string) $rawKey);
            if ($key !== '' && isset($cellsByKey[$key])) {
                $requestedByKey[$key] = true;
            }
        }
        foreach (array_keys($this->normalizeIds(is_array($input['selected_template_ids'] ?? null) ? $input['selected_template_ids'] : [])) as $templateId) {
            $template = $this->findTemplate($templateId);
            if (!is_array($template)) {
                throw new InvalidArgumentException('Template `' . $templateId . '` does not exist.');
            }
            $candidateCells = $cellsByTemplateId[$templateId] ?? [];
            if ($candidateCells === []) {
                continue;
            }
            $winner = $this->choosePreferredCell($candidateCells, $primaryBundleId, []);
            $requestedByKey[(string) $winner['key']] = true;
        }
        if ($selectDefaultTemplates) {
            foreach ($cells as $cell) {
                if (($cell['isDefault'] ?? false) === true) {
                    $requestedByKey[(string) $cell['key']] = true;
                }
            }
        }

        $selectedByKey = [];
        $suppressedByKey = [];
        $cellsByRole = [];
        foreach ($cells as $cell) {
            $cellsByRole[(string) $cell['roleKey']][] = $cell;
        }

        foreach ($cellsByRole as $roleKey => $roleCells) {
            $requestedRoleCells = array_values(array_filter(
                $roleCells,
                static fn (array $cell): bool => isset($requestedByKey[(string) $cell['key']])
            ));
            if (count($requestedRoleCells) > 1) {
                $winner = $this->choosePreferredCell($requestedRoleCells, $primaryBundleId, array_keys($requestedByKey));
                $winnerKey = (string) $winner['key'];
                foreach ($requestedRoleCells as $cell) {
                    $key = (string) $cell['key'];
                    if ($key === $winnerKey) {
                        continue;
                    }
                    $suppressedByKey[$key] = true;
                    unset($requestedByKey[$key]);
                }
            }
        }

        foreach (array_keys($requestedByKey) as $key) {
            $cell = $cellsByKey[$key] ?? null;
            if (!is_array($cell)) {
                continue;
            }
            $selectedByKey[$key] = true;
        }

        $selectedTemplateKeys = array_keys($selectedByKey);
        $suppressedTemplateKeys = array_values(array_diff(array_keys($suppressedByKey), $selectedTemplateKeys));
        sort($selectedTemplateKeys, SORT_STRING);
        sort($suppressedTemplateKeys, SORT_STRING);

        $selectedTemplateIds = [];
        foreach ($selectedTemplateKeys as $key) {
            $templateId = (string) ($cellsByKey[$key]['templateId'] ?? '');
            if ($templateId !== '') {
                $selectedTemplateIds[$templateId] = true;
            }
        }
        $suppressedTemplateIds = [];
        foreach ($suppressedTemplateKeys as $key) {
            $templateId = (string) ($cellsByKey[$key]['templateId'] ?? '');
            if ($templateId !== '') {
                $suppressedTemplateIds[$templateId] = true;
            }
        }
        $selectedTemplateIds = array_keys($selectedTemplateIds);
        $suppressedTemplateIds = array_values(array_diff(array_keys($suppressedTemplateIds), $selectedTemplateIds));
        sort($selectedTemplateIds, SORT_STRING);
        sort($suppressedTemplateIds, SORT_STRING);

        return [
            'schemaVersion' => 1,
            'primary_bundle_id' => $primaryBundleId,
            'included_bundle_ids' => $includedBundleIds,
            'selected_template_keys' => $selectedTemplateKeys,
            'selected_template_ids' => $selectedTemplateIds,
            'suppressed_template_keys' => $suppressedTemplateKeys,
            'suppressed_template_ids' => $suppressedTemplateIds,
            'updated_at' => myronNow(),
        ];
    }

    /**
     * @param list<string> $additionalBundleIds
     * @return array<string, mixed>
     */
    public function defaultSelectionManifestForPrimary(string $primaryBundleId, array $additionalBundleIds = []): array
    {
        return $this->normalizeSelectionManifest([
            'primary_bundle_id' => $primaryBundleId,
            'included_bundle_ids' => $additionalBundleIds,
            'selected_template_ids' => [],
            'select_default_templates' => true,
        ]);
    }

    /**
     * @return list<string>
     */
    public function descendantBundleIdsForPrimary(string $primaryBundleId): array
    {
        $primary = trim($primaryBundleId);
        if ($primary === '') {
            return [];
        }

        $descendants = [];
        foreach ($this->siteTypes->listSiteTypes() as $record) {
            $bundleId = trim((string) ($record['id'] ?? ''));
            if ($bundleId === '' || (string) ($record['bundleType'] ?? '') !== 'subtype') {
                continue;
            }
            if (in_array($primary, $this->parentBundleIds($record), true)) {
                $descendants[$bundleId] = true;
            }
        }

        $ids = array_keys($descendants);
        sort($ids, SORT_STRING);

        return $ids;
    }

    /**
     * @return list<string>
     */
    public function selectedTemplateIdsForBootstrap(string $primaryBundleId): array
    {
        $manifest = $this->getSelectionManifest();
        if (!is_array($manifest) || (string) ($manifest['primary_bundle_id'] ?? '') !== trim($primaryBundleId)) {
            return [];
        }

        return array_values(array_map('strval', is_array($manifest['selected_template_ids'] ?? null) ? $manifest['selected_template_ids'] : []));
    }

    /**
     * @param array<string, mixed> $template
     */
    public function isCoreTemplate(array $template): bool
    {
        $rawRoute = trim((string) ($template['route'] ?? ''));
        $route = $rawRoute !== '' ? myronNormalizePath($rawRoute) : '';
        $pageTitle = strtolower(trim((string) ($template['pageTitle'] ?? '')));
        $sourceTemplateId = (string) ($template['id'] ?? '');

        if (in_array($route, ['/', '/about', '/contact', '/search', '/legal', '/404'], true)) {
            return true;
        }

        foreach (['home', 'about', 'contact', 'search', 'legal', '404'] as $role) {
            if ($pageTitle === $role || str_contains($sourceTemplateId, 'template.' . $role . '.')) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param array<string, mixed> $template
     */
    public function roleKeyForTemplate(array $template): string
    {
        $id = (string) ($template['id'] ?? '');
        $parts = explode('.', $id);
        if (($parts[1] ?? '') === 'funnel') {
            return 'funnel-' . (string) ($parts[2] ?? myronNormalizeSlug((string) ($template['pageTitle'] ?? $id)));
        }

        return (string) ($parts[1] ?? myronNormalizeSlug((string) ($template['pageTitle'] ?? $id)));
    }

    public function templateSelectionKey(string $bundleId, string $templateId): string
    {
        return trim($bundleId) . '::' . trim($templateId);
    }

    public function normalizeSelectionKey(string $key): string
    {
        $normalized = trim($key);
        if ($normalized === '') {
            return '';
        }

        if (str_contains($normalized, '::')) {
            [$bundleId, $templateId] = array_pad(explode('::', $normalized, 2), 2, '');
            $bundleId = trim($bundleId);
            $templateId = trim($templateId);
            return $bundleId !== '' && $templateId !== '' ? $this->templateSelectionKey($bundleId, $templateId) : '';
        }

        return '';
    }

    public function isSingletonTemplate(array $template): bool
    {
        return $this->isSingletonRoleKey($this->roleKeyForTemplate($template));
    }

    /**
     * @param array<string, mixed> $template
     */
    public function isSuppressedDuplicateCoreTemplate(array $template, string $primaryBundleId, string $bundleId): bool
    {
        if ($bundleId === $primaryBundleId) {
            return false;
        }

        $record = $this->siteTypes->findSiteTypeById($bundleId);
        if (!is_array($record) || (string) ($record['bundleType'] ?? '') !== 'primary') {
            return false;
        }

        return $this->isCoreTemplate($template);
    }

    /**
     * @param array<string, mixed> $record
     * @return list<string>
     */
    public function templateIdsForBundle(array $record, bool $defaultsOnly = false): array
    {
        $templateIds = [];
        foreach (is_array($record['defaultTemplates'] ?? null) ? $record['defaultTemplates'] : [] as $templateId) {
            $normalized = trim((string) $templateId);
            if ($normalized !== '') {
                $templateIds[$normalized] = true;
            }
        }

        if (!$defaultsOnly) {
            foreach (is_array($record['optionalTemplates'] ?? null) ? $record['optionalTemplates'] : [] as $templateId) {
                $normalized = trim((string) $templateId);
                if ($normalized !== '') {
                    $templateIds[$normalized] = true;
                }
            }
        }

        return array_keys($templateIds);
    }

    /**
     * @return array<string, mixed>|null
     */
    public function findTemplate(string $templateId): ?array
    {
        $templates = $this->templatesById();
        return $templates[trim($templateId)] ?? null;
    }

    /**
     * @param array<string, mixed> $record
     * @return list<string>
     */
    public 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);
    }

    /**
     * @return array<string, array<string, mixed>>
     */
    private function templatesById(): array
    {
        if ($this->templatesById !== null) {
            return $this->templatesById;
        }

        $indexed = [];
        foreach ($this->templates->listTemplates() as $template) {
            $templateId = (string) ($template['id'] ?? '');
            if ($templateId !== '') {
                $indexed[$templateId] = $template;
            }
        }

        $this->templatesById = $indexed;
        return $indexed;
    }

    /**
     * @param list<mixed> $ids
     * @return array<string, true>
     */
    private function normalizeIds(array $ids): array
    {
        $normalized = [];
        foreach ($ids as $id) {
            $value = trim((string) $id);
            if ($value !== '') {
                $normalized[$value] = true;
            }
        }

        return $normalized;
    }

    private function isSingletonRoleKey(string $roleKey): bool
    {
        return in_array($roleKey, ['home', 'about', 'contact', 'search', 'legal', '404', 'team', 'coming-soon', 'location'], true);
    }

    /**
     * @param array<string, array<string, mixed>> $records
     * @return list<array<string, mixed>>
     */
    private function templateCellsForRecords(array $records, string $primaryBundleId): array
    {
        $cells = [];
        foreach ($records as $bundleId => $record) {
            $defaultIds = array_fill_keys($this->templateIdsForBundle($record, true), true);
            foreach ($this->templateIdsForBundle($record, false) as $templateId) {
                $template = $this->findTemplate($templateId);
                if (!is_array($template)) {
                    throw new InvalidArgumentException('Template `' . $templateId . '` does not exist.');
                }
                $cells[] = [
                    'key' => $this->templateSelectionKey((string) $bundleId, (string) $templateId),
                    'bundleId' => (string) $bundleId,
                    'templateId' => (string) $templateId,
                    'template' => $template,
                    'isDefault' => isset($defaultIds[(string) $templateId]),
                    'roleKey' => $this->roleKeyForTemplate($template),
                    'precedence' => $this->bundlePrecedence((string) $bundleId, $primaryBundleId),
                ];
            }
        }

        return $cells;
    }

    /**
     * @param list<array<string, mixed>> $cells
     * @param list<string> $preferredKeys
     * @return array<string, mixed>
     */
    private function choosePreferredCell(array $cells, string $primaryBundleId, array $preferredKeys): array
    {
        $preferred = array_fill_keys(array_map([$this, 'normalizeSelectionKey'], $preferredKeys), true);
        usort($cells, function (array $a, array $b) use ($primaryBundleId, $preferred): int {
            $aKey = (string) ($a['key'] ?? '');
            $bKey = (string) ($b['key'] ?? '');
            $aPreferred = isset($preferred[$aKey]) ? 0 : 1;
            $bPreferred = isset($preferred[$bKey]) ? 0 : 1;
            if ($aPreferred !== $bPreferred) {
                return $aPreferred <=> $bPreferred;
            }

            $aPrimary = (string) ($a['bundleId'] ?? '') === $primaryBundleId ? 0 : 1;
            $bPrimary = (string) ($b['bundleId'] ?? '') === $primaryBundleId ? 0 : 1;
            if ($aPrimary !== $bPrimary) {
                return $aPrimary <=> $bPrimary;
            }

            $precedence = ((int) ($a['precedence'] ?? 99)) <=> ((int) ($b['precedence'] ?? 99));
            if ($precedence !== 0) {
                return $precedence;
            }

            return strcmp((string) ($a['bundleId'] ?? ''), (string) ($b['bundleId'] ?? ''))
                ?: strcmp((string) ($a['templateId'] ?? ''), (string) ($b['templateId'] ?? ''));
        });

        return $cells[0];
    }

    private function bundlePrecedence(string $bundleId, string $primaryBundleId): int
    {
        if ($bundleId === $primaryBundleId) {
            return 0;
        }

        $record = $this->siteTypes->findSiteTypeById($bundleId);
        $type = is_array($record) ? (string) ($record['bundleType'] ?? '') : '';
        return match ($type) {
            'primary' => 10,
            'subtype' => 20,
            default => 30,
        };
    }
}
