<?php

declare(strict_types=1);

final class MyronTemplateInstantiationService
{
    public function __construct(
        private readonly PDO $pdo,
        private readonly MyronContentRepository $content,
    ) {
    }

    /**
     * @param array<string, mixed> $definition
     * @return array<string, mixed>
     */
    public function instantiateTemplate(array $definition, array $options = []): array
    {
        $syncExistingTemplate = ($options['syncExistingTemplate'] ?? true) !== false;
        $sourceTemplateId = trim((string) ($definition['id'] ?? ''));
        $sourceBundleId = trim((string) ($definition['bundleId'] ?? ''));
        if ($sourceTemplateId === '' || $sourceBundleId === '') {
            throw new InvalidArgumentException('Template definitions require source template and bundle ids.');
        }

        $existing = $this->findRuntimeTemplateBySource($sourceTemplateId);
        $templateKey = $this->templateKey($sourceTemplateId);
        $name = trim((string) ($definition['name'] ?? $sourceTemplateId)) ?: $sourceTemplateId;
        $description = trim((string) ($definition['description'] ?? 'Instantiated from bundle template definition.'));
        $chrome = MyronTemplateDefinitionRepository::chromeForTemplateRecord($definition);
        $headerCapabilities = MyronTemplateDefinitionRepository::headerCapabilitiesForTemplateRecord($definition, $chrome);
        $chromeJson = json_encode($chrome, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
        $headerCapabilitiesJson = json_encode($headerCapabilities, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
        $now = myronNow();

        if (is_array($existing) && !$syncExistingTemplate) {
            return $existing;
        }

        if (is_array($existing)) {
            $templateId = (string) $existing['id'];
            $versionId = (string) $existing['current_version_id'];

            // STN-012-004: Preserve Designer-modified defaultContent on re-bootstrap.
            // If the existing runtime template's schema already has non-empty defaultContent
            // (a Designer has customized the Page Template's seed content via the
            // Site Type Studio Page Templates Design view), keep that customization.
            // Otherwise the seed defaultContent from the bundle definition flows through.
            $preservedDefaultContent = $this->fetchExistingDefaultContent($versionId);
            $schemaJson = json_encode(
                $this->schemaForDefinition($definition, $preservedDefaultContent),
                JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES
            );

            $updateTemplate = $this->pdo->prepare(
                'UPDATE page_templates
                 SET template_key = :template_key,
                     name = :name,
                     description = :description,
                     source_bundle_id = :source_bundle_id,
                     chrome_json = :chrome_json,
                     header_capabilities_json = :header_capabilities_json,
                     updated_at = :updated_at
                 WHERE id = :id'
            );
            $updateTemplate->execute([
                ':template_key' => $templateKey,
                ':name' => $name,
                ':description' => $description,
                ':source_bundle_id' => $sourceBundleId,
                ':chrome_json' => $chromeJson,
                ':header_capabilities_json' => $headerCapabilitiesJson,
                ':updated_at' => $now,
                ':id' => $templateId,
            ]);

            if ($versionId !== '') {
                $updateVersion = $this->pdo->prepare(
                    'UPDATE page_template_versions
                     SET view_name = :view_name,
                         schema_json = :schema_json
                     WHERE id = :id'
                );
                $updateVersion->execute([
                    ':view_name' => 'basic-page',
                    ':schema_json' => $schemaJson,
                    ':id' => $versionId,
                ]);
            }

            return $this->findRuntimeTemplateBySource($sourceTemplateId) ?? $existing;
        }

        // First-time instantiation: seed defaultContent verbatim from the bundle definition.
        $schemaJson = json_encode($this->schemaForDefinition($definition), JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);

        $templateId = myronGenerateId('tpl');
        $versionId = myronGenerateId('tplver');
        $this->pdo->beginTransaction();

        try {
            $insertTemplate = $this->pdo->prepare(
                'INSERT INTO page_templates (
                    id,
                    template_key,
                    name,
                    description,
                    source_template_id,
                    source_bundle_id,
                    chrome_json,
                    header_capabilities_json,
                    current_version_id,
                    created_at,
                    updated_at
                 ) VALUES (
                    :id,
                    :template_key,
                    :name,
                    :description,
                    :source_template_id,
                    :source_bundle_id,
                    :chrome_json,
                    :header_capabilities_json,
                    :current_version_id,
                    :created_at,
                    :updated_at
                 )'
            );
            $insertTemplate->execute([
                ':id' => $templateId,
                ':template_key' => $templateKey,
                ':name' => $name,
                ':description' => $description,
                ':source_template_id' => $sourceTemplateId,
                ':source_bundle_id' => $sourceBundleId,
                ':chrome_json' => $chromeJson,
                ':header_capabilities_json' => $headerCapabilitiesJson,
                ':current_version_id' => $versionId,
                ':created_at' => $now,
                ':updated_at' => $now,
            ]);

            $insertVersion = $this->pdo->prepare(
                'INSERT INTO page_template_versions (
                    id,
                    template_id,
                    version_number,
                    view_name,
                    schema_json,
                    created_at
                 ) VALUES (
                    :id,
                    :template_id,
                    :version_number,
                    :view_name,
                    :schema_json,
                    :created_at
                 )'
            );
            $insertVersion->execute([
                ':id' => $versionId,
                ':template_id' => $templateId,
                ':version_number' => 1,
                ':view_name' => 'basic-page',
                ':schema_json' => $schemaJson,
                ':created_at' => $now,
            ]);

            $this->pdo->commit();
        } catch (Throwable $exception) {
            $this->pdo->rollBack();
            throw $exception;
        }

        $saved = $this->findRuntimeTemplateBySource($sourceTemplateId);
        if (!is_array($saved)) {
            throw new RuntimeException('Failed to instantiate runtime template for `' . $sourceTemplateId . '`.');
        }

        return $saved;
    }

