<?php

declare(strict_types=1);

final class MyronSectionRegistryRepository
{
    public function __construct(
        private readonly PDO $pdo,
        private readonly ?MyronSectionBridgeRepository $bridge = null,
        private readonly ?MyronCustomSectionTemplateRepository $customSections = null,
    ) {
    }

    public function ensureSeeded(): void
    {
        $records = self::canonicalRecords();
        $now = myronNow();
        $savedKeys = [];

        foreach ($records as $record) {
            $sectionKey = (string) $record['sectionKey'];
            $savedKeys[] = $sectionKey;
            $primaryComponents = json_encode($record['primaryComponents'], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
            $payload = json_encode($record, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);

            if ($this->recordExists($sectionKey)) {
                $statement = $this->pdo->prepare(
                    'UPDATE section_registry
                     SET label = :label,
                         jtbd_group = :jtbd_group,
                         section_type = :section_type,
                         primary_components_json = :primary_components_json,
                         remediation_status = :remediation_status,
                         record_json = :record_json,
                         updated_at = :updated_at
                     WHERE section_key = :section_key'
                );
                $statement->execute([
                    ':label' => (string) $record['label'],
                    ':jtbd_group' => (string) $record['jtbdGroup'],
                    ':section_type' => (string) $record['sectionType'],
                    ':primary_components_json' => $primaryComponents,
                    ':remediation_status' => (string) $record['remediationStatus'],
                    ':record_json' => $payload,
                    ':updated_at' => $now,
                    ':section_key' => $sectionKey,
                ]);
                continue;
            }

            $statement = $this->pdo->prepare(
                'INSERT INTO section_registry (
                    section_key,
                    label,
                    jtbd_group,
                    section_type,
                    primary_components_json,
                    remediation_status,
                    record_json,
                    seeded_at,
                    updated_at
                 ) VALUES (
                    :section_key,
                    :label,
                    :jtbd_group,
                    :section_type,
                    :primary_components_json,
                    :remediation_status,
                    :record_json,
                    :seeded_at,
                    :updated_at
                 )'
            );
            $statement->execute([
                ':section_key' => $sectionKey,
                ':label' => (string) $record['label'],
                ':jtbd_group' => (string) $record['jtbdGroup'],
                ':section_type' => (string) $record['sectionType'],
                ':primary_components_json' => $primaryComponents,
                ':remediation_status' => (string) $record['remediationStatus'],
                ':record_json' => $payload,
                ':seeded_at' => $now,
                ':updated_at' => $now,
            ]);
        }

        $this->deleteMissingSections($savedKeys);
    }

    public function countSections(): int
    {
        return (int) $this->pdo->query('SELECT COUNT(*) FROM section_registry')->fetchColumn();
    }

