<?php

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound -- APD is the established namespace shared with the Pro add-on and retained for backward compatibility.
namespace APD\Runtime;

use APD\Admin\AdminSettings;
use APD\Domain\Config\DefaultConfigFactory;
use APD\I18n\JsTranslationManager;
use APD\Support\AssetCatalog;
use APD\Support\CommercialRuntimeConfig;
use APD\Support\Nonces;
use APD\Support\RuntimeDataSchema;
use APD\Theme\DesignerTheme;

defined('ABSPATH') || exit;

/**
 * Builds the final AsukaDesigner runtime payload from normalized WordPress data.
 *
 * WordPress keeps its admin/catalog structures focused on editorial UX. This
 * class is the single PHP boundary that composes the type-specific mappers and
 * emits the JavaScript shape consumed by createAsukaDesigner().
 */
class RuntimeConfigBuilder
{
    private RuntimeCatalogMapper $catalogMapper;
    private RuntimeColorMapper $colorMapper;
    private RuntimeFontMapper $fontMapper;
    private RuntimePrintingMethodMapper $printingMethodMapper;
    private RuntimePricingMapper $pricingMapper;
    private RuntimeBootPayloadResolver $bootPayloadResolver;

    public function __construct(
        ?RuntimeCatalogMapper $catalogMapper = null,
        ?RuntimeColorMapper $colorMapper = null,
        ?RuntimeFontMapper $fontMapper = null,
        ?RuntimePrintingMethodMapper $printingMethodMapper = null,
        ?RuntimePricingMapper $pricingMapper = null,
        ?RuntimeBootPayloadResolver $bootPayloadResolver = null
    ) {
        $this->catalogMapper = $catalogMapper ?? new RuntimeCatalogMapper();
        $this->colorMapper = $colorMapper ?? new RuntimeColorMapper();
        $this->fontMapper = $fontMapper ?? new RuntimeFontMapper();
        $this->printingMethodMapper = $printingMethodMapper ?? new RuntimePrintingMethodMapper($this->colorMapper);
        $this->pricingMapper = $pricingMapper ?? new RuntimePricingMapper();
        $this->bootPayloadResolver = $bootPayloadResolver ?? new RuntimeBootPayloadResolver();
    }