    /**
     * @param array<string, mixed> $definition
     * @return array<string, mixed>
     */
    public function instantiatePage(array $definition, array $runtimeTemplate, array $options = []): ?array
    {
        $result = $this->instantiatePageWithResult($definition, $runtimeTemplate, $options);
        $page = $result['page'] ?? null;

        return is_array($page) ? $page : null;
    }

    /**
     * @param array<string, mixed> $definition
     * @param array<string, mixed> $runtimeTemplate
     * @param array<string, mixed> $options
     * @return array{status:string,page:array<string,mixed>|null,sourceTemplateId:string,path:string,reason:string}
     */
    public function instantiatePageWithResult(array $definition, array $runtimeTemplate, array $options = []): array
    {
        $options = $this->normalizePageInstantiationOptions($options);
        $sourceTemplateId = trim((string) ($definition['id'] ?? ''));
        $sourceBundleId = trim((string) ($definition['bundleId'] ?? ''));
        $result = [
            'status' => 'skipped',
            'page' => null,
            'sourceTemplateId' => $sourceTemplateId,
            'path' => '',
            'reason' => '',
        ];
        if ($sourceTemplateId === '' || $sourceBundleId === '') {
            $result['reason'] = 'missing_source_identity';
            return $result;
        }
        if (($definition['autoInstantiatePage'] ?? true) === false) {
            $result['reason'] = 'auto_instantiation_disabled';
            return $result;
        }

        $identity = $this->pageIdentityForTemplate($definition);
        $result['path'] = $identity['path'];
        $existing = $this->findRuntimePageBySource($sourceTemplateId);
        if (is_array($existing)) {
            if (!$options['syncExistingPageIdentity']) {
                return array_merge($result, [
                    'status' => 'existing_preserved',
                    'page' => $existing,
                    'reason' => 'existing_source_linked_page_preserved',
                ]);
            }

            $this->syncRuntimePageIdentity($existing, $definition, $runtimeTemplate, $identity);
            return array_merge($result, [
                'status' => 'synced',
                'page' => $this->findRuntimePageBySource($sourceTemplateId) ?? $existing,
                'reason' => '',
            ]);
        }

        $matchingPage = $this->findRuntimePageByPath($identity['path']);
        if (is_array($matchingPage)) {
            $matchingSourceTemplateId = trim((string) ($matchingPage['source_template_id'] ?? ''));
            if ($matchingSourceTemplateId === '' || $matchingSourceTemplateId === $sourceTemplateId) {
                if (!$options['claimPathMatches'] || !$options['syncExistingPageIdentity']) {
                    return array_merge($result, [
                        'status' => 'skipped',
                        'page' => null,
                        'reason' => $matchingSourceTemplateId === '' ? 'manual_route_collision' : 'existing_source_route_preserved',
                    ]);
                }

                $this->syncRuntimePageIdentity($matchingPage, $definition, $runtimeTemplate, $identity);

                return array_merge($result, [
                    'status' => 'synced',
                    'page' => $this->findRuntimePageBySource($sourceTemplateId),
                    'reason' => '',
                ]);
            }

            if ($options['collisionPolicy'] === 'skip') {
                return array_merge($result, [
                    'status' => 'skipped',
                    'page' => null,
                    'reason' => 'route_collision',
                ]);
            }

            $identity = $this->collisionSafeIdentity($identity, $sourceTemplateId, $sourceBundleId);
            $result['path'] = $identity['path'];
        }

        $parentPageId = null;
        if ($identity['parentPath'] !== '') {
            $parentPage = $this->findRuntimePageByPath($identity['parentPath']);
            $parentPageId = is_array($parentPage) ? (string) ($parentPage['id'] ?? '') : null;
        }

        $pageId = $this->content->createPage(
            (string) $runtimeTemplate['id'],
            $identity['title'],
            $identity['slug'],
            $parentPageId,
            $identity['seoTitle'],
            $identity['metaDescription'],
            $this->pageContentForTemplate($definition, $identity),
            true
        );

        $update = $this->pdo->prepare(
            'UPDATE pages
             SET source_template_id = :source_template_id,
                 source_bundle_id = :source_bundle_id
             WHERE id = :id'
        );
        $update->execute([
            ':source_template_id' => $sourceTemplateId,
            ':source_bundle_id' => $sourceBundleId,
            ':id' => $pageId,
        ]);

        return array_merge($result, [
            'status' => 'created',
            'page' => $this->findRuntimePageBySource($sourceTemplateId),
            'reason' => '',
        ]);
    }