    /**
     * @return list<string>
     */
    public function listSectionKeys(): array
    {
        $statement = $this->pdo->query('SELECT section_key FROM section_registry ORDER BY section_key ASC');
        $rows = $statement ? $statement->fetchAll(PDO::FETCH_COLUMN) : [];

        $keys = array_values(array_map('strval', is_array($rows) ? $rows : []));
        if ($this->customSections instanceof MyronCustomSectionTemplateRepository) {
            foreach ($this->customSections->listTemplates() as $section) {
                $sectionKey = trim((string) ($section['sectionKey'] ?? ''));
                if ($sectionKey !== '') {
                    $keys[] = $sectionKey;
                }
            }
        }

        $keys = array_values(array_unique($keys));
        sort($keys);
        return $keys;
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function listSections(?string $jtbdGroup = null): array
    {
        $sql = 'SELECT * FROM section_registry';
        $params = [];

        if ($jtbdGroup !== null && trim($jtbdGroup) !== '') {
            $sql .= ' WHERE jtbd_group = :jtbd_group';
            $params[':jtbd_group'] = trim($jtbdGroup);
        }

        $sql .= ' ORDER BY jtbd_group ASC, section_key ASC';
        $statement = $this->pdo->prepare($sql);
        $statement->execute($params);
        $rows = $statement->fetchAll();

        if (!is_array($rows)) {
            return [];
        }

        $sections = array_map(fn (array $row): array => $this->hydrateSection($row), $rows);

        $sections = $this->bridge instanceof MyronSectionBridgeRepository
            ? $this->bridge->mergeSections($sections)
            : $sections;
        if ($this->customSections instanceof MyronCustomSectionTemplateRepository) {
            $sections = array_merge($sections, $this->customSections->listTemplates($jtbdGroup));
        }

        usort(
            $sections,
            static fn (array $a, array $b): int => [
                (string) ($a['jtbdGroup'] ?? ''),
                (string) ($a['sectionKey'] ?? ''),
            ] <=> [
                (string) ($b['jtbdGroup'] ?? ''),
                (string) ($b['sectionKey'] ?? ''),
            ]
        );

        return $sections;
    }

    public function findSectionByKey(string $sectionKey): ?array
    {
        $statement = $this->pdo->prepare('SELECT * FROM section_registry WHERE section_key = :section_key LIMIT 1');
        $statement->execute([':section_key' => trim($sectionKey)]);
        $row = $statement->fetch();

        if (!is_array($row)) {
            return $this->customSections instanceof MyronCustomSectionTemplateRepository
                ? $this->customSections->findBySectionKey($sectionKey)
                : null;
        }

        $section = $this->hydrateSection($row);

        return $this->bridge instanceof MyronSectionBridgeRepository
            ? $this->bridge->mergeSection($section)
            : $section;
    }

    public function isGovernedSectionKey(string $sectionKey): bool
    {
        return $this->findSectionByKey($sectionKey) !== null;
    }

    public function assertGovernedSectionKey(string $sectionKey, string $templateId): void
    {
        $normalized = trim($sectionKey);
        if ($normalized === '') {
            throw new InvalidArgumentException('Template `' . $templateId . '` contains an empty governed section key.');
        }

        if (in_array($normalized, self::forbiddenBodySectionKeys(), true)) {
            throw new InvalidArgumentException('Template `' . $templateId . '` may not use non-section `' . $normalized . '` as a body section.');
        }

        if (!$this->isGovernedSectionKey($normalized)) {
            throw new InvalidArgumentException('Template `' . $templateId . '` references unknown governed section `' . $normalized . '`.');
        }
    }

    /**
     * @return list<string>
     */
    public static function forbiddenBodySectionKeys(): array
    {
        return [
            'button',
            'empty-state',
            'footer',
            'form-field',
            'header',
            'image',
            'link',
        ];
    }

    /**
     * @return list<array{sectionKey:string,label:string,jtbdGroup:string,sectionType:string,primaryComponents:list<string>,remediationStatus:string}>
     */
    public static function canonicalRecords(): array
    {
        $primaryComponentOverrides = [
            'page-intro' => ['hero-simple'],
            'content-body' => ['cms-content-body'],
            'next-step' => ['cta-band'],
            'follow-up-links' => ['stack'],
            'contact' => ['contact-form-with-info'],
            'legal-document' => ['cms-content-body', 'legal-doc-toc'],
            'search-results' => ['combobox', 'list', 'pagination', 'empty-state'],
            'article' => ['cms-content-title', 'cms-content-body', 'cms-featured-media', 'user-identity', 'chip'],
            'post-listing' => ['blog-teaser', 'product-filters', 'pagination', 'stack'],
            'post-follow-up' => ['blog-teaser', 'cta-band', 'newsletter-signup'],
            'index-follow-up' => ['newsletter-signup', 'cta-band'],
            'content-preview' => ['blog-teaser', 'stack'],
            'archive-listing' => ['product-filters', 'blog-teaser', 'pagination'],
            'people' => ['team-grid', 'user-identity'],
            'testimonials' => ['testimonial-grid'],
            'logo-strip' => ['logo'],
            'stats-strip' => ['stats-tile'],
            'faq' => ['faq-list'],
            'value-proposition' => ['feature-grid'],
            'about-principles' => ['feature-grid'],
            'about-story' => ['section-band'],
            'home-hero' => ['hero-with-cta', 'hero-simple', 'carousel'],
            'offering-overview' => ['services-grid', 'product-grid', 'course-catalog', 'feature-grid'],
        ];
        $groups = [
            'universal' => [
                'page-intro',
                'content-body',
                'next-step',
                'follow-up-links',
                'contact',
                'legal-document',
                'search-results',
            ],
            'content-publishing' => [
                'article',
                'post-listing',
                'post-follow-up',
                'archive-listing',
                'index-follow-up',
                'content-preview',
                'resource-listing',
            ],
            'portfolio-work' => [
                'work-listing',
                'work-preview',
                'project-detail',
            ],
            'commerce-transaction' => [
                'product-listing',
                'product-detail',
                'product-reviews',
                'cart-workspace',
                'checkout-workspace',
                'account-workspace',
            ],
            'education-program' => [
                'course-listing',
                'course-detail',
                'program-enrollment',
                'event-schedule',
                'people',
            ],
            'funnel' => [
                'webinar-watch',
                'webinar-registration',
                'funnel-confirmation',
                'funnel-video-offer',
                'funnel-offer-decision',
                'funnel-sales-letter',
                'funnel-quiz',
                'retention-flow',
                'application-flow',
            ],
            'marketing-proof-conversion' => [
                'conversion-offer',
                'featured-offers',
                'pricing-options',
                'comparison',
                'testimonials',
                'logo-strip',
                'stats-strip',
                'standards-card',
                'faq',
                'value-proposition',
            ],
            'brand-story' => [
                'about-principles',
                'about-story',
                'announcement-bar',
                'booking',
                'feature-listing',
                'feature-story',
                'home-hero',
                'location-details',
                'offering-overview',
                'pricing-preview',
                'restaurant-menu',
            ],
        ];

        $records = [];
        foreach ($groups as $group => $sectionKeys) {
            foreach ($sectionKeys as $sectionKey) {
                $records[] = [
                    'sectionKey' => $sectionKey,
                    'label' => self::labelForKey($sectionKey),
                    'jtbdGroup' => $group,
                    'sectionType' => self::sectionTypeForGroup($group),
                    'primaryComponents' => $primaryComponentOverrides[$sectionKey] ?? [$sectionKey],
                    'remediationStatus' => 'governed',
                ];
            }
        }

        return $records;
    }

    /**
     * @param list<string> $sectionKeys
     */
    private function deleteMissingSections(array $sectionKeys): void
    {
        $normalized = array_values(array_filter(array_map('strval', $sectionKeys), static fn (string $key): bool => trim($key) !== ''));
        if ($normalized === []) {
            $this->pdo->exec('DELETE FROM section_registry');
            return;
        }

        $placeholders = [];
        $params = [];
        foreach ($normalized as $index => $sectionKey) {
            $placeholder = ':section_key_' . $index;
            $placeholders[] = $placeholder;
            $params[$placeholder] = $sectionKey;
        }

        $statement = $this->pdo->prepare(
            'DELETE FROM section_registry WHERE section_key NOT IN (' . implode(', ', $placeholders) . ')'
        );
        $statement->execute($params);
    }

    /**
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function hydrateSection(array $row): array
    {
        $record = $this->decodeJsonField((string) ($row['record_json'] ?? ''), 'section registry record');
        $primaryComponents = $this->decodeJsonListField((string) ($row['primary_components_json'] ?? '[]'), 'section primary components');

        $record['_registry'] = [
            'sectionKey' => (string) ($row['section_key'] ?? ''),
            'label' => (string) ($row['label'] ?? ''),
            'jtbdGroup' => (string) ($row['jtbd_group'] ?? ''),
            'sectionType' => (string) ($row['section_type'] ?? ''),
            'primaryComponents' => $primaryComponents,
            'remediationStatus' => (string) ($row['remediation_status'] ?? ''),
            'seededAt' => (string) ($row['seeded_at'] ?? ''),
            'updatedAt' => (string) ($row['updated_at'] ?? ''),
        ];
        $record['primaryComponents'] = $primaryComponents;

        return $record;
    }

    private static function labelForKey(string $sectionKey): string
    {
        return ucwords(str_replace('-', ' ', $sectionKey));
    }

    private static function sectionTypeForGroup(string $group): string
    {
        return match ($group) {
            'commerce-transaction' => 'commerce',
            'content-publishing' => 'content',
            'education-program' => 'program',
            'funnel' => 'funnel',
            'marketing-proof-conversion' => 'conversion',
            'portfolio-work' => 'portfolio',
            default => 'semantic',
        };
    }

    private function recordExists(string $sectionKey): bool
    {
        $statement = $this->pdo->prepare('SELECT 1 FROM section_registry WHERE section_key = :section_key LIMIT 1');
        $statement->execute([':section_key' => trim($sectionKey)]);

        return $statement->fetchColumn() !== false;
    }

    /**
     * @return array<string, mixed>
     */
    private function decodeJsonField(string $json, string $label): array
    {
        $decoded = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
        if (!is_array($decoded)) {
            throw new RuntimeException('Stored ' . $label . ' could not be decoded into an array payload.');
        }

        return $decoded;
    }

    /**
     * @return list<string>
     */
    private function decodeJsonListField(string $json, string $label): array
    {
        $decoded = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
        if (!is_array($decoded)) {
            throw new RuntimeException('Stored ' . $label . ' could not be decoded into a list payload.');
        }

        return array_values(array_map('strval', $decoded));
    }
}