    public function build(array $normalized, array $rawConfig = [], array $context = []) : array
    {
        $defaults = (new DefaultConfigFactory())->make();
        $assetCatalog = new AssetCatalog();
        $assetFilters = is_array($normalized['assetFilters'] ?? null) ? $normalized['assetFilters'] : [];
        $catalogPayload = $assetCatalog->buildPayload($assetFilters);
        $settings = (new AdminSettings())->all();
        $commercialRuntime = (new CommercialRuntimeConfig())->build($settings);

        $fontItems = $this->catalogMapper->mergeById((array) ($catalogPayload['fonts'] ?? []), (array) ($normalized['fonts'] ?? []));
        $fonts = $this->fontMapper->buildRuntimeConfig($fontItems);

        $colorItems = $this->catalogMapper->mergeById((array) ($catalogPayload['colors'] ?? []), (array) ($normalized['colors'] ?? []));
        $colorsConfig = $this->colorMapper->buildRuntimeConfig($colorItems, $normalized['colorsConfig'] ?? []);
        $colors = $colorsConfig['items'];

        $printingMethods = $this->catalogMapper->mergeById((array) ($catalogPayload['printingMethods'] ?? []), (array) ($normalized['printingMethods'] ?? []));
        $printingMethods = $this->printingMethodMapper->hydrateColors($printingMethods, $colors);
        unset($normalized['colorsConfig']);

        $catalog = $this->catalogMapper->buildCatalog($catalogPayload, $normalized, $colors, $printingMethods);
        $moduleConfig = $this->catalogMapper->mergeModuleConfigCatalog((array) ($normalized['moduleConfig'] ?? []), $catalog, $assetFilters);
        $uploadConfig = $this->mergeUploadConfiguration($normalized['upload'] ?? []);
        $moduleConfig = $this->mergeUploadIntoModuleConfig($moduleConfig, $uploadConfig);
        $initialDocument = $this->stripRuntimeConfigBucketsFromBootPayload($normalized['initialDocument'] ?? null);
        $initialState = $this->stripRuntimeConfigBucketsFromBootPayload($normalized['initialState'] ?? null);
        $initialState = $this->catalogMapper->mergeInitialStateCatalog($initialState, $catalog, $assetFilters);
        $runtimeElements = $this->catalogMapper->extractRuntimeElementDefinitions($initialState, $initialDocument);
        $bootPayload = $this->bootPayloadResolver->resolve($initialDocument, $initialState);
        $pricing = $this->pricingMapper->mergePricingConfiguration(
            $normalized['pricing'] ?? [],
            $assetCatalog->getSelectedPricingRules((string) ($rawConfig['_apd_pricing_rule_ids'] ?? ''))
        );
        $normalized['permissions'] = $this->normalizeLayoutPermissions((array) ($normalized['permissions'] ?? []));
        $normalized['layout'] = $this->normalizeRuntimeLayoutModuleIds((array) ($normalized['layout'] ?? []));
        $price = $this->pricingMapper->extractBasePrice($pricing);
        $priceFormat = $this->pricingMapper->normalizePriceFormatConfig($pricing['priceFormat'] ?? []);

        $runtime = array_replace_recursive($defaults, $normalized);
        $runtime['theme'] = (new DesignerTheme())->resolveRuntimeTheme(
            is_array($normalized['theme'] ?? null) ? $normalized['theme'] : []
        );

        $runtime['runtimePayloadVersion'] = RuntimeDataSchema::VERSION;
        $runtime['mode'] = $context['mode'] ?? 'standalone';

        // These buckets contain filtered/authoritative runtime lists. They must be
        // assigned directly instead of merged recursively: array_replace_recursive()
        // keeps numeric-array entries when the replacement array is empty, which can
        // resurrect hidden catalog items from the saved product/template state.
        $runtime['fonts'] = $fonts;
        $runtime['colors'] = $colorsConfig;
        $runtime['printingMethods'] = $printingMethods;
        $runtime['catalog'] = $catalog;
        if ($runtimeElements !== []) {
            $runtime['runtimeElements'] = $runtimeElements;
        } else {
            unset($runtime['runtimeElements']);
        }
        $runtime['moduleConfig'] = $moduleConfig;
        $runtime['initialDocument'] = $bootPayload['initialDocument'];
        $runtime['initialState'] = $bootPayload['initialState'];
        $runtime['pricing'] = $pricing;
        $runtime['price'] = $price;
        $runtime['pricingRules'] = $pricing['pricingRules'] ?? [];
        $runtime['priceFormat'] = $priceFormat;
        $runtime['upload'] = $uploadConfig;
        $runtime['i18n'] = (new JsTranslationManager())->buildRuntimeConfig(determine_locale(), is_admin());
        $runtime['integrations'] = $this->buildPublicIntegrations($settings);
        $runtime['license'] = $commercialRuntime['license'] ?? [];
        $runtime['ai'] = $commercialRuntime['ai'] ?? [];
        $runtime['exports'] = $commercialRuntime['exports'] ?? [];

        $runtimeContext = is_array($runtime['context'] ?? null) ? $runtime['context'] : [];
        $runtime['context'] = array_replace_recursive($runtimeContext, [
            'configId' => $context['configId'] ?? ($normalized['ID'] ?? ($normalized['context']['configId'] ?? null)),
            'productId' => $context['productId'] ?? null,
            'variationId' => $context['variationId'] ?? null,
            'orderId' => null,
            'userDesignId' => null,
            'isWooCommerce' => class_exists('WooCommerce'),
            'isAdmin' => is_admin(),
            'apdRuntimeConfigBuilder' => 'php',
            'apdRuntimeConfigVersion' => RuntimeDataSchema::VERSION,
        ]);

        $runtimeLayout = is_array($runtime['layout'] ?? null) ? $runtime['layout'] : [];
        $runtime['layout'] = array_replace_recursive($runtimeLayout, [
            'height' => $context['height'] ?? ($settings['default_height'] ?? '720px'),
        ]);

        return $runtime;
    }