    /**
     * @param array<string, mixed> $options
     * @return array{syncExistingPageIdentity:bool,claimPathMatches:bool,collisionPolicy:string}
     */
    private function normalizePageInstantiationOptions(array $options): array
    {
        $collisionPolicy = trim((string) ($options['collisionPolicy'] ?? 'rename'));
        if (!in_array($collisionPolicy, ['rename', 'skip'], true)) {
            $collisionPolicy = 'rename';
        }

        return [
            'syncExistingPageIdentity' => ($options['syncExistingPageIdentity'] ?? true) !== false,
            'claimPathMatches' => ($options['claimPathMatches'] ?? true) !== false,
            'collisionPolicy' => $collisionPolicy,
        ];
    }

    /**
     * @return array<string, mixed>|null
     */
    public function findRuntimeTemplateBySource(string $sourceTemplateId): ?array
    {
        $statement = $this->pdo->prepare(
            'SELECT *
             FROM page_templates
             WHERE source_template_id = :source_template_id
             LIMIT 1'
        );
        $statement->execute([':source_template_id' => trim($sourceTemplateId)]);
        $row = $statement->fetch();

        return is_array($row) ? $row : null;
    }

    /**
     * @return array<string, mixed>|null
     */
    private function findRuntimePageByPath(string $path): ?array
    {
        $statement = $this->pdo->prepare(
            'SELECT *
             FROM pages
             WHERE path = :path
             LIMIT 1'
        );
        $statement->execute([':path' => myronNormalizePath($path)]);
        $row = $statement->fetch();

        return is_array($row) ? $row : null;
    }

    /**
     * @return array<string, mixed>|null
     */
    private function findRuntimePageBySlug(string $slug): ?array
    {
        $statement = $this->pdo->prepare(
            'SELECT *
             FROM pages
             WHERE slug = :slug
             LIMIT 1'
        );
        $statement->execute([':slug' => myronNormalizeSlug($slug)]);
        $row = $statement->fetch();

        return is_array($row) ? $row : null;
    }

    /**
     * @return array<string, mixed>|null
     */
    public function findRuntimePageBySource(string $sourceTemplateId): ?array
    {
        $statement = $this->pdo->prepare(
            'SELECT *
             FROM pages
             WHERE source_template_id = :source_template_id
             LIMIT 1'
        );
        $statement->execute([':source_template_id' => trim($sourceTemplateId)]);
        $row = $statement->fetch();

        return is_array($row) ? $row : null;
    }

    private function templateKey(string $sourceTemplateId): string
    {
        return myronNormalizeSlug(str_replace(['template.', '.v1'], ['', ''], $sourceTemplateId));
    }

