<?php

declare(strict_types=1);

final class MyronSectionContentSchemaService
{
    public function __construct(private readonly ?MyronSectionRendererRegistry $renderers = null)
    {
    }

    /**
     * @param array<string, mixed> $section
     * @param array<string, mixed> $content
     * @return array{fields:list<array<string, mixed>>,repeaters:list<array<string, mixed>>,contracted:bool}
     */
    public function schemaForSection(array $section, int $position, array $content = [], string $surfaceKind = 'page'): array
    {
        $componentId = trim((string) ($section['componentId'] ?? ''));
        $sectionKey = trim((string) ($section['sectionKey'] ?? ''));
        $declared = $this->declaredContractForSection($section);
        $componentContract = $this->renderers?->contractForComponent($componentId, $section, $content);
        $contracted = $componentContract instanceof MyronSectionContract;
        if ($contracted) {
            $resolvedContract = $componentContract->withOverrides($declared['fields'], $declared['repeaters']);
            $fields = $resolvedContract->fields;
            $repeaters = $resolvedContract->repeaters;
        } else {
            $fields = $declared['fields'] !== []
                ? $declared['fields']
                : $this->flatFieldsForSection($componentId, $sectionKey, $position);
            $repeaters = $declared['repeaters'] !== []
                ? $declared['repeaters']
                : $this->repeatersForSection($componentId, $sectionKey, $content);
        }

        foreach ($fields as $index => $field) {
            $key = (string) ($field['key'] ?? '');
            $value = $key !== '' ? ($content[$key] ?? '') : '';
            $fields[$index]['value'] = is_array($value) ? $value : trim((string) $value);
            $fields[$index]['surfaceKind'] = $surfaceKind;
            $fields[$index]['binding'] = (string) ($fields[$index]['binding'] ?? ('content.sections.{instanceKey}.' . $key));
            $fields[$index] = $this->withInferredRichtext($fields[$index]);
        }
        foreach ($repeaters as $index => $repeater) {
            $key = trim((string) ($repeater['key'] ?? ''));
            if ($key !== '' && array_key_exists($key, $content)) {
                $items = is_array($content[$key]) ? $content[$key] : [];
                $repeaters[$index]['items'] = array_values(array_filter($items, 'is_array'));
            } elseif (!is_array($repeater['items'] ?? null)) {
                $repeaters[$index]['items'] = [];
            }
            $repeaterFields = is_array($repeater['fields'] ?? null) ? $repeater['fields'] : [];
            foreach ($repeaterFields as $fieldIndex => $field) {
                if (is_array($field)) {
                    $repeaterFields[$fieldIndex] = $this->withInferredRichtext($field);
                }
            }
            $repeaters[$index]['fields'] = $repeaterFields;
        }

        return [
            'fields' => $this->dedupeFields($fields),
            'repeaters' => $repeaters,
            'contracted' => $contracted,
        ];
    }