    private function stripRuntimeConfigBucketsFromBootPayload(mixed $payload) : mixed
    {
        if (! is_array($payload)) {
            return $payload;
        }

        $clean = $payload;
        foreach ([
            'layout',
            'permissions',
            'permissionsByView',
            'moduleConfig',
            'modules',
            'colors',
            'fonts',
            'printingMethods',
            'pricing',
            'price',
            'pricingRules',
            'priceFormat',
            'upload',
            'license',
            'ai',
            'exports',
            'integrations',
            'listeners',
            'theme',
            'pdf',
            'defaults',
            'tools',
            'configuration',
            'appearance',
            'ui',
        ] as $key) {
            unset($clean[$key]);
        }

        if (isset($clean['catalog']) && is_array($clean['catalog'])) {
            $catalog = [];
            if (isset($clean['catalog']['elements']) && is_array($clean['catalog']['elements']) && $clean['catalog']['elements'] !== []) {
                $catalog['elements'] = $clean['catalog']['elements'];
            }
            if ($catalog !== []) {
                $clean['catalog'] = $catalog;
            } else {
                unset($clean['catalog']);
            }
        }

        if (isset($clean['scope']) && is_array($clean['scope'])) {
            $clean['scope'] = $this->stripRuntimeConfigScope($clean['scope']);
            if ($clean['scope'] === []) {
                unset($clean['scope']);
            }
        }

        foreach (['document', 'initialDocument', 'documentState', 'state'] as $key) {
            if (isset($clean[$key]) && is_array($clean[$key])) {
                $clean[$key] = $this->stripRuntimeConfigBucketsFromBootPayload($clean[$key]);
            }
        }

        if (isset($clean['legacy']) && is_array($clean['legacy'])) {
            $legacy = $clean['legacy'];
            if (isset($legacy['state']) && is_array($legacy['state'])) {
                $legacy['state'] = $this->stripRuntimeConfigBucketsFromBootPayload($legacy['state']);
            }
            if (isset($legacy['scope']) && is_array($legacy['scope'])) {
                $legacy['scope'] = $this->stripRuntimeConfigScope($legacy['scope']);
                if ($legacy['scope'] === []) {
                    unset($legacy['scope']);
                }
            }
            $clean['legacy'] = $legacy;
        }

        return $clean;
    }

    private function stripRuntimeConfigScope(array $scope) : array
    {
        foreach (['layout', 'permissions', 'moduleConfig', 'modules', 'catalog', 'pricing', 'tools', 'configuration', 'appearance', 'ui'] as $key) {
            unset($scope[$key]);
        }

        return $scope;
    }

    private function mergeUploadConfiguration(mixed $upload) : array
    {
        $source = is_admin() ? 'admin' : 'frontend';
        $payload = [
            'strategy' => 'remote',
            'endpoint' => add_query_arg('apd_source', $source, rest_url('asuka-designer/v1/upload')),
            'method' => 'POST',
            'fieldName' => 'file',
            'source' => $source,
            'credentials' => 'same-origin',
            'headers' => [],
        ];

        if (is_array($upload)) {
            $payload = array_replace_recursive($payload, $upload);
        }

        if (! isset($payload['headers']) || ! is_array($payload['headers'])) {
            $payload['headers'] = [];
        }

        if (! isset($payload['fieldName']) || trim((string) $payload['fieldName']) === '') {
            $payload['fieldName'] = 'file';
        }

        if (($payload['strategy'] ?? 'remote') === 'remote' || ! empty($payload['endpoint'])) {
            $payload['strategy'] = 'remote';
            $payload['headers']['X-WP-Nonce'] = Nonces::runtime();
        }

        return $payload;
    }

    private function mergeUploadIntoModuleConfig(array $moduleConfig, array $uploadConfig) : array
    {
        $existingUpload = isset($moduleConfig['upload']) && is_array($moduleConfig['upload']) ? $moduleConfig['upload'] : [];
        $moduleConfig['upload'] = array_replace_recursive($existingUpload, $uploadConfig);

        return $moduleConfig;
    }