    /**
     * Build the schema_json payload for a runtime template version.
     *
     * STN-012-004: A `defaultContent` field is included in the schema. Sources, in priority order:
     *   1. `$preserveDefaultContent` if non-null and non-empty — preserves Designer customizations
     *      already present on a runtime template that's being re-instantiated.
     *   2. `$definition['defaultContent']` — the platform-shipped seed content for this template,
     *      authored as JSON in the bundle definition (per the STN-012 contract pipeline).
     *   3. Empty array — Page Template starts with no seed content; Designer or Content operator
     *      will fill in via their respective surfaces.
     *
     * The Designer edits seed content via {@see MyronContentTemplatesTrait::updateRuntimeTemplateDefaultContent()},
     * which writes back to the same `defaultContent` field on `page_template_versions.schema_json`.
     *
     * @param array<string, mixed> $definition
     * @param array<string, mixed>|null $preserveDefaultContent
     * @return array<string, mixed>
     */
    private function schemaForDefinition(array $definition, ?array $preserveDefaultContent = null): array
    {
        $chrome = MyronTemplateDefinitionRepository::chromeForTemplateRecord($definition);
        $bodySections = MyronTemplateDefinitionRepository::bodySectionsForRecord($definition);

        $defaultContent = is_array($preserveDefaultContent) && $preserveDefaultContent !== []
            ? $preserveDefaultContent
            : (is_array($definition['defaultContent'] ?? null) ? $definition['defaultContent'] : []);

        return [
            'sourceTemplateId' => (string) ($definition['id'] ?? ''),
            'sourceBundleId' => (string) ($definition['bundleId'] ?? ''),
            'chrome' => $chrome,
            'headerCapabilities' => MyronTemplateDefinitionRepository::headerCapabilitiesForTemplateRecord($definition, $chrome),
            'bridge' => $this->bridgeMetadataForDefinition($definition, count($bodySections)),
            'regions' => [
                ['key' => 'eyebrow', 'label' => 'Eyebrow', 'type' => 'text', 'richtext' => true],
                ['key' => 'headline', 'label' => 'Headline', 'type' => 'text', 'richtext' => true],
                ['key' => 'intro', 'label' => 'Intro', 'type' => 'textarea', 'richtext' => true],
                ['key' => 'body', 'label' => 'Body', 'type' => 'textarea', 'richtext' => true],
                ['key' => 'ctaLabel', 'label' => 'CTA label', 'type' => 'text', 'richtext' => true],
                ['key' => 'ctaHref', 'label' => 'CTA href', 'type' => 'text'],
            ],
            'sections' => $bodySections,
            'defaultContent' => $defaultContent,
        ];
    }

    /**
     * Fetches the `defaultContent` field from an existing page_template_versions row, used by the
     * re-bootstrap path of {@see instantiateTemplate()} to preserve Designer customizations.
     *
     * Returns null (not empty array) so the caller can distinguish "no existing version found"
     * from "existing version had empty defaultContent" — both cases lead to the definition's
     * defaultContent being used.
     *
     * @return array<string, mixed>|null
     */
    private function fetchExistingDefaultContent(string $versionId): ?array
    {
        $versionId = trim($versionId);
        if ($versionId === '') {
            return null;
        }
        $statement = $this->pdo->prepare(
            'SELECT schema_json FROM page_template_versions WHERE id = :id LIMIT 1'
        );
        $statement->execute([':id' => $versionId]);
        $schemaJson = $statement->fetchColumn();
        if (!is_string($schemaJson) || trim($schemaJson) === '') {
            return null;
        }
        try {
            $schema = json_decode($schemaJson, true, 512, JSON_THROW_ON_ERROR);
        } catch (Throwable $exception) {
            return null;
        }
        if (!is_array($schema)) {
            return null;
        }
        $existing = $schema['defaultContent'] ?? null;
        return is_array($existing) ? $existing : null;
    }

    /**
     * @param array<string, mixed> $definition
     * @return array{normalizedBodySections:bool,legacyChromeSectionCount:int,bodySectionCount:int}
     */
    private function bridgeMetadataForDefinition(array $definition, int $bodySectionCount): array
    {
        $legacyChromeSectionCount = 0;
        foreach (is_array($definition['sections'] ?? null) ? $definition['sections'] : [] as $section) {
            if (!is_array($section)) {
                continue;
            }

            if (in_array(trim((string) ($section['componentId'] ?? '')), ['header', 'footer'], true)) {
                $legacyChromeSectionCount++;
            }
        }

        return [
            'normalizedBodySections' => true,
            'legacyChromeSectionCount' => $legacyChromeSectionCount,
            'bodySectionCount' => $bodySectionCount,
        ];
    }