    /** @param array<string, mixed> $section */
    public function hasComponentContract(array $section, array $content = []): bool
    {
        $componentId = trim((string) ($section['componentId'] ?? ''));
        return $componentId !== '' && $this->renderers?->contractForComponent($componentId, $section, $content) instanceof MyronSectionContract;
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function flatFieldsForSection(string $componentId, string $sectionKey, int $position): array
    {
        $fields = [];

        if ($position === 0 || in_array($componentId, ['hero-simple', 'hero-with-cta'], true) || in_array($sectionKey, ['home-hero', 'page-intro'], true)) {
            $fields = array_merge($fields, $this->baseCopyFields(true, true));
            $fields = array_merge($fields, $this->mediaFields('media', 'Media'));
        }

        if (in_array($componentId, ['section-band', 'cms-content-body'], true) || in_array($sectionKey, ['content-body', 'about-story'], true)) {
            $fields = array_merge($fields, $this->baseCopyFields(false, true));
            $fields = array_merge($fields, $this->mediaFields('media', 'Media'));
        }

        if ($componentId === 'feature-grid') {
            $fields = array_merge($fields, $this->baseCopyFields(false, false));
            $fields = array_merge($fields, $this->repeaterCompatibilityFields('item', 'Highlight', [
                'Title' => 'Title',
                'Body' => 'Body',
                'IconFileId' => 'Icon Asset',
                'IconAlt' => 'Icon Alt Text',
            ], 3));
        }

        if ($componentId === 'services-grid') {
            $fields = array_merge($fields, $this->baseCopyFields(false, false));
            $fields = array_merge($fields, $this->repeaterCompatibilityFields('service', 'Service', [
                'Title' => 'Title',
                'Body' => 'Body',
                'Href' => 'Link',
                'CtaLabel' => 'CTA Label',
                'IconFileId' => 'Icon Asset',
                'IconAlt' => 'Icon Alt Text',
            ], 3));
        }

        if ($componentId === 'stats-tile') {
            $fields = array_merge($fields, $this->baseCopyFields(false, false));
            $fields = array_merge($fields, $this->repeaterCompatibilityFields('stat', 'Stat', ['Label' => 'Label', 'Value' => 'Value', 'Trend' => 'Context'], 3));
        }

        if ($componentId === 'testimonial-grid') {
            $fields = array_merge($fields, $this->baseCopyFields(false, false));
            $fields = array_merge($fields, $this->repeaterCompatibilityFields('testimonial', 'Quote', ['Quote' => 'Quote', 'Author' => 'Author', 'Organization' => 'Organization'], 3));
        }

        if ($componentId === 'team-grid') {
            $fields = array_merge($fields, $this->baseCopyFields(false, false));
            for ($index = 1; $index <= 3; $index++) {
                $fields[] = $this->field('person' . $index . 'Name', 'Person ' . $index . ' Name');
                $fields[] = $this->field('person' . $index . 'Title', 'Person ' . $index . ' Title');
                $fields[] = $this->field('person' . $index . 'Bio', 'Person ' . $index . ' Bio', 'textarea');
                $fields = array_merge($fields, $this->mediaFields('person' . $index, 'Person ' . $index . ' Photo'));
            }
        }

        if ($componentId === 'logo') {
            $fields = array_merge($fields, $this->baseCopyFields(false, false));
            for ($index = 1; $index <= 6; $index++) {
                $fields = array_merge($fields, $this->mediaFields('logo' . $index, 'Logo ' . $index));
            }
        }

        if ($componentId === 'portfolio-grid') {
            $fields = array_merge($fields, $this->baseCopyFields(false, false));
        }

        if ($componentId === 'blog-teaser') {
            $fields = array_merge($fields, $this->baseCopyFields(false, false));
            $fields = array_merge($fields, $this->blogTeaserSourceFields());
        }

        if ($componentId === 'cta-band') {
            $fields = array_merge($fields, $this->baseCopyFields(false, true));
        }

        if ($componentId === 'scrolljack-hero') {
            for ($index = 1; $index <= 4; $index++) {
                $fields[] = $this->field('state_' . $index . '_headline', 'State ' . $index . ' Headline');
                $fields[] = $this->field('state_' . $index . '_body', 'State ' . $index . ' Body', 'textarea');
                $fields[] = $this->field('state_' . $index . '_cta_label', 'State ' . $index . ' CTA Label');
                $fields[] = $this->field('state_' . $index . '_cta_href', 'State ' . $index . ' CTA Link', 'url');
            }
        }

        if ($componentId === 'cms-featured-media') {
            $fields = array_merge($fields, $this->mediaFields('media', 'Featured Media'));
            $fields[] = $this->field('body', 'Caption', 'textarea');
        }

        return $this->dedupeFields($fields);
    }

    /**
     * @param array<string, mixed> $content
     * @return list<array<string, mixed>>
     */
    public function repeatersForSection(string $componentId, string $sectionKey, array $content = []): array
    {
        if ($componentId === 'portfolio-grid' || in_array($sectionKey, ['work-preview', 'work-listing'], true)) {
            return [
                $this->repeaterGroup(
                    'projects',
                    'Work Entries',
                    'Project',
                    [
                        $this->repeaterField('title', 'Title'),
                        $this->repeaterField('summary', 'Summary', 'textarea'),
                        $this->repeaterField('href', 'Link', 'url'),
                        $this->repeaterField('mediaFileId', 'Image Asset', 'image'),
                        $this->repeaterField('mediaAlt', 'Image Alt Text'),
                    ],
                    $content,
                    [
                        ['title' => 'First Project', 'summary' => 'Add your project details in Content.', 'href' => ''],
                        ['title' => 'Second Project', 'summary' => 'Add your project details in Content.', 'href' => ''],
                        ['title' => 'Third Project', 'summary' => 'Add your project details in Content.', 'href' => ''],
                    ]
                ),
            ];
        }

        return [];
    }

    /**
     * @param list<array<string, mixed>> $fields
     * @return list<array<string, mixed>>
     */
    private function dedupeFields(array $fields): array
    {
        $deduped = [];
        foreach ($fields as $field) {
            $key = (string) ($field['key'] ?? '');
            if ($key === '') {
                continue;
            }
            $deduped[$key] = $field;
        }

        return array_values($deduped);
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function baseCopyFields(bool $includeEyebrow, bool $includeCta): array
    {
        $fields = [];
        if ($includeEyebrow) {
            $fields[] = $this->field('eyebrow', 'Eyebrow');
        }
        $fields[] = $this->field('headline', 'Headline');
        $fields[] = $this->field('intro', 'Intro', 'textarea');
        $fields[] = $this->field('body', 'Body', 'textarea');
        if ($includeCta) {
            $fields[] = $this->field('ctaLabel', 'CTA Label');
            $fields[] = $this->field('ctaHref', 'CTA Link', 'url');
        }

        return $fields;
    }

    /**
     * @param array<string, string> $suffixes
     * @return list<array<string, mixed>>
     */
    private function repeaterCompatibilityFields(string $prefix, string $labelPrefix, array $suffixes, int $count): array
    {
        $fields = [];
        for ($index = 1; $index <= $count; $index++) {
            foreach ($suffixes as $suffix => $label) {
                $type = match ($suffix) {
                    'Body', 'Quote' => 'textarea',
                    'Href' => 'url',
                    'IconFileId' => 'icon',
                    default => 'text',
                };
                $fields[] = $this->field($prefix . $index . $suffix, $labelPrefix . ' ' . $index . ' ' . $label, $type);
            }
        }

        return $fields;
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function mediaFields(string $prefix, string $label): array
    {
        return [
            $this->field($prefix . 'FileId', $label . ' Asset', 'image'),
            $this->field($prefix . 'Alt', $label . ' Alt Text'),
            $this->field($prefix . 'Caption', $label . ' Caption', 'textarea'),
        ];
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function blogTeaserSourceFields(): array
    {
        $sourceMode = $this->field('sourceMode', 'Post Source Mode');
        $sourceMode['default'] = 'default';
        $sourceMode['constraints'] = [
            'acceptedValues' => ['default', 'recent', 'sourcePath'],
            'description' => 'default uses the normal archive/context post source, recent uses site-wide recent published posts, and sourcePath uses Source Path as the archive parent. A populated Source Path also activates path sourcing unless mode is recent.',
        ];

        $sourcePath = $this->field('sourcePath', 'Source Path', 'url');
        $sourcePath['default'] = '';
        $sourcePath['constraints'] = [
            'description' => 'Root-relative archive path such as /insights. Matching posts are descendants of this path.',
        ];

        $postsLimit = $this->field('postsLimit', 'Post Limit', 'number');
        $postsLimit['default'] = 6;
        $postsLimit['constraints'] = [
            'min' => 0,
            'max' => 100,
            'description' => 'Default is 6. Use 0 to render all matching posts, capped by available published posts.',
        ];

        $viewAllCtaLabel = $this->field('viewAllCtaLabel', 'View All CTA Label');
        $viewAllCtaLabel['default'] = '';
        $viewAllCtaLabel['constraints'] = [
            'description' => 'Optional label for the populated teaser archive CTA. Defaults to the section archive label.',
        ];

        $viewAllCtaHref = $this->field('viewAllCtaHref', 'View All CTA Href', 'url');
        $viewAllCtaHref['default'] = '';
        $viewAllCtaHref['constraints'] = [
            'description' => 'Optional root-relative archive URL for the populated teaser CTA, such as /insights.',
        ];

        return [$sourceMode, $sourcePath, $postsLimit, $viewAllCtaLabel, $viewAllCtaHref];
    }

    /**
     * @return array<string, mixed>
     */
    private function field(string $key, string $label, string $type = 'text'): array
    {
        $field = [
            'key' => $key,
            'label' => $label,
            'type' => $type,
            'binding' => 'content.sections.{instanceKey}.' . $key,
            'required' => false,
        ];
        if (in_array($type, ['image', 'icon', 'video', 'audio', 'document'], true)) {
            $field['assetKind'] = $type === 'icon' ? 'image' : $type;
            if ($type === 'icon') {
                $field['presentation'] = 'icon';
            }
        }

        return $field;
    }

    /**
     * @param list<array<string, mixed>> $fields
     * @param array<string, mixed> $content
     * @param list<array<string, string>> $fallbackItems
     * @return array<string, mixed>
     */
    private function repeaterGroup(string $key, string $label, string $itemLabel, array $fields, array $content, array $fallbackItems): array
    {
        $items = is_array($content[$key] ?? null) ? array_values(array_filter($content[$key], 'is_array')) : [];
        if ($items === []) {
            $items = $fallbackItems;
        }

        return [
            'key' => $key,
            'label' => $label,
            'itemLabel' => $itemLabel,
            'type' => 'repeater',
            'minItems' => 0,
            'maxItems' => 12,
            'binding' => 'content.sections.{instanceKey}.' . $key,
            'fields' => $fields,
            'items' => $items,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function repeaterField(string $key, string $label, string $type = 'text'): array
    {
        $field = [
            'key' => $key,
            'label' => $label,
            'type' => $type,
            'required' => false,
        ];
        if (in_array($type, ['image', 'icon', 'video', 'audio', 'document'], true)) {
            $field['assetKind'] = $type === 'icon' ? 'image' : $type;
            if ($type === 'icon') {
                $field['presentation'] = 'icon';
            }
        }

        return $field;
    }

    /**
     * @param array<string, mixed> $section
     * @return array{fields:list<array<string, mixed>>,repeaters:list<array<string, mixed>>}
     */
    private function declaredContractForSection(array $section): array
    {
        $sources = [];
        foreach (['contentContract', 'contentSchema'] as $key) {
            if (is_array($section[$key] ?? null)) {
                $sources[] = $section[$key];
            }
        }
        if (is_array($section['editing'] ?? null)) {
            $editing = $section['editing'];
            foreach (['contentContract', 'contentSchema'] as $key) {
                if (is_array($editing[$key] ?? null)) {
                    $sources[] = $editing[$key];
                }
            }
            if (is_array($editing['fields'] ?? null) || is_array($editing['repeaters'] ?? null)) {
                $sources[] = $editing;
            }
        }
        if (is_array($section['editableFields'] ?? null) || is_array($section['repeatableGroups'] ?? null)) {
            $sources[] = [
                'fields' => is_array($section['editableFields'] ?? null) ? $section['editableFields'] : [],
                'repeaters' => is_array($section['repeatableGroups'] ?? null) ? $section['repeatableGroups'] : [],
            ];
        }

        foreach ($sources as $source) {
            $fields = $this->normalizeDeclaredFields(is_array($source['fields'] ?? null) ? $source['fields'] : []);
            $repeaters = $this->normalizeDeclaredRepeaters(is_array($source['repeaters'] ?? null) ? $source['repeaters'] : []);
            if ($fields !== [] || $repeaters !== []) {
                return ['fields' => $fields, 'repeaters' => $repeaters];
            }
        }

        return ['fields' => [], 'repeaters' => []];
    }

    /**
     * @param array<mixed> $fields
     * @return list<array<string, mixed>>
     */
    private function normalizeDeclaredFields(array $fields): array
    {
        $normalized = [];
        foreach ($fields as $field) {
            if (!is_array($field)) {
                continue;
            }
            $key = trim((string) ($field['key'] ?? $field['name'] ?? ''));
            if ($key === '') {
                continue;
            }
            $type = $this->normalizeFieldType((string) ($field['type'] ?? 'text'));
            $record = [
                'key' => $key,
                'label' => trim((string) ($field['label'] ?? $field['title'] ?? $key)),
                'type' => $type,
                'required' => (bool) ($field['required'] ?? false),
                'binding' => trim((string) ($field['binding'] ?? '')) !== ''
                    ? trim((string) $field['binding'])
                    : 'content.sections.{instanceKey}.' . $key,
            ];
            if (array_key_exists('default', $field)) {
                $record['default'] = $field['default'];
            }
            if (is_array($field['constraints'] ?? null)) {
                $record['constraints'] = $field['constraints'];
            }
            $assetKind = $this->assetKindForField($field, $type);
            if ($assetKind !== '') {
                $record['assetKind'] = $assetKind;
            }
            if ($type === 'icon') {
                $record['presentation'] = 'icon';
            }
            if (filter_var($field['richtext'] ?? false, FILTER_VALIDATE_BOOL)) {
                $record['richtext'] = true;
                $record['mics'] = $type === 'textarea' ? ['table', 'form'] : [];
            }
            if (array_key_exists('mics', $field) && is_array($field['mics'])) {
                $record['mics'] = array_values(array_map('strval', $field['mics']));
            }
            $normalized[] = $record;
        }

        return $this->dedupeFields($normalized);
    }

    /**
     * @param array<mixed> $repeaters
     * @return list<array<string, mixed>>
     */
    private function normalizeDeclaredRepeaters(array $repeaters): array
    {
        $normalized = [];
        foreach ($repeaters as $repeater) {
            if (!is_array($repeater)) {
                continue;
            }
            $key = trim((string) ($repeater['key'] ?? $repeater['name'] ?? ''));
            if ($key === '') {
                continue;
            }
            $normalized[] = [
                'key' => $key,
                'label' => trim((string) ($repeater['label'] ?? $repeater['title'] ?? $key)),
                'itemLabel' => trim((string) ($repeater['itemLabel'] ?? 'Item')),
                'type' => 'repeater',
                'minItems' => max(0, (int) ($repeater['minItems'] ?? 0)),
                'maxItems' => max(0, (int) ($repeater['maxItems'] ?? 50)),
                'binding' => trim((string) ($repeater['binding'] ?? '')) !== ''
                    ? trim((string) $repeater['binding'])
                    : 'content.sections.{instanceKey}.' . $key,
                'fields' => $this->normalizeDeclaredFields(is_array($repeater['fields'] ?? null) ? $repeater['fields'] : []),
                'items' => array_values(array_filter(is_array($repeater['items'] ?? null) ? $repeater['items'] : [], 'is_array')),
            ];
        }

        return $normalized;
    }

    private function normalizeFieldType(string $type): string
    {
        $normalized = strtolower(trim($type));
        return match ($normalized) {
            'asset:icon', 'file:icon', 'media:icon' => 'icon',
            'asset:image', 'file:image', 'media:image' => 'image',
            'asset:video', 'file:video', 'media:video' => 'video',
            'asset:audio', 'file:audio', 'media:audio' => 'audio',
            'asset:document', 'file:document', 'media:document' => 'document',
            'text', 'textarea', 'url', 'icon', 'image', 'video', 'audio', 'document', 'number', 'boolean', 'repeater' => $normalized,
            default => 'text',
        };
    }

    /**
     * @param array<string, mixed> $field
     * @return array<string, mixed>
     */
    private function withInferredRichtext(array $field): array
    {
        if (array_key_exists('richtext', $field)) {
            $field['richtext'] = filter_var($field['richtext'], FILTER_VALIDATE_BOOL);
            if ($field['richtext'] === true && !array_key_exists('mics', $field)) {
                $field['mics'] = strtolower(trim((string) ($field['type'] ?? 'text'))) === 'textarea' ? ['table', 'form'] : [];
            }
            return $field;
        }

        $type = strtolower(trim((string) ($field['type'] ?? 'text')));
        $key = strtolower(trim((string) ($field['key'] ?? '')));
        if ($type === 'textarea' && preg_match('/(?:body|intro|description|summary|caption|quote|bio|copy|supporting|disclosure|content)/', $key) === 1) {
            $field['richtext'] = true;
            $field['mics'] = ['table', 'form'];
        } elseif ($type === 'text' && preg_match('/(?:eyebrow|headline|title|subtitle|tagline|ctalabel|cta_label|label|subtext|message|success)/', $key) === 1) {
            $field['richtext'] = true;
            $field['mics'] = [];
        }

        return $field;
    }

    /**
     * @param array<string, mixed> $field
     */
    private function assetKindForField(array $field, string $type): string
    {
        $assetKind = strtolower(trim((string) ($field['assetKind'] ?? $field['asset_class'] ?? $field['mediaClass'] ?? '')));
        if ($assetKind === '' && in_array($type, ['icon', 'image', 'video', 'audio', 'document'], true)) {
            $assetKind = $type === 'icon' ? 'image' : $type;
        }
        $assetKind = rtrim($assetKind, 's');

        return in_array($assetKind, ['image', 'video', 'audio', 'document'], true) ? $assetKind : '';
    }
}