    private function moduleAliasGroups() : array
    {
        return [
            ['views'],
            ['designs'],
            ['text'],
            ['image', 'images'],
            ['image-upload', 'uploadImage', 'upload-image'],
            ['image-qrcode', 'qrcodeImage', 'qrcode-image'],
            ['image-ai-text-to-image', 'aiTextToImage', 'ai-text-to-image'],
            ['image-facebook', 'facebookImage', 'facebook-image'],
            ['image-pixabay', 'pixabayImage', 'pixabay-image'],
            ['image-pexels', 'pexelsImage', 'pexels-image'],
            ['shapes'],
            ['layers'],
            ['colors'],
            ['zoom'],
            ['magnifier'],
            ['download'],
            ['history'],
            ['state'],
            ['pricing'],
            ['pricingRules', 'pricing-rules'],
            ['elements'],
            ['element-variation-picker', 'elementVariationPicker', 'variations'],
            ['drawingAreas', 'drawing-areas'],
            ['marker', 'fixedSlots', 'fixed-slots'],
            ['element-editor', 'elementEditor'],
            ['product-parameters', 'productParameters'],
            ['assembly'],
            ['camera'],
            ['modelOptimizer', 'model-optimizer'],
            ['license'],
            ['studio'],
            ['ruler'],
            ['font-family'],
            ['text-formatting'],
            ['text-curve'],
            ['transform'],
        ];
    }

    private function normalizeRuntimeLayoutModuleIds(array $layout) : array
    {
        foreach (['left', 'right', 'top', 'bottom'] as $zone) {
            if (! isset($layout[$zone]['items']) || ! is_array($layout[$zone]['items'])) {
                continue;
            }
            foreach ($layout[$zone]['items'] as $index => $item) {
                if (! is_array($item)) {
                    continue;
                }
                $rawKind = (string) ($item['kind'] ?? $item['id'] ?? '');
                $kind = $this->normalizeRuntimeModuleId($rawKind);
                if ($kind === 'materials') {
                    unset($layout[$zone]['items'][$index]);
                    continue;
                }
                if ($kind === '') {
                    continue;
                }
                $item['kind'] = $kind;
                if (! isset($item['id']) || sanitize_key((string) $item['id']) === sanitize_key($rawKind)) {
                    $item['id'] = $kind;
                }
                $layout[$zone]['items'][$index] = $item;
            }
            $layout[$zone]['items'] = array_values($layout[$zone]['items']);
        }

        if (isset($layout['selectionMenu']['items']) && is_array($layout['selectionMenu']['items'])) {
            $layout['selectionMenu']['items'] = array_values(array_filter(array_map(
                fn (mixed $moduleId) : string => $this->normalizeRuntimeModuleId((string) $moduleId),
                $layout['selectionMenu']['items'],
            ), static fn (string $moduleId) : bool => $moduleId !== ''));
        }

        return $layout;
    }

    private function normalizeRuntimeModuleId(string $id) : string
    {
        $key = sanitize_key($id);
        return match ($key) {
            'images' => 'image',
            'fixedslots', 'fixed-slots' => 'marker',
            'variations', 'elementvariationpicker' => 'element-variation-picker',
            'drawingareas', 'drawing-areas' => 'drawingAreas',
            'pricingrules', 'pricing-rules' => 'pricingRules',
            'modeloptimizer', 'model-optimizer' => 'modelOptimizer',
            'elementeditor', 'element-editor' => 'element-editor',
            'productparameters', 'product-parameters' => 'product-parameters',
            'uploadimage', 'upload-image', 'image-upload' => 'image-upload',
            'qrcodeimage', 'qrcode-image', 'image-qrcode' => 'image-qrcode',
            'aitexttoimage', 'ai-text-to-image', 'image-ai-text-to-image' => 'image-ai-text-to-image',
            'pixabayimage', 'pixabay-image', 'image-pixabay' => 'image-pixabay',
            'pexelsimage', 'pexels-image', 'image-pexels' => 'image-pexels',
            'facebookimage', 'facebook-image', 'image-facebook' => 'image-facebook',
            default => $id,
        };
    }

    private function normalizeModuleOpenAliases(array $moduleOpen) : array
    {
        foreach ($this->moduleAliasGroups() as $aliases) {
            $hasExplicitFalse = false;
            $hasExplicitTrue = false;
            foreach ($aliases as $alias) {
                if (! array_key_exists($alias, $moduleOpen)) {
                    continue;
                }
                if ($moduleOpen[$alias] === false) {
                    $hasExplicitFalse = true;
                    break;
                }
                if ($moduleOpen[$alias] === true) {
                    $hasExplicitTrue = true;
                }
            }
            if (! $hasExplicitFalse && ! $hasExplicitTrue) {
                continue;
            }
            $resolved = ! $hasExplicitFalse;
            foreach ($aliases as $alias) {
                $moduleOpen[$alias] = $resolved;
            }
        }

        return array_filter($moduleOpen, static fn ($value) => $value === true || $value === false);
    }