    /**
     * @param array<string, mixed> $definition
     * @return array{title:string,slug:string,path:string,parentPath:string,seoTitle:string,metaDescription:string}
     */
    private function pageIdentityForTemplate(array $definition): array
    {
        $sourceTemplateId = (string) ($definition['id'] ?? '');
        $name = trim((string) ($definition['name'] ?? $sourceTemplateId)) ?: $sourceTemplateId;
        $explicitIdentity = $this->explicitPageIdentity($definition, $sourceTemplateId, $name);
        if ($explicitIdentity !== null) {
            return $explicitIdentity;
        }

        $parts = explode('.', $sourceTemplateId);
        $role = (string) ($parts[1] ?? myronNormalizeSlug($name));
        $titleMap = [
            'home' => 'Home',
            'about' => 'About',
            'services' => 'Services',
            'contact' => 'Contact',
            'legal' => 'Legal',
            '404' => '404',
            'search' => 'Search',
            'coming-soon' => 'Coming Soon',
        ];
        $title = trim((string) ($definition['pageTitle'] ?? '')) ?: ($titleMap[$role] ?? ucwords(str_replace('-', ' ', $role)));
        $route = trim((string) ($definition['route'] ?? ''));
        $path = $route !== ''
            ? myronNormalizePath($route)
            : ($role === 'home' ? '/' : '/' . myronNormalizeSlug($title));
        $segments = array_values(array_filter(explode('/', trim($path, '/')), static fn (string $segment): bool => $segment !== ''));
        $slug = $path === '/'
            ? 'home'
            : myronNormalizeSlug((string) end($segments));
        $parentPath = '';
        if (count($segments) > 1) {
            array_pop($segments);
            $parentPath = myronNormalizePath('/' . implode('/', $segments));
        }

        return [
            'title' => $title,
            'slug' => $slug,
            'path' => $path,
            'parentPath' => $parentPath,
            'seoTitle' => $title,
            'metaDescription' => $name . ' page instantiated from the active bundle.',
        ];
    }

    /**
     * @param array<string, mixed> $definition
     * @return array{title:string,slug:string,path:string,parentPath:string,seoTitle:string,metaDescription:string}|null
     */
    private function explicitPageIdentity(array $definition, string $sourceTemplateId, string $name): ?array
    {
        $routeMap = [
            'template.blog-index.mkt.v1' => ['Blog', '/blog'],
            'template.post-single.mkt.v1' => ['Post', '/blog/post'],
            'template.resources.mkt.v1' => ['Resources', '/resources'],
            'template.featured-offers.mkt.v1' => ['Featured Offers', '/featured-offers'],
            'template.funnel.squeeze.mkt.v1' => ['Squeeze', '/squeeze'],
            'template.funnel.bridge.mkt.v1' => ['Bridge', '/bridge'],
            'template.funnel.sales-long-form.mkt.v1' => ['Sales', '/sales'],
            'template.funnel.checkout.mkt.v1' => ['Checkout', '/checkout'],
            'template.funnel.upsell.mkt.v1' => ['Upsell', '/upsell'],
            'template.funnel.downsell.mkt.v1' => ['Downsell', '/downsell'],
            'template.funnel.confirmation.mkt.v1' => ['Confirmation', '/confirmation'],
            'template.funnel.expired.mkt.v1' => ['Expired', '/expired'],
            'template.funnel.reverse-squeeze.mkt.v1' => ['Reverse Squeeze', '/reverse-squeeze'],
            'template.funnel.quiz.mkt.v1' => ['Quiz', '/quiz'],
            'template.funnel.vsl.mkt.v1' => ['Video Sales Letter', '/vsl'],
            'template.funnel.application.mkt.v1' => ['Application', '/application'],
            'template.funnel.webinar-registration.mkt.v1' => ['Webinar Registration', '/webinar-registration'],
            'template.funnel.webinar-confirmation.mkt.v1' => ['Webinar Confirmation', '/webinar-confirmation'],
            'template.funnel.webinar-broadcast.mkt.v1' => ['Webinar Broadcast', '/webinar-broadcast'],
            'template.funnel.webinar-replay.mkt.v1' => ['Webinar Replay', '/webinar-replay'],
            'template.funnel.free-plus-shipping-checkout.mkt.v1' => ['Free Plus Shipping Checkout', '/free-plus-shipping-checkout'],
            'template.funnel.summit-hub.mkt.v1' => ['Summit Hub', '/summit-hub'],
            'template.funnel.challenge-enrollment.mkt.v1' => ['Challenge Enrollment', '/challenge-enrollment'],
            'template.funnel.cancellation-retention.mkt.v1' => ['Cancellation Retention', '/cancellation-retention'],
        ];

        $route = trim((string) ($definition['route'] ?? ''));
        $title = trim((string) ($definition['pageTitle'] ?? ''));
        if (!isset($routeMap[$sourceTemplateId]) && $route === '') {
            return null;
        }

        $mapped = $routeMap[$sourceTemplateId] ?? [$title, $route];
        $resolvedTitle = $title !== '' ? $title : (string) $mapped[0];
        $path = myronNormalizePath($route !== '' ? $route : (string) $mapped[1]);
        $segments = array_values(array_filter(explode('/', trim($path, '/')), static fn (string $segment): bool => $segment !== ''));
        $slug = $path === '/' ? 'home' : myronNormalizeSlug((string) end($segments));
        $parentPath = '';
        if (count($segments) > 1) {
            array_pop($segments);
            $parentPath = myronNormalizePath('/' . implode('/', $segments));
        }

        return [
            'title' => $resolvedTitle,
            'slug' => $slug,
            'path' => $path,
            'parentPath' => $parentPath,
            'seoTitle' => $resolvedTitle,
            'metaDescription' => $name . ' page instantiated from the active bundle.',
        ];
    }

