<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Bridge\Symfony\Bundle\Controller\Admin;

use CpCreation\VitiCore\Bridge\Symfony\Bundle\Form\Admin\PageType;
use CpCreation\VitiCore\PageBuilder\Document\BuilderDocumentManager;
use CpCreation\VitiCore\PageBuilder\Command\CompileBuilderTailwindClassesCommand;
use CpCreation\VitiCore\PageBuilder\Model\Page;
use CpCreation\VitiCore\PageBuilder\Model\PageSection;
use CpCreation\VitiCore\PageBuilder\Model\Translation\PageTranslation;
use CpCreation\VitiCore\PageBuilder\Model\Translation\PageSectionTranslation;
use CpCreation\VitiCore\PageBuilder\Repository\PageRepository;
use CpCreation\VitiCore\PageBuilder\Repository\PageSectionRepository;
use CpCreation\VitiCore\PageBuilder\Rendering\PageLayoutManager;
use CpCreation\VitiCore\PageBuilder\Rendering\PageLayoutResolver;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route('/pages')]
final class PageController extends AbstractController
{
    private const HERO_STYLE_SETTING_MAP = [
        'data-container-classes' => 'containerClasses',
        'data-eyebrow-classes' => 'eyebrowClasses',
        'data-heading-classes' => 'headingClasses',
        'data-text-classes' => 'textClasses',
        'data-buttons-classes' => 'buttonsClasses',
        'data-primary-button-classes' => 'primaryButtonClasses',
        'data-secondary-button-classes' => 'secondaryButtonClasses',
        'data-eyebrow-preset' => 'eyebrowPreset',
        'data-heading-preset' => 'headingPreset',
        'data-text-preset' => 'textPreset',
        'data-buttons-preset' => 'buttonsPreset',
        'data-primary-button-preset' => 'primaryButtonPreset',
        'data-secondary-button-preset' => 'secondaryButtonPreset',
        'data-eyebrow-style-font-family' => 'eyebrowStyleFontFamily',
        'data-eyebrow-style-font-size' => 'eyebrowStyleFontSize',
        'data-eyebrow-style-font-weight' => 'eyebrowStyleFontWeight',
        'data-eyebrow-style-font-style' => 'eyebrowStyleFontStyle',
        'data-eyebrow-style-text-align' => 'eyebrowStyleTextAlign',
        'data-eyebrow-style-text-transform' => 'eyebrowStyleTextTransform',
        'data-eyebrow-style-letter-spacing' => 'eyebrowStyleLetterSpacing',
        'data-eyebrow-style-line-height' => 'eyebrowStyleLineHeight',
        'data-eyebrow-style-color' => 'eyebrowStyleColor',
        'data-eyebrow-style-max-width' => 'eyebrowStyleMaxWidth',
        'data-eyebrow-style-margin-bottom' => 'eyebrowStyleMarginBottom',
        'data-heading-style-font-family' => 'headingStyleFontFamily',
        'data-heading-style-font-size' => 'headingStyleFontSize',
        'data-heading-style-font-weight' => 'headingStyleFontWeight',
        'data-heading-style-font-style' => 'headingStyleFontStyle',
        'data-heading-style-text-align' => 'headingStyleTextAlign',
        'data-heading-style-text-transform' => 'headingStyleTextTransform',
        'data-heading-style-letter-spacing' => 'headingStyleLetterSpacing',
        'data-heading-style-line-height' => 'headingStyleLineHeight',
        'data-heading-style-color' => 'headingStyleColor',
        'data-heading-style-max-width' => 'headingStyleMaxWidth',
        'data-heading-style-margin-bottom' => 'headingStyleMarginBottom',
        'data-text-style-font-family' => 'textStyleFontFamily',
        'data-text-style-font-size' => 'textStyleFontSize',
        'data-text-style-font-weight' => 'textStyleFontWeight',
        'data-text-style-font-style' => 'textStyleFontStyle',
        'data-text-style-text-align' => 'textStyleTextAlign',
        'data-text-style-text-transform' => 'textStyleTextTransform',
        'data-text-style-letter-spacing' => 'textStyleLetterSpacing',
        'data-text-style-line-height' => 'textStyleLineHeight',
        'data-text-style-color' => 'textStyleColor',
        'data-text-style-max-width' => 'textStyleMaxWidth',
        'data-text-style-margin-bottom' => 'textStyleMarginBottom',
        'data-primary-button-style-font-family' => 'primaryButtonStyleFontFamily',
        'data-primary-button-style-font-size' => 'primaryButtonStyleFontSize',
        'data-primary-button-style-font-weight' => 'primaryButtonStyleFontWeight',
        'data-primary-button-style-font-style' => 'primaryButtonStyleFontStyle',
        'data-primary-button-style-text-align' => 'primaryButtonStyleTextAlign',
        'data-primary-button-style-text-transform' => 'primaryButtonStyleTextTransform',
        'data-primary-button-style-letter-spacing' => 'primaryButtonStyleLetterSpacing',
        'data-primary-button-style-line-height' => 'primaryButtonStyleLineHeight',
        'data-primary-button-style-color' => 'primaryButtonStyleColor',
        'data-primary-button-style-background-color' => 'primaryButtonStyleBackgroundColor',
        'data-primary-button-style-border' => 'primaryButtonStyleBorder',
        'data-primary-button-style-padding' => 'primaryButtonStylePadding',
        'data-primary-button-style-border-radius' => 'primaryButtonStyleBorderRadius',
        'data-secondary-button-style-font-family' => 'secondaryButtonStyleFontFamily',
        'data-secondary-button-style-font-size' => 'secondaryButtonStyleFontSize',
        'data-secondary-button-style-font-weight' => 'secondaryButtonStyleFontWeight',
        'data-secondary-button-style-font-style' => 'secondaryButtonStyleFontStyle',
        'data-secondary-button-style-text-align' => 'secondaryButtonStyleTextAlign',
        'data-secondary-button-style-text-transform' => 'secondaryButtonStyleTextTransform',
        'data-secondary-button-style-letter-spacing' => 'secondaryButtonStyleLetterSpacing',
        'data-secondary-button-style-line-height' => 'secondaryButtonStyleLineHeight',
        'data-secondary-button-style-color' => 'secondaryButtonStyleColor',
        'data-secondary-button-style-background-color' => 'secondaryButtonStyleBackgroundColor',
        'data-secondary-button-style-border' => 'secondaryButtonStyleBorder',
        'data-secondary-button-style-padding' => 'secondaryButtonStylePadding',
        'data-secondary-button-style-border-radius' => 'secondaryButtonStyleBorderRadius',
        'data-container-layout-display' => 'containerLayoutDisplay',
        'data-container-layout-flex-direction' => 'containerLayoutFlexDirection',
        'data-container-layout-justify-content' => 'containerLayoutJustifyContent',
        'data-container-layout-align-items' => 'containerLayoutAlignItems',
        'data-container-layout-gap' => 'containerLayoutGap',
        'data-container-layout-flex-wrap' => 'containerLayoutFlexWrap',
        'data-buttons-layout-display' => 'buttonsLayoutDisplay',
        'data-buttons-layout-flex-direction' => 'buttonsLayoutFlexDirection',
        'data-buttons-layout-justify-content' => 'buttonsLayoutJustifyContent',
        'data-buttons-layout-align-items' => 'buttonsLayoutAlignItems',
        'data-buttons-layout-gap' => 'buttonsLayoutGap',
        'data-buttons-layout-flex-wrap' => 'buttonsLayoutFlexWrap',
        'data-eyebrow-flex-flex' => 'eyebrowFlexFlex',
        'data-eyebrow-flex-align-self' => 'eyebrowFlexAlignSelf',
        'data-eyebrow-flex-order' => 'eyebrowFlexOrder',
        'data-heading-flex-flex' => 'headingFlexFlex',
        'data-heading-flex-align-self' => 'headingFlexAlignSelf',
        'data-heading-flex-order' => 'headingFlexOrder',
        'data-text-flex-flex' => 'textFlexFlex',
        'data-text-flex-align-self' => 'textFlexAlignSelf',
        'data-text-flex-order' => 'textFlexOrder',
        'data-buttons-flex-flex' => 'buttonsFlexFlex',
        'data-buttons-flex-align-self' => 'buttonsFlexAlignSelf',
        'data-buttons-flex-order' => 'buttonsFlexOrder',
        'data-primary-button-flex-flex' => 'primaryButtonFlexFlex',
        'data-primary-button-flex-align-self' => 'primaryButtonFlexAlignSelf',
        'data-primary-button-flex-order' => 'primaryButtonFlexOrder',
        'data-secondary-button-flex-flex' => 'secondaryButtonFlexFlex',
        'data-secondary-button-flex-align-self' => 'secondaryButtonFlexAlignSelf',
        'data-secondary-button-flex-order' => 'secondaryButtonFlexOrder',
    ];