    private function hasDisabledModuleAlias(array $moduleOpen, array $aliases) : bool
    {
        foreach ($aliases as $alias) {
            if (array_key_exists($alias, $moduleOpen) && $moduleOpen[$alias] === false) {
                return true;
            }
        }
        return false;
    }

    private function setModuleAliases(array &$moduleOpen, array $aliases, bool $enabled) : void
    {
        foreach ($aliases as $alias) {
            $moduleOpen[$alias] = $enabled;
        }
    }

    private function setPermissionsFalse(array &$permissions, array $keys) : void
    {
        foreach ($keys as $key) {
            $permissions[$key] = false;
        }
    }

    private function normalizeLayoutPermissions(array $permissions) : array
    {
        $hasMovePanels = array_key_exists('canMovePanels', $permissions);
        $hasCollapsePanels = array_key_exists('canCollapsePanels', $permissions);

        if (! $hasCollapsePanels) {
            // Older product configs saved canMovePanels=false because collapse and
            // drag/reorder used to be coupled. The current runtime separates them,
            // so legacy payloads should get the new customer-friendly default.
            $permissions['canCollapsePanels'] = true;
            if (! $hasMovePanels || $permissions['canMovePanels'] === false) {
                $permissions['canMovePanels'] = true;
            }
        }

        $moduleOpen = is_array($permissions['moduleOpen'] ?? null) ? (array) $permissions['moduleOpen'] : [];
        $moduleOpen = $this->normalizeModuleOpenAliases($moduleOpen);
        unset($moduleOpen['materials']);

        if (($permissions['canOpenModule'] ?? null) === false) {
            foreach ($this->moduleAliasGroups() as $aliases) {
                $this->setModuleAliases($moduleOpen, $aliases, false);
            }
        }

        if ($this->hasDisabledModuleAlias($moduleOpen, ['views'])) {
            $permissions['canViewViews'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['designs'])) {
            $permissions['canViewDesigns'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['text'])) {
            $this->setPermissionsFalse($permissions, ['canAddText', 'canEditText']);
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['image', 'images'])) {
            $permissions['canOpenImages'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['shapes'])) {
            $this->setPermissionsFalse($permissions, ['canAddRect', 'canAddShape', 'canDrawShape']);
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['layers'])) {
            $permissions['canViewLayers'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['colors'])) {
            $permissions['canUseColors'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['zoom'])) {
            $permissions['canUseZoom'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['magnifier'])) {
            $permissions['canUseMagnifier'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['download'])) {
            $permissions['canDownload'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['history'])) {
            $this->setPermissionsFalse($permissions, ['canUndo', 'canRedo', 'canReset']);
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['state'])) {
            $permissions['canUseStateTools'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['pricing', 'pricingRules', 'pricing-rules'])) {
            $permissions['canViewPricing'] = false;
        }
        // Hiding the element catalog is independent from reading product
        // elements. The customer variation picker needs read access while the
        // Elements and Element editor administration surfaces stay hidden.
        if ($this->hasDisabledModuleAlias($moduleOpen, ['element-editor', 'elementEditor'])) {
            $this->setPermissionsFalse($permissions, ['canEditElements', 'canEditElementVariations', 'canDeleteElements']);
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['drawingAreas', 'drawing-areas'])) {
            $permissions['canViewDrawingAreas'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['marker', 'fixedSlots', 'fixed-slots'])) {
            $permissions['canViewFixedSlots'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['studio'])) {
            $permissions['canUseStudio'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['image-facebook', 'facebookImage', 'facebook-image'])) {
            $permissions['canUseFacebookImages'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['image-pixabay', 'pixabayImage', 'pixabay-image'])) {
            $permissions['canUsePixabayImages'] = false;
        }
        if ($this->hasDisabledModuleAlias($moduleOpen, ['image-pexels', 'pexelsImage', 'pexels-image'])) {
            $permissions['canUsePexelsImages'] = false;
        }

        if (($permissions['canCreateView'] ?? null) === false || ($permissions['canAddView'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canCreateView', 'canAddView']);
        }
        if (($permissions['canViewViews'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canCreateView', 'canAddView', 'canEditView', 'canDeleteView']);
            $this->setModuleAliases($moduleOpen, ['views'], false);
        }
        if (($permissions['canViewDesigns'] ?? null) === false) {
            $permissions['canAddDesigns'] = false;
            $this->setModuleAliases($moduleOpen, ['designs'], false);
        }
        if (($permissions['canOpenImages'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canAddImage', 'canUploadImage', 'canUseRemoteImages', 'canUseExternalImageLibraries', 'canUseFacebookImages', 'canUsePixabayImages', 'canUsePexelsImages']);
            $this->setModuleAliases($moduleOpen, ['image', 'images', 'image-upload', 'uploadImage', 'upload-image', 'image-qrcode', 'qrcodeImage', 'qrcode-image', 'image-ai-text-to-image', 'aiTextToImage', 'ai-text-to-image', 'image-facebook', 'facebookImage', 'facebook-image', 'image-pixabay', 'pixabayImage', 'pixabay-image', 'image-pexels', 'pexelsImage', 'pexels-image'], false);
        }
        if (($permissions['canAddImage'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canUploadImage', 'canUseRemoteImages']);
            $this->setModuleAliases($moduleOpen, ['image-upload', 'uploadImage', 'upload-image', 'image-qrcode', 'qrcodeImage', 'qrcode-image', 'image-ai-text-to-image', 'aiTextToImage', 'ai-text-to-image', 'image-facebook', 'facebookImage', 'facebook-image', 'image-pixabay', 'pixabayImage', 'pixabay-image', 'image-pexels', 'pexelsImage', 'pexels-image'], false);
        }
        if (($permissions['canUploadImage'] ?? null) === false) {
            $this->setModuleAliases($moduleOpen, ['image-upload', 'uploadImage', 'upload-image'], false);
        }
        if (($permissions['canUseExternalImageLibraries'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canUseFacebookImages', 'canUsePixabayImages', 'canUsePexelsImages']);
            $this->setModuleAliases($moduleOpen, ['image-facebook', 'facebookImage', 'facebook-image', 'image-pixabay', 'pixabayImage', 'pixabay-image', 'image-pexels', 'pexelsImage', 'pexels-image'], false);
        }
        if (($permissions['canUseFacebookImages'] ?? null) === false) {
            $this->setModuleAliases($moduleOpen, ['image-facebook', 'facebookImage', 'facebook-image'], false);
        }
        if (($permissions['canUsePixabayImages'] ?? null) === false) {
            $this->setModuleAliases($moduleOpen, ['image-pixabay', 'pixabayImage', 'pixabay-image'], false);
        }
        if (($permissions['canUsePexelsImages'] ?? null) === false) {
            $this->setModuleAliases($moduleOpen, ['image-pexels', 'pexelsImage', 'pexels-image'], false);
        }
        if (($permissions['canEditText'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canEditFontFamily', 'canEditTextFormatting', 'canEditTextCurve', 'canEditObjectText']);
        }
        $contextualModulesAllowed = ($permissions['canOpenModule'] ?? true) !== false;
        $this->setModuleAliases(
            $moduleOpen,
            ['font-family'],
            $contextualModulesAllowed && ($permissions['canEditFontFamily'] ?? true) !== false
        );
        $this->setModuleAliases(
            $moduleOpen,
            ['text-formatting'],
            $contextualModulesAllowed && ($permissions['canEditTextFormatting'] ?? true) !== false
        );
        $this->setModuleAliases(
            $moduleOpen,
            ['text-curve'],
            $contextualModulesAllowed && ($permissions['canEditTextCurve'] ?? true) !== false
        );
        $this->setModuleAliases(
            $moduleOpen,
            ['transform'],
            $contextualModulesAllowed && (
                ($permissions['canMoveObject'] ?? true) !== false
                || ($permissions['canResizeObject'] ?? true) !== false
                || ($permissions['canRotateObject'] ?? true) !== false
            )
        );
        if (($permissions['canAddShape'] ?? null) === false) {
            $permissions['canDrawShape'] = false;
        }
        if (($permissions['canViewLayers'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canReorderLayers', 'canLockLayers', 'canHideLayers', 'canSelectLayers']);
            $this->setModuleAliases($moduleOpen, ['layers'], false);
        }
        if (($permissions['canUseColors'] ?? null) === false) {
            $permissions['canEditColors'] = false;
            $this->setModuleAliases($moduleOpen, ['colors'], false);
        }
        if (($permissions['canUseZoom'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canZoomIn', 'canZoomOut', 'canFitViewport', 'canResetViewport']);
            $this->setModuleAliases($moduleOpen, ['zoom'], false);
        }
        if (($permissions['canUseMagnifier'] ?? null) === false) {
            $this->setModuleAliases($moduleOpen, ['magnifier'], false);
        }
        if (($permissions['canDownload'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canDownloadCurrentView', 'canDownloadAllViews', 'canDownloadObjects', 'canDownloadDrawingZones']);
            $this->setModuleAliases($moduleOpen, ['download'], false);
        }
        if (($permissions['canUseStateTools'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canExportJson', 'canImportJson']);
            $this->setModuleAliases($moduleOpen, ['state'], false);
        }
        if (($permissions['canUseStudio'] ?? null) === false) {
            $permissions['canApplyStudioEffects'] = false;
            $this->setModuleAliases($moduleOpen, ['studio'], false);
        }
        if (($permissions['canViewDrawingAreas'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canCreateDrawingAreas', 'canEditDrawingAreas', 'canDeleteDrawingAreas']);
            $this->setModuleAliases($moduleOpen, ['drawingAreas', 'drawing-areas'], false);
        }
        if (($permissions['canViewFixedSlots'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canCreateFixedSlots', 'canEditFixedSlots', 'canDeleteFixedSlots']);
            $this->setModuleAliases($moduleOpen, ['marker', 'fixedSlots', 'fixed-slots'], false);
        }
        if (($permissions['canViewPricing'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canEditPricingBasePrice', 'canEditPricingRules', 'canAddPricingRule', 'canRemovePricingRule']);
            $this->setModuleAliases($moduleOpen, ['pricing', 'pricingRules', 'pricing-rules'], false);
        }
        if (($permissions['canEditPricingRules'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canAddPricingRule', 'canRemovePricingRule']);
        }
        if (($permissions['canViewElements'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canAddElements', 'canEditElements', 'canEditElementVariations', 'canDeleteElements']);
            $this->setModuleAliases($moduleOpen, ['elements', 'element-editor', 'elementEditor'], false);
        }
        if (($permissions['canEditElements'] ?? null) === false) {
            $this->setPermissionsFalse($permissions, ['canEditElementVariations', 'canDeleteElements']);
            $this->setModuleAliases($moduleOpen, ['element-editor', 'elementEditor'], false);
        }

        $moduleOpen = $this->normalizeModuleOpenAliases($moduleOpen);
        if ($moduleOpen !== []) {
            $permissions['moduleOpen'] = $moduleOpen;
        } else {
            unset($permissions['moduleOpen']);
        }

        return $permissions;
    }

    private function buildPublicIntegrations(array $settings) : array
    {
        $integrations = [
            'pixabay' => ['apiKey' => (string) ($settings['pixabay_api_key'] ?? '')],
            'pexels' => ['apiKey' => (string) ($settings['pexels_api_key'] ?? '')],
            'unsplash' => ['accessKey' => (string) ($settings['unsplash_access_key'] ?? '')],
            'facebook' => ['appId' => (string) ($settings['facebook_app_id'] ?? '')],
            'googleFonts' => ['apiKey' => (string) ($settings['google_fonts_api_key'] ?? '')],
        ];

        foreach ($integrations as $providerId => $providerConfig) {
            $providerConfig = array_filter(
                $providerConfig,
                static fn($value) => is_string($value) ? trim($value) !== '' : ! empty($value)
            );

            if ($providerConfig === []) {
                unset($integrations[$providerId]);
                continue;
            }

            $integrations[$providerId] = $providerConfig;
        }

        if ($integrations !== []) {
            $integrations['assetProviders'] = $integrations;
        }

        return $integrations;
    }
}