    /**
     * @param array{title:string,slug:string,path:string,parentPath:string,seoTitle:string,metaDescription:string} $identity
     * @return array{title:string,slug:string,path:string,parentPath:string,seoTitle:string,metaDescription:string}
     */
    private function collisionSafeIdentity(array $identity, string $sourceTemplateId, string $sourceBundleId): array
    {
        $baseSlug = myronNormalizeSlug($identity['slug'] . '-' . $this->bundleSlugSuffix($sourceBundleId));
        $fallbackSlug = $baseSlug;
        $counter = 2;

        while ($this->findRuntimePageBySlug($fallbackSlug) !== null) {
            $fallbackSlug = $baseSlug . '-' . (string) $counter;
            $counter++;
        }

        $identity['slug'] = $fallbackSlug;
        $identity['path'] = myronNormalizePath('/' . $fallbackSlug);
        $identity['parentPath'] = '';
        $identity['metaDescription'] = $identity['metaDescription'] . ' Route adjusted because another bundled page already owns the canonical path for `' . $sourceTemplateId . '`.';

        return $identity;
    }

    private function bundleSlugSuffix(string $sourceBundleId): string
    {
        $parts = explode('.', trim($sourceBundleId));
        $suffix = (string) end($parts);

        return myronNormalizeSlug($suffix !== '' ? $suffix : $sourceBundleId);
    }