    public function __construct(
        private readonly PageRepository $pageRepository,
        private readonly PageSectionRepository $pageSectionRepository,
        private readonly PageLayoutManager $pageLayoutManager,
        private readonly BuilderDocumentManager $builderDocumentManager,
        private readonly EntityManagerInterface $entityManager,
        private readonly MessageBusInterface $messageBus,
        private readonly string $kernelEnvironment,
    ) {
    }

    #[Route('', name: 'admin_page')]
    #[IsGranted('ROLE_ADMIN')]
    public function index(): Response
    {
        return $this->render('@CpCreationVitiCore/admin/page/index.html.twig', [
            'items' => $this->pageRepository->findForAdmin(),
        ]);
    }

    #[Route('/new', name: 'admin_page_new')]
    #[IsGranted('ROLE_ADMIN')]
    public function new(Request $request): Response
    {
        $page = new Page('nouvelle-page', 'Nouvelle page');
        $form = $this->createForm(PageType::class, $page);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            /** @var Page $data */
            $data = $form->getData();
            $this->validatePage($form, $data);

            if (!$form->isValid()) {
                return $this->render('@CpCreationVitiCore/admin/page/new.html.twig', [
                    'form' => $form->createView(),
                ]);
            }

            $this->preparePage($data);
            $this->pageRepository->save($data);

            $this->addFlash('alert-success', 'La page '.$data->getName().' a été ajoutée avec succès.');

            return $this->redirectToRoute('admin_page');
        }

        return $this->render('@CpCreationVitiCore/admin/page/new.html.twig', [
            'form' => $form->createView(),
        ]);
    }

    #[Route('/{id}/edit', name: 'admin_page_edit')]
    #[IsGranted('ROLE_ADMIN')]
    public function edit(Page $page, Request $request): Response
    {
        $form = $this->createForm(PageType::class, $page);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            /** @var Page $data */
            $data = $form->getData();
            $this->validatePage($form, $data);

            if (!$form->isValid()) {
                return $this->render('@CpCreationVitiCore/admin/page/edit.html.twig', [
                    'item' => $page,
                    'form' => $form->createView(),
                ]);
            }

            $this->preparePage($data);
            $this->pageRepository->save($data);

            $this->addFlash('alert-success', 'La page '.$data->getName().' a été modifiée avec succès.');

            return $this->redirectToRoute('admin_page');
        }

        return $this->render('@CpCreationVitiCore/admin/page/edit.html.twig', [
            'item' => $page,
            'form' => $form->createView(),
        ]);
    }

    #[Route('/{id}/builder/start/{locale}', name: 'admin_page_builder_start')]
    #[IsGranted('ROLE_ADMIN')]
    public function startBuilder(Page $page, string $locale = 'fr'): RedirectResponse
    {
        return $this->redirectToRoute('cms_page_builder', [
            'id' => (string) $page->getId(),
            '_locale' => $locale,
        ]);
    }

    #[Route('/{id}/builder/data/{locale}', name: 'admin_page_builder_data', methods: ['GET'])]
    public function builderData(Page $page, Request $request, string $locale = 'fr'): JsonResponse
    {
        $this->denyUnlessPublicBuilderRequestedOrAdmin($request);

        $builderDocument = $this->builderDocumentForLocale($page, $locale);
        if ([] !== $builderDocument) {
            return $this->json([
                'blocks' => [],
                'components' => $builderDocument,
                'documentMode' => 'dom',
                'documentInspector' => $this->builderDocumentManager->inspectorSchema(),
                'pageSettings' => [
                    'headerMode' => $this->normalizeLayoutMode($page->getSetting('headerMode', PageLayoutResolver::MODE_GLOBAL)),
                    'footerMode' => $this->normalizeLayoutMode($page->getSetting('footerMode', PageLayoutResolver::MODE_GLOBAL)),
                ],
                'layoutBuilderUrls' => [
                    'header' => $this->generateUrl('cms_page_builder', [
                        'id' => (string) $this->pageLayoutManager->ensureGlobalHeaderPage()->getId(),
                        '_locale' => $locale,
                    ]),
                    'footer' => $this->generateUrl('cms_page_builder', [
                        'id' => (string) $this->pageLayoutManager->ensureGlobalFooterPage()->getId(),
                        '_locale' => $locale,
                    ]),
                ],
                'globalThemeOverrides' => $this->normalizeGlobalThemeOverrides($page->getSetting('globalThemeOverrides', [])),
                'themeSourceFiles' => $this->themeSourceFiles(),
            ]);
        }

        return $this->json([
            'blocks' => [
                [
                    'id' => 'Hero',
                    'label' => 'Hero',
                    'category' => 'Blocs du thème',
                    'content' => [
                        'type' => 'Hero',
                        'attributes' => [
                            'data-admin-label' => 'Hero',
                            'data-title' => 'Nouveau hero',
                            'data-alignment' => 'start',
                            'data-horizontal-alignment' => 'left',
                            'data-vertical-alignment' => 'bottom',
                            'data-section-max-width' => '1280',
                            'data-section-horizontal-padding' => '64',
                            'data-section-margin-bottom' => '96',
                            'data-container-padding' => '64',
                            'data-min-height' => '720',
                            'data-overlay' => 'dark',
                        ],
                    ],
                ],
            ],
            'components' => array_map(
                fn (PageSection $section): array => $this->serializeSectionForBuilder($section, $locale),
                array_values(array_filter(
                    $this->pageSectionRepository->findByPageCodeAdmin($page->getCode()),
                    static fn (PageSection $section): bool => 'hero' === $section->getType()
                ))
            ),
            'pageSettings' => [
                'headerMode' => $this->normalizeLayoutMode($page->getSetting('headerMode', PageLayoutResolver::MODE_GLOBAL)),
                'footerMode' => $this->normalizeLayoutMode($page->getSetting('footerMode', PageLayoutResolver::MODE_GLOBAL)),
            ],
            'layoutBuilderUrls' => [
                'header' => $this->generateUrl('cms_page_builder', [
                    'id' => (string) $this->pageLayoutManager->ensureGlobalHeaderPage()->getId(),
                    '_locale' => $locale,
                ]),
                'footer' => $this->generateUrl('cms_page_builder', [
                    'id' => (string) $this->pageLayoutManager->ensureGlobalFooterPage()->getId(),
                    '_locale' => $locale,
                ]),
            ],
            'globalThemeOverrides' => $this->normalizeGlobalThemeOverrides($page->getSetting('globalThemeOverrides', [])),
            'themeSourceFiles' => $this->themeSourceFiles(),
        ]);
    }

    #[Route('/{id}/builder/save/{locale}', name: 'admin_page_builder_save', methods: ['POST'])]
    public function builderSave(Page $page, Request $request, string $locale = 'fr'): JsonResponse
    {
        $this->denyUnlessPublicBuilderRequestedOrAdmin($request);

        /** @var array{components?: list<array{type?: mixed, attributes?: mixed}>, builderDocument?: mixed, documentMode?: mixed, globalThemeOverrides?: mixed, pageSettings?: mixed} $payload */
        $payload = $request->toArray();
        $components = $payload['components'] ?? [];
        $keptSectionIds = [];
        $page->setSetting('globalThemeOverrides', $this->normalizeGlobalThemeOverrides($payload['globalThemeOverrides'] ?? []));
        $pageSettings = \is_array($payload['pageSettings'] ?? null) ? $payload['pageSettings'] : [];
        $page->setSetting('headerMode', $this->normalizeLayoutMode($pageSettings['headerMode'] ?? PageLayoutResolver::MODE_GLOBAL));
        $page->setSetting('footerMode', $this->normalizeLayoutMode($pageSettings['footerMode'] ?? PageLayoutResolver::MODE_GLOBAL));

        if (($payload['documentMode'] ?? null) === 'dom' && \is_array($payload['builderDocument'] ?? null)) {
            $this->setBuilderDocumentForLocale($page, $locale, $payload['builderDocument']);
            $this->pageRepository->save($page);

            return $this->json(['saved' => true]);
        }

        foreach ($components as $position => $component) {
            if (($component['type'] ?? null) !== 'Hero') {
                continue;
            }

            $attributes = \is_array($component['attributes'] ?? null) ? $component['attributes'] : [];
            $section = $this->resolveBuilderSection($page, $attributes);

            if (!$section instanceof PageSection) {
                $section = new PageSection('hero');
                $page->addSection($section);
                $this->entityManager->persist($section);
            }

            $section->setPosition($position);
            $section->setAdminLabel($this->stringOrNull($attributes['data-admin-label'] ?? null) ?? 'Hero');
            $section->setSetting('alignment', $this->stringOrNull($attributes['data-alignment'] ?? null) ?? 'start');
            $section->setSetting('horizontalAlignment', $this->stringOrNull($attributes['data-horizontal-alignment'] ?? null) ?? 'left');
            $section->setSetting('verticalAlignment', $this->stringOrNull($attributes['data-vertical-alignment'] ?? null) ?? 'bottom');
            $section->setSetting('sectionMaxWidth', $this->stringOrNull($attributes['data-section-max-width'] ?? null) ?? '1280');
            $section->setSetting('sectionHorizontalPadding', $this->stringOrNull($attributes['data-section-horizontal-padding'] ?? null) ?? '64');
            $section->setSetting('sectionMarginBottom', $this->stringOrNull($attributes['data-section-margin-bottom'] ?? null) ?? '96');
            $section->setSetting('containerPadding', $this->stringOrNull($attributes['data-container-padding'] ?? null) ?? '64');
            $section->setSetting('minHeight', $this->stringOrNull($attributes['data-min-height'] ?? null) ?? '720');
            $section->setSetting('overlay', $this->stringOrNull($attributes['data-overlay'] ?? null) ?? 'dark');
            $section->setSetting('backgroundAsset', $this->stringOrNull($attributes['data-background-asset'] ?? null));
            $section->setSetting('backgroundImageOpacity', $this->stringOrNull($attributes['data-background-image-opacity'] ?? null));
            $section->setSetting('backgroundItems', $this->stringOrNull($attributes['data-background-items'] ?? null));
            foreach ($this->backgroundItemsSettingMap($attributes) as $attribute => $settingKey) {
                $section->setSetting($settingKey, $this->stringOrNull($attributes[$attribute] ?? null));
            }
            $section->setSetting('domMutations', $this->stringOrNull($attributes['data-dom-mutations'] ?? null));
            $this->setLocalizedSectionSetting($section, 'contentMutationsByLocale', $locale, $this->stringOrNull($attributes['data-content-mutations'] ?? null));
            foreach ($this->heroStyleSettingMap($attributes) as $attribute => $settingKey) {
                $section->setSetting($settingKey, $this->stringOrNull($attributes[$attribute] ?? null));
            }

            $translation = $section->translate($locale, false);
            $translation->setEyebrow($this->stringOrNull($attributes['data-eyebrow'] ?? null));
            $translation->setTitle($this->stringOrNull($attributes['data-title'] ?? null));
            $translation->setBody($this->stringOrNull($attributes['data-body'] ?? null));
            $translation->setPrimaryCtaLabel($this->stringOrNull($attributes['data-primary-cta-label'] ?? null));
            $translation->setPrimaryCtaUrl($this->stringOrNull($attributes['data-primary-cta-url'] ?? null));
            $translation->setSecondaryCtaLabel($this->stringOrNull($attributes['data-secondary-cta-label'] ?? null));
            $translation->setSecondaryCtaUrl($this->stringOrNull($attributes['data-secondary-cta-url'] ?? null));
            $section->mergeNewTranslations();

            $keptSectionIds[] = (string) $section->getId();
        }

        foreach ($this->pageSectionRepository->findByPageCodeAdmin($page->getCode()) as $section) {
            if ('hero' !== $section->getType()) {
                continue;
            }

            if (\in_array((string) $section->getId(), $keptSectionIds, true)) {
                continue;
            }

            $page->removeSection($section);
            $this->entityManager->remove($section);
        }

        $this->entityManager->flush();

        return $this->json(['saved' => true]);
    }

    #[Route('/{id}/builder/tailwind-classes/{locale}', name: 'admin_page_builder_tailwind_classes', methods: ['POST'])]
    public function builderTailwindClasses(Page $page, Request $request, string $locale = 'fr'): JsonResponse
    {
        $this->denyUnlessPublicBuilderRequestedOrAdmin($request);

        /** @var array{classes?: mixed, force?: mixed} $payload */
        $payload = $request->toArray();
        $classes = $this->normalizeBuilderTailwindClasses($payload['classes'] ?? []);
        $forceCompile = true === ($payload['force'] ?? false);

        if ([] === $classes) {
            return $this->json([
                'updated' => false,
                'compiled' => false,
                'message' => 'Aucune classe Tailwind à compiler.',
            ], Response::HTTP_BAD_REQUEST);
        }

        $targetFile = dirname(__DIR__, 6).'/assets/page-builder-tailwind-classes.js';
        $output = "// Generated from the classes currently present in the page builder.\n"
            ."// Do not edit manually; the builder rewrites this file when classes change.\n"
            .'export const pageBuilderTailwindClasses = '.json_encode(implode(' ', $classes), JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES).";\n";

        $classesFileNeedsUpdate = !is_file($targetFile) || file_get_contents($targetFile) !== $output;
        if (!$forceCompile && !$classesFileNeedsUpdate) {
            return $this->json([
                'updated' => false,
                'compiled' => true,
                'classes' => \count($classes),
                'compile' => ['compiled' => true, 'skipped' => true],
            ]);
        }

        if ($classesFileNeedsUpdate) {
            file_put_contents($targetFile, $output, LOCK_EX);
        }

        if ('prod' !== $this->kernelEnvironment) {
            return $this->json([
                'updated' => $classesFileNeedsUpdate,
                'compiled' => true,
                'classes' => \count($classes),
                'compile' => [
                    'compiled' => true,
                    'skipped' => true,
                    'reason' => 'Compilation runtime inutile hors prod: le watcher dev doit compiler les assets.',
                ],
            ]);
        }

        $this->messageBus->dispatch(new CompileBuilderTailwindClassesCommand());

        return $this->json([
            'updated' => $classesFileNeedsUpdate,
            'compiled' => false,
            'classes' => \count($classes),
            'compile' => [
                'compiled' => false,
                'queued' => true,
                'command' => 'node ./node_modules/.bin/encore production',
            ],
        ], Response::HTTP_ACCEPTED);
    }

    #[Route('/{id}/front-edit/{locale}', name: 'admin_page_front_edit')]
    #[IsGranted('ROLE_ADMIN')]
    public function frontEdit(Page $page, string $locale = 'fr'): RedirectResponse
    {
        return $this->redirectToRoute('cms_page_builder', [
            'id' => (string) $page->getId(),
            '_locale' => $locale,
        ]);
    }

    #[Route('/{id}/delete', name: 'admin_page_delete')]
    #[IsGranted('ROLE_ADMIN')]
    public function delete(Page $page): RedirectResponse
    {
        $name = $page->getName();
        $this->pageRepository->delete($page);

        $this->addFlash('alert-success', 'La page '.$name.' a été supprimée avec succès.');

        return $this->redirectToRoute('admin_page');
    }

    #[Route('/{id}/disable', name: 'admin_page_disable')]
    #[IsGranted('ROLE_ADMIN')]
    public function disable(Page $page): RedirectResponse
    {
        $this->pageRepository->disable($page);

        return $this->redirectToRoute('admin_page');
    }

    #[Route('/{id}/enable', name: 'admin_page_enable')]
    #[IsGranted('ROLE_ADMIN')]
    public function enable(Page $page): RedirectResponse
    {
        $this->pageRepository->enable($page);

        return $this->redirectToRoute('admin_page');
    }

    private function preparePage(Page $page): void
    {
        if (Page::ROUTE_MODE_CMS === $page->getRouteMode()) {
            $page->setRouteName(null);
        }
    }

    private function validatePage(FormInterface $form, Page $page): void
    {
        $existingPage = $this->pageRepository->findOneByCode($page->getCode());
        if (null !== $existingPage && (string) $existingPage->getId() !== (string) $page->getId()) {
            $form->get('code')->addError(new FormError('Ce code technique est déjà utilisé par une autre page.'));
        }

        if (Page::ROUTE_MODE_SYSTEM === $page->getRouteMode() && null === $page->getRouteName()) {
            $form->get('routeName')->addError(new FormError('Le nom de route Symfony est obligatoire pour une page système.'));
        }

        if (Page::ROUTE_MODE_CMS !== $page->getRouteMode()) {
            return;
        }

        foreach ($page->getTranslations() as $translation) {
            if (!$translation instanceof PageTranslation) {
                continue;
            }

            if ($this->pageRepository->hasConflictingPath($page, $translation->getLocale(), $translation->getPath())) {
                $form->addError(new FormError(sprintf(
                    'Le chemin "%s" est déjà utilisé pour la langue "%s".',
                    '' === $translation->getPath() ? 'page d’accueil' : $translation->getPath(),
                    $translation->getLocale()
                )));
            }
        }
    }

    private function findTranslation(Page $page, string $locale): ?PageTranslation
    {
        foreach ($page->getTranslations() as $translation) {
            if ($translation instanceof PageTranslation && $translation->getLocale() === $locale) {
                return $translation;
            }
        }

        foreach ($page->getTranslations() as $translation) {
            if ($translation instanceof PageTranslation) {
                return $translation;
            }
        }

        return null;
    }

    /**
     * @param array<string, scalar> $extraParameters
     */
    private function redirectToFrontPage(PageTranslation $translation, array $extraParameters = []): RedirectResponse
    {
        $routeName = '' === $translation->getPath() ? 'cms_homepage' : 'cms_page';
        $routeParameters = array_merge([
            '_locale' => $translation->getLocale(),
        ], $extraParameters);

        if ('' !== $translation->getPath()) {
            $routeParameters['path'] = $translation->getPath();
        }

        return $this->redirectToRoute($routeName, $routeParameters);
    }

    /**
     * @param array<string, mixed> $attributes
     */
    private function resolveBuilderSection(Page $page, array $attributes): ?PageSection
    {
        $sectionId = $this->stringOrNull($attributes['data-section-id'] ?? null);
        if (null === $sectionId) {
            return null;
        }

        foreach ($this->pageSectionRepository->findByPageCodeAdmin($page->getCode()) as $section) {
            if ((string) $section->getId() === $sectionId) {
                return $section;
            }
        }

        return null;
    }

    private function serializeSectionForBuilder(PageSection $section, string $locale): array
    {
        /** @var PageSectionTranslation $translation */
        $translation = $section->translate($locale, true);
        $settings = $section->getSettings();
        $sectionMaxWidth = $settings['sectionMaxWidth'] ?? '1280';
        $sectionHorizontalPadding = $settings['sectionHorizontalPadding'] ?? '64';
        $sectionMarginBottom = $settings['sectionMarginBottom'] ?? '96';

        return [
            'type' => 'Hero',
            'attributes' => [
                'data-section-id' => (string) $section->getId(),
                'data-admin-label' => $section->getAdminLabel() ?? 'Hero',
                'data-eyebrow' => $translation->getEyebrow(),
                'data-title' => $translation->getTitle(),
                'data-body' => $translation->getBody(),
                'data-primary-cta-label' => $translation->getPrimaryCtaLabel(),
                'data-primary-cta-url' => $translation->getPrimaryCtaUrl(),
                'data-secondary-cta-label' => $translation->getSecondaryCtaLabel(),
                'data-secondary-cta-url' => $translation->getSecondaryCtaUrl(),
                'data-alignment' => $settings['alignment'] ?? 'start',
                'data-horizontal-alignment' => $settings['horizontalAlignment'] ?? (($settings['alignment'] ?? 'start') === 'center' ? 'center' : 'left'),
                'data-vertical-alignment' => $settings['verticalAlignment'] ?? 'bottom',
                'data-section-max-width' => $sectionMaxWidth,
                'data-section-horizontal-padding' => $sectionHorizontalPadding,
                'data-section-margin-bottom' => $sectionMarginBottom,
                'data-container-padding' => $settings['containerPadding'] ?? '64',
                'data-min-height' => $settings['minHeight'] ?? '720',
                'data-overlay' => $settings['overlay'] ?? 'dark',
                'data-background-asset' => $settings['backgroundAsset'] ?? '',
                'data-background-image-opacity' => $settings['backgroundImageOpacity'] ?? '',
                'data-background-items' => $settings['backgroundItems'] ?? '',
                'data-dom-mutations' => $settings['domMutations'] ?? '',
                'data-content-mutations' => $this->localizedSectionSetting($settings, 'contentMutationsByLocale', $locale),
                ...$this->serializeBackgroundItemsSettings($settings),
                ...$this->serializeHeroStyleSettings($settings),
            ],
        ];
    }

    /**
     * @param array<string, mixed> $settings
     *
     * @return array<string, mixed>
     */
    private function serializeHeroStyleSettings(array $settings): array
    {
        $attributes = [];
        foreach ($this->heroStyleSettingMap($settings) as $attribute => $settingKey) {
            if (!\array_key_exists($settingKey, $settings)) {
                continue;
            }

            $attributes[$attribute] = $settings[$settingKey];
        }

        return $attributes;
    }

    /**
     * @param array<string, mixed> $settings
     *
     * @return array<string, mixed>
     */
    private function serializeBackgroundItemsSettings(array $settings): array
    {
        $attributes = [];
        foreach ($this->backgroundItemsSettingMap($settings) as $attribute => $settingKey) {
            if (!\array_key_exists($settingKey, $settings)) {
                continue;
            }

            $attributes[$attribute] = $settings[$settingKey];
        }

        return $attributes;
    }

    private function setLocalizedSectionSetting(PageSection $section, string $key, string $locale, ?string $value): void
    {
        $values = $section->getSetting($key, []);
        $values = \is_array($values) ? $values : [];

        if (null === $value) {
            unset($values[$locale]);
        } else {
            $values[$locale] = $value;
        }

        $section->setSetting($key, [] === $values ? null : $values);
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function localizedSectionSetting(array $settings, string $key, string $locale): string
    {
        $values = $settings[$key] ?? [];

        return \is_array($values) && \is_string($values[$locale] ?? null) ? $values[$locale] : '';
    }

    /**
     * @param array<string, mixed> $source
     *
     * @return array<string, string>
     */
    private function heroStyleSettingMap(array $source = []): array
    {
        $map = self::HERO_STYLE_SETTING_MAP;
        foreach ($source as $key => $_) {
            if (str_starts_with((string) $key, 'data-')) {
                $attribute = (string) $key;
                $settingKey = $this->settingKeyFromHeroAttribute($attribute);
                if (null !== $settingKey) {
                    $map[$attribute] = $settingKey;
                }

                continue;
            }

            $attribute = $this->heroAttributeFromSettingKey((string) $key);
            if (null !== $attribute) {
                $map[$attribute] = (string) $key;
            }
        }

        return $map;
    }

    /**
     * @param array<string, mixed> $source
     *
     * @return array<string, string>
     */
    private function backgroundItemsSettingMap(array $source = []): array
    {
        $map = [
            'data-background-items' => 'backgroundItems',
        ];

        foreach ($source as $key => $_) {
            $key = (string) $key;
            if (str_starts_with($key, 'data-')) {
                $settingKey = $this->settingKeyFromBackgroundItemsAttribute($key);
                if (null !== $settingKey) {
                    $map[$key] = $settingKey;
                }

                continue;
            }

            $attribute = $this->backgroundItemsAttributeFromSettingKey($key);
            if (null !== $attribute) {
                $map[$attribute] = $key;
            }
        }

        return $map;
    }

    private function settingKeyFromHeroAttribute(string $attribute): ?string
    {
        if (!preg_match('/^data-(section|container|buttons|eyebrow|heading|text|primary-button|secondary-button)-(style|layout|flex)-(.+)$/', $attribute, $matches)) {
            return null;
        }

        return $this->camelizeHeroPart($matches[1]).ucfirst($this->camelizeHeroPart($matches[2])).ucfirst($this->camelizeHeroPart($matches[3]));
    }

    private function settingKeyFromBackgroundItemsAttribute(string $attribute): ?string
    {
        if ('data-background-items' === $attribute) {
            return 'backgroundItems';
        }

        if (!preg_match('/^data-([a-z0-9-]+)-background-items$/', $attribute, $matches)) {
            return null;
        }

        return $this->camelizeHeroPart($matches[1]).'BackgroundItems';
    }

    private function heroAttributeFromSettingKey(string $settingKey): ?string
    {
        if (!preg_match('/^(section|container|buttons|eyebrow|heading|text|primaryButton|secondaryButton)(Style|Layout|Flex)(.+)$/', $settingKey, $matches)) {
            return null;
        }

        return 'data-'.$this->dasherizeHeroPart($matches[1]).'-'.strtolower($matches[2]).'-'.$this->dasherizeHeroPart($matches[3]);
    }

    private function backgroundItemsAttributeFromSettingKey(string $settingKey): ?string
    {
        if ('backgroundItems' === $settingKey) {
            return 'data-background-items';
        }

        if (!preg_match('/^([a-z][a-zA-Z0-9]*)BackgroundItems$/', $settingKey, $matches)) {
            return null;
        }

        return 'data-'.$this->dasherizeHeroPart($matches[1]).'-background-items';
    }

    private function camelizeHeroPart(string $value): string
    {
        return lcfirst(str_replace(' ', '', ucwords(str_replace('-', ' ', $value))));
    }

    private function dasherizeHeroPart(string $value): string
    {
        return strtolower((string) preg_replace('/(?<!^)[A-Z]/', '-$0', $value));
    }

    private function denyUnlessPublicBuilderRequestedOrAdmin(Request $request): void
    {
        if ($request->query->getBoolean('builder') || $this->isGranted('ROLE_ADMIN')) {
            return;
        }

        throw new AccessDeniedException();
    }

    /**
     * @return array<string, string>
     */
    private function normalizeGlobalThemeOverrides(mixed $value): array
    {
        if (!\is_array($value)) {
            return [];
        }

        $overrides = [];
        foreach ($value as $token => $tokenValue) {
            $token = trim((string) $token);
            $tokenValue = trim((string) $tokenValue);
            if (!$this->isAllowedGlobalThemeToken($token) || !$this->isAllowedCssValue($tokenValue)) {
                continue;
            }

            $overrides[$token] = $tokenValue;
        }

        ksort($overrides, SORT_STRING);

        return $overrides;
    }

    private function normalizeLayoutMode(mixed $value): string
    {
        $mode = trim((string) $value);

        return \in_array($mode, [
            PageLayoutResolver::MODE_GLOBAL,
            PageLayoutResolver::MODE_CUSTOM,
            PageLayoutResolver::MODE_NONE,
        ], true) ? $mode : PageLayoutResolver::MODE_GLOBAL;
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function builderDocumentForLocale(Page $page, string $locale): array
    {
        $documents = $page->getSetting('builderDocumentByLocale', []);
        if (!\is_array($documents)) {
            return [];
        }

        $document = $documents[$locale] ?? null;

        return \is_array($document)
            ? $this->builderDocumentManager->normalizeDocument(array_values($document))
            : [];
    }

    /**
     * @param list<array<string, mixed>> $document
     */
    private function setBuilderDocumentForLocale(Page $page, string $locale, array $document): void
    {
        $documents = $page->getSetting('builderDocumentByLocale', []);
        $documents = \is_array($documents) ? $documents : [];
        $documents[$locale] = $this->builderDocumentManager->normalizeDocument(
            array_values(array_filter($document, \is_array(...)))
        );
        $page->setSetting('builderDocumentByLocale', $documents);
    }

    /**
     * @return array<string, array{label: string, language: string, content: string}>
     */
    private function themeSourceFiles(): array
    {
        $themeDir = dirname(__DIR__, 7).'/viti-theme-bundle/themes/theme-default/assets/styles';
        $sources = [
            'base' => [
                'label' => '_base.scss',
                'language' => 'scss',
                'path' => $themeDir.'/_base.scss',
            ],
            'components' => [
                'label' => '_components.scss',
                'language' => 'scss',
                'path' => $themeDir.'/_components.scss',
            ],
        ];
        $resolved = [];

        foreach ($sources as $key => $source) {
            if (!is_file($source['path']) || !is_readable($source['path'])) {
                continue;
            }

            $content = file_get_contents($source['path']);
            if (false === $content) {
                continue;
            }

            $resolved[$key] = [
                'label' => $source['label'],
                'language' => $source['language'],
                'content' => $content,
            ];
        }

        return $resolved;
    }

    private function isAllowedGlobalThemeToken(string $token): bool
    {
        return 1 === preg_match('/^--(?:color|font|text|radius|spacing|shadow|border)-[a-z0-9-]+(?:--[a-z0-9-]+)?$/', $token);
    }

    private function isAllowedCssValue(string $value): bool
    {
        return '' !== $value && \strlen($value) <= 240 && 1 !== preg_match('/[<>{};]/', $value);
    }

    /**
     * @return list<string>
     */
    private function normalizeBuilderTailwindClasses(mixed $value): array
    {
        $rawClasses = \is_array($value) ? $value : preg_split('/\s+/', (string) $value);
        $classes = [];

        foreach ($rawClasses ?: [] as $className) {
            $className = trim((string) $className);
            if ('' === $className || \strlen($className) > 180) {
                continue;
            }

            if (!preg_match('/^[^\s<>"`\\\\;{}]+$/u', $className)) {
                continue;
            }

            $classes[$className] = true;
        }

        $classes = array_keys($classes);
        sort($classes, SORT_STRING);

        return $classes;
    }

    private function stringOrNull(mixed $value): ?string
    {
        if (null === $value) {
            return null;
        }

        $value = trim((string) $value);

        return '' === $value ? null : $value;
    }
}