    /**
     * @param array<string, mixed> $definition
     * @param array{title:string,slug:string,path:string,parentPath:string,seoTitle:string,metaDescription:string} $identity
     * @return array<string, string>
     */
    private function pageContentForTemplate(array $definition, array $identity): array
    {
        $sourceTemplateId = (string) ($definition['id'] ?? '');
        $sourceBundleId = (string) ($definition['bundleId'] ?? '');
        $sectionCount = count(is_array($definition['sections'] ?? null) ? $definition['sections'] : []);

        $ctaLabel = $identity['title'] === 'Home' ? 'Contact us' : 'Back to home';
        $ctaHref = $identity['title'] === 'Home' ? '/contact' : '/';
        if ($sourceBundleId === 'site-type.blog') {
            $ctaLabel = 'Subscribe';
            $ctaHref = '#subscribe';
        }
        if ($sourceBundleId === 'site-type.news') {
            $ctaLabel = 'Read article';
            $ctaHref = '/first-news-story';
            if (!str_contains($sourceTemplateId, '.home.')) {
                $ctaLabel = 'Subscribe';
                $ctaHref = '#subscribe';
            }
        }
        if ($sourceBundleId === 'site-type.saas') {
            $ctaLabel = 'Start Free Trial';
            $ctaHref = '/signup';
            if ($identity['title'] === 'Account') {
                $ctaLabel = 'Login';
                $ctaHref = '/login';
            }
        }
        if ($sourceBundleId === 'site-type.ecom') {
            $ctaLabel = 'Shop now';
            $ctaHref = '/shop';
            if (str_contains($sourceTemplateId, '.cart.')) {
                $ctaLabel = 'Checkout';
                $ctaHref = '/checkout';
            } elseif (str_contains($sourceTemplateId, '.checkout.')) {
                $ctaLabel = 'Place test order';
                $ctaHref = '/checkout';
            } elseif (str_contains($sourceTemplateId, '.account.')) {
                $ctaLabel = 'View account';
                $ctaHref = '/account';
            } elseif (str_contains($sourceTemplateId, '.category-archive.')) {
                $ctaLabel = 'Browse collections';
                $ctaHref = '/category';
            } elseif (str_contains($sourceTemplateId, '.about.')) {
                $ctaLabel = 'Visit shop';
                $ctaHref = '/shop';
            }
        }
        if ($sourceBundleId === 'site-type.bkg') {
            $ctaLabel = 'Book now';
            $ctaHref = '/reservation';
        }
        if ($sourceBundleId === 'site-type.mkt') {
            $ctaLabel = 'Join the list';
            $ctaHref = '/squeeze';
            if (str_contains($sourceTemplateId, '.squeeze.')) {
                $ctaLabel = 'Get the resource';
                $ctaHref = '/bridge';
            } elseif (str_contains($sourceTemplateId, '.bridge.')) {
                $ctaLabel = 'See the offer';
                $ctaHref = '/sales';
            } elseif (str_contains($sourceTemplateId, '.sales-long-form.')) {
                $ctaLabel = 'Checkout';
                $ctaHref = '/checkout';
            } elseif (str_contains($sourceTemplateId, '.checkout.') && !str_contains($sourceTemplateId, '.free-plus-shipping-checkout.')) {
                $ctaLabel = 'Continue';
                $ctaHref = '/upsell';
            } elseif (str_contains($sourceTemplateId, '.upsell.')) {
                $ctaLabel = 'Accept offer';
                $ctaHref = '/confirmation';
            } elseif (str_contains($sourceTemplateId, '.downsell.')) {
                $ctaLabel = 'No thanks';
                $ctaHref = '/confirmation';
            } elseif (str_contains($sourceTemplateId, '.confirmation.') && !str_contains($sourceTemplateId, '.webinar-confirmation.')) {
                $ctaLabel = 'Back to resources';
                $ctaHref = '/resources';
            } elseif (str_contains($sourceTemplateId, '.expired.')) {
                $ctaLabel = 'See current offers';
                $ctaHref = '/featured-offers';
            } elseif (str_contains($sourceTemplateId, '.featured-offers.')) {
                $ctaLabel = 'See the offer';
                $ctaHref = '/sales';
            } elseif (str_contains($sourceTemplateId, '.resources.')) {
                $ctaLabel = 'Get the resource';
                $ctaHref = '/squeeze';
            } elseif (str_contains($sourceTemplateId, '.application.')) {
                $ctaLabel = 'Submit application';
                $ctaHref = '/confirmation';
            } elseif (str_contains($sourceTemplateId, '.challenge-enrollment.')) {
                $ctaLabel = 'Join the challenge';
                $ctaHref = '/confirmation';
            } elseif (str_contains($sourceTemplateId, '.cancellation-retention.')) {
                $ctaLabel = 'Review options';
                $ctaHref = '/confirmation';
            } elseif (str_contains($sourceTemplateId, '.free-plus-shipping-checkout.')) {
                $ctaLabel = 'Complete test checkout';
                $ctaHref = '/upsell';
            } elseif (str_contains($sourceTemplateId, '.quiz.')) {
                $ctaLabel = 'See your result';
                $ctaHref = '/bridge';
            } elseif (str_contains($sourceTemplateId, '.reverse-squeeze.')) {
                $ctaLabel = 'Continue';
                $ctaHref = '/bridge';
            } elseif (str_contains($sourceTemplateId, '.summit-hub.')) {
                $ctaLabel = 'Start learning';
                $ctaHref = '/resources';
            } elseif (str_contains($sourceTemplateId, '.vsl.')) {
                $ctaLabel = 'Start checkout';
                $ctaHref = '/checkout';
            } elseif (str_contains($sourceTemplateId, '.webinar-broadcast.')) {
                $ctaLabel = 'Watch replay';
                $ctaHref = '/webinar-replay';
            } elseif (str_contains($sourceTemplateId, '.webinar-confirmation.')) {
                $ctaLabel = 'Join the webinar';
                $ctaHref = '/webinar-broadcast';
            } elseif (str_contains($sourceTemplateId, '.webinar-registration.')) {
                $ctaLabel = 'Reserve your spot';
                $ctaHref = '/webinar-confirmation';
            } elseif (str_contains($sourceTemplateId, '.webinar-replay.')) {
                $ctaLabel = 'View the offer';
                $ctaHref = '/sales';
            }
        }

        $eyebrow = $sourceBundleId === 'starter.default' ? 'Starter page' : 'Bundle page';
        $intro = $sourceBundleId === 'starter.default'
            ? 'This starter page gives a new site a source-linked, section-based launch surface before a primary site type is selected.'
            : 'This page was instantiated from `' . $sourceTemplateId . '` for `' . $sourceBundleId . '`.';
        $body = $sourceBundleId === 'starter.default'
            ? 'Use this page as an initial launch scaffold, or keep it while choosing a real site type. The structure is governed by reusable sections so it can render through the same public template path as bundled pages.'
            : 'The active bundle supplied this page template with ' . (string) $sectionCount . ' governed section definitions. Content can now be edited without losing the source template link.';

        return [
            'eyebrow' => $eyebrow,
            'headline' => $identity['title'],
            'intro' => $intro,
            'body' => $body,
            'ctaLabel' => $ctaLabel,
            'ctaHref' => $ctaHref,
        ];
    }

    /**
     * @param array<string, mixed> $page
     * @param array<string, mixed> $definition
     * @param array<string, mixed> $runtimeTemplate
     * @param array{title:string,slug:string,path:string,parentPath:string,seoTitle:string,metaDescription:string} $identity
     */
    private function syncRuntimePageIdentity(array $page, array $definition, array $runtimeTemplate, array $identity): void
    {
        $pageId = (string) ($page['id'] ?? '');
        if ($pageId === '') {
            return;
        }
        $wasSourceLinked = trim((string) ($page['source_template_id'] ?? '')) !== '';

        $pathInUse = $this->findRuntimePageByPath($identity['path']);
        if (is_array($pathInUse) && (string) ($pathInUse['id'] ?? '') !== $pageId) {
            return;
        }

        $parentPageId = null;
        if ($identity['parentPath'] !== '') {
            $parentPage = $this->findRuntimePageByPath($identity['parentPath']);
            $parentPageId = is_array($parentPage) ? (string) ($parentPage['id'] ?? '') : null;
        }

        $statement = $this->pdo->prepare(
            'UPDATE pages
             SET template_id = :template_id,
                 parent_page_id = :parent_page_id,
                 slug = :slug,
                 path = :path,
                 title = :title,
                 source_template_id = :source_template_id,
                 source_bundle_id = :source_bundle_id,
                 updated_at = :updated_at
             WHERE id = :id'
        );
        $statement->execute([
            ':template_id' => (string) ($runtimeTemplate['id'] ?? ''),
            ':parent_page_id' => $parentPageId !== '' ? $parentPageId : null,
            ':slug' => $identity['slug'],
            ':path' => $identity['path'],
            ':title' => $identity['title'],
            ':source_template_id' => (string) ($definition['id'] ?? ''),
            ':source_bundle_id' => (string) ($definition['bundleId'] ?? ''),
            ':updated_at' => myronNow(),
            ':id' => $pageId,
        ]);

        $versionIdParams = [
            ':template_version_id' => (string) ($runtimeTemplate['current_version_id'] ?? ''),
            ':title' => $identity['title'],
        ];
        if (!$wasSourceLinked) {
            $versionIdParams[':seo_title'] = $identity['seoTitle'];
            $versionIdParams[':meta_description'] = $identity['metaDescription'];
            $versionIdParams[':content_json'] = json_encode(
                $this->pageContentForTemplate($definition, $identity),
                JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES
            );
        }
        foreach (['current_draft_version_id', 'current_published_version_id'] as $field) {
            $versionId = trim((string) ($page[$field] ?? ''));
            if ($versionId === '') {
                continue;
            }
            if ($wasSourceLinked) {
                $updateVersion = $this->pdo->prepare(
                    'UPDATE page_versions
                     SET template_version_id = :template_version_id,
                         title = :title
                     WHERE id = :id'
                );
            } else {
                $updateVersion = $this->pdo->prepare(
                    'UPDATE page_versions
                     SET template_version_id = :template_version_id,
                         title = :title,
                         seo_title = :seo_title,
                         meta_description = :meta_description,
                         content_json = :content_json
                     WHERE id = :id'
                );
            }
            $updateVersion->execute($versionIdParams + [':id' => $versionId]);
        }
    }
}
