<?php

declare(strict_types=1);

final class MyronSiteSettingsRepository
{
    public function __construct(
        private readonly PDO $pdo,
        private readonly MyronFileRepository $files,
    ) {
    }

    public function getSettings(): array
    {
        $statement = $this->pdo->query('SELECT * FROM site_settings WHERE id = 1 LIMIT 1');
        $settings = $statement ? $statement->fetch() : false;

        if (!is_array($settings)) {
            return $this->defaultSettings();
        }

        return $this->decorateSettings($settings);
    }

    /**
     * @param array<string, mixed> $input
     * @param array<string, mixed>|null $faviconUpload
     */
    public function saveSettings(array $input, ?array $faviconUpload, string $updatedByUserId): array
    {
        $current = $this->getSettings();
        $siteTitle = trim((string) ($input['site_title'] ?? ''));
        if ($siteTitle === '') {
            throw new InvalidArgumentException('Site Title is required.');
        }

        $siteLanguage = trim((string) ($input['site_language'] ?? 'en-US'));
        if (!array_key_exists($siteLanguage, myronSiteLanguageOptions())) {
            throw new InvalidArgumentException('Choose a supported site language.');
        }

        $timezone = trim((string) ($input['timezone'] ?? 'UTC'));
        if (!in_array($timezone, timezone_identifiers_list(), true)) {
            throw new InvalidArgumentException('Choose a valid timezone.');
        }

        $dateFormat = trim((string) ($input['date_format'] ?? 'F j, Y'));
        if (!array_key_exists($dateFormat, myronSiteDateFormatOptions())) {
            throw new InvalidArgumentException('Choose a supported date format.');
        }

        $timeFormat = trim((string) ($input['time_format'] ?? 'g:i a'));
        if (!array_key_exists($timeFormat, myronSiteTimeFormatOptions())) {
            throw new InvalidArgumentException('Choose a supported time format.');
        }

        $faviconFileId = trim((string) ($current['favicon_file_id'] ?? ''));
        if (($input['remove_favicon'] ?? '') === '1') {
            $faviconFileId = '';
        }

        if (is_array($faviconUpload) && (int) ($faviconUpload['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_NO_FILE) {
            $uploaded = $this->files->uploadFile($faviconUpload, $updatedByUserId, $input, true);
            $mimeType = trim((string) ($uploaded['mime_type'] ?? ''));
            if (!str_starts_with($mimeType, 'image/')) {
                $this->files->deleteFile((string) ($uploaded['id'] ?? ''));
                throw new InvalidArgumentException('Site favicon must be an image file.');
            }
            $faviconFileId = (string) ($uploaded['id'] ?? '');
        }

        $siteLogoLightUrl = $this->normalizeLogoUrl((string) ($input['site_logo_light_url'] ?? ''), true);
        $siteLogoDarkUrl = $this->normalizeLogoUrl((string) ($input['site_logo_dark_url'] ?? ''), true);
        if ($siteLogoLightUrl === '' && $siteLogoDarkUrl !== '') {
            $siteLogoLightUrl = $siteLogoDarkUrl;
        }
        if ($siteLogoDarkUrl === '' && $siteLogoLightUrl !== '') {
            $siteLogoDarkUrl = $siteLogoLightUrl;
        }
        $siteLogoAlt = trim((string) ($input['site_logo_alt'] ?? ''));
        if (strlen($siteLogoAlt) > 160) {
            throw new InvalidArgumentException('Site logo alt text must be 160 characters or fewer.');
        }
        $analyticsEnabled = !empty($input['analytics_enabled']) ? 1 : 0;
        $analyticsRetentionDays = (int) ($input['analytics_retention_days'] ?? 90);
        if (!in_array($analyticsRetentionDays, [30, 90, 180, 365], true)) {
            throw new InvalidArgumentException('Choose a supported analytics retention period.');
        }
        $analyticsHeadScript = trim((string) ($input['analytics_head_script'] ?? ''));
        if (strlen($analyticsHeadScript) > 10000) {
            throw new InvalidArgumentException('Analytics head script must be 10,000 characters or fewer.');
        }
        $seo = $this->normalizeSeoSettings($input, $siteTitle, $current);
        $creativeNavStyle = $this->normalizeCreativeStyle((string) ($input['creative_nav_style'] ?? ($current['creative_nav_style'] ?? '')));
        $creativeNavParams = $this->normalizeCreativeNavParams($creativeNavStyle, $current['creative_nav_params'] ?? []);
        $creativeButtonStyle = $this->normalizeCreativeStyle((string) ($input['creative_button_style'] ?? ($current['creative_button_style'] ?? '')));
        $publicMode = $this->normalizePublicMode((string) ($input['public_mode'] ?? ($current['public_mode'] ?? 'off')));

        $statement = $this->pdo->prepare(
            'UPDATE site_settings
             SET site_title = :site_title,
                 favicon_file_id = :favicon_file_id,
                 site_logo_light_url = :site_logo_light_url,
                 site_logo_dark_url = :site_logo_dark_url,
                 site_logo_alt = :site_logo_alt,
                 site_language = :site_language,
                 timezone = :timezone,
                 date_format = :date_format,
                 time_format = :time_format,
                 analytics_enabled = :analytics_enabled,
                 analytics_retention_days = :analytics_retention_days,
                 analytics_head_script = :analytics_head_script,
                 seo_title_suffix = :seo_title_suffix,
                 seo_default_description = :seo_default_description,
                 seo_default_og_image = :seo_default_og_image,
                 seo_twitter_card_type = :seo_twitter_card_type,
                 seo_org_name = :seo_org_name,
                 seo_org_url = :seo_org_url,
                 seo_org_logo = :seo_org_logo,
                 seo_social_profiles_json = :seo_social_profiles_json,
                 seo_local_business_enabled = :seo_local_business_enabled,
                 seo_local_business_name = :seo_local_business_name,
                 seo_local_business_address = :seo_local_business_address,
                 seo_local_business_telephone = :seo_local_business_telephone,
                 seo_local_business_hours = :seo_local_business_hours,
                 seo_local_business_image = :seo_local_business_image,
                 seo_enable_canonical_output = :seo_enable_canonical_output,
                 seo_enable_json_ld_output = :seo_enable_json_ld_output,
                 seo_enable_social_output = :seo_enable_social_output,
                 seo_enable_robots_output = :seo_enable_robots_output,
                 seo_robots_txt_override = :seo_robots_txt_override,
                 creative_nav_style = :creative_nav_style,
                 creative_nav_params_json = :creative_nav_params_json,
                 creative_button_style = :creative_button_style,
                 public_mode = :public_mode,
                 updated_at = :updated_at
             WHERE id = 1'
        );
            $statement->execute([
            ':site_title' => $siteTitle,
            ':favicon_file_id' => $faviconFileId !== '' ? $faviconFileId : null,
            ':site_logo_light_url' => $siteLogoLightUrl,
            ':site_logo_dark_url' => $siteLogoDarkUrl,
            ':site_logo_alt' => $siteLogoAlt,
            ':site_language' => $siteLanguage,
            ':timezone' => $timezone,
            ':date_format' => $dateFormat,
            ':time_format' => $timeFormat,
            ':analytics_enabled' => $analyticsEnabled,
            ':analytics_retention_days' => $analyticsRetentionDays,
            ':analytics_head_script' => $analyticsHeadScript,
            ':seo_title_suffix' => $seo['seo_title_suffix'],
            ':seo_default_description' => $seo['seo_default_description'],
            ':seo_default_og_image' => $seo['seo_default_og_image'],
            ':seo_twitter_card_type' => $seo['seo_twitter_card_type'],
            ':seo_org_name' => $seo['seo_org_name'],
            ':seo_org_url' => $seo['seo_org_url'],
            ':seo_org_logo' => $seo['seo_org_logo'],
            ':seo_social_profiles_json' => json_encode($seo['seo_social_profiles'], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES),
            ':seo_local_business_enabled' => $seo['seo_local_business_enabled'] ? 1 : 0,
            ':seo_local_business_name' => $seo['seo_local_business_name'],
            ':seo_local_business_address' => $seo['seo_local_business_address'],
            ':seo_local_business_telephone' => $seo['seo_local_business_telephone'],
            ':seo_local_business_hours' => $seo['seo_local_business_hours'],
            ':seo_local_business_image' => $seo['seo_local_business_image'],
            ':seo_enable_canonical_output' => $seo['seo_enable_canonical_output'] ? 1 : 0,
            ':seo_enable_json_ld_output' => $seo['seo_enable_json_ld_output'] ? 1 : 0,
            ':seo_enable_social_output' => $seo['seo_enable_social_output'] ? 1 : 0,
            ':seo_enable_robots_output' => $seo['seo_enable_robots_output'] ? 1 : 0,
            ':seo_robots_txt_override' => $seo['seo_robots_txt_override'] !== '' ? $seo['seo_robots_txt_override'] : null,
            ':creative_nav_style' => $creativeNavStyle !== '' ? $creativeNavStyle : null,
            ':creative_nav_params_json' => $creativeNavParams !== []
                ? json_encode($creativeNavParams, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)
                : null,
            ':creative_button_style' => $creativeButtonStyle !== '' ? $creativeButtonStyle : null,
            ':public_mode' => $publicMode,
                ':updated_at' => myronNow(),
            ]);

        unset($GLOBALS['myron_site_settings_cache']);
        myronInvalidatePublicPageCache('site settings changed');

            return $this->getSettings();
        }

    /**
     * @return array{navStyle:string|null,navParams:array<string,string>,btnStyle:string|null}
     */
    public function creativeSettings(): array
    {
        $settings = $this->getSettings();
        $navStyle = $this->normalizeCreativeStyle((string) ($settings['creative_nav_style'] ?? ''));
        $buttonStyle = $this->normalizeCreativeStyle((string) ($settings['creative_button_style'] ?? ''));

        return [
            'navStyle' => $navStyle !== '' ? $navStyle : null,
            'navParams' => $this->normalizeCreativeNavParams($navStyle, $settings['creative_nav_params'] ?? []),
            'btnStyle' => $buttonStyle !== '' ? $buttonStyle : null,
        ];
    }

    /**
     * @param array<string, mixed> $navParams
     * @return array{navStyle:string|null,navParams:array<string,string>,btnStyle:string|null}
     */
    public function saveCreativeSettings(?string $navStyle, ?string $buttonStyle, array $navParams = []): array
    {
        $nav = $this->normalizeCreativeStyle((string) $navStyle);
        $button = $this->normalizeCreativeStyle((string) $buttonStyle);
        $normalizedNavParams = $this->normalizeCreativeNavParams($nav, $navParams);
        $statement = $this->pdo->prepare(
            'UPDATE site_settings
             SET creative_nav_style = :creative_nav_style,
                 creative_nav_params_json = :creative_nav_params_json,
                 creative_button_style = :creative_button_style,
                 updated_at = :updated_at
             WHERE id = 1'
        );
            $statement->execute([
                ':creative_nav_style' => $nav !== '' ? $nav : null,
                ':creative_nav_params_json' => $normalizedNavParams !== []
                    ? json_encode($normalizedNavParams, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)
                    : null,
                ':creative_button_style' => $button !== '' ? $button : null,
                ':updated_at' => myronNow(),
            ]);

        unset($GLOBALS['myron_site_settings_cache']);
        myronInvalidatePublicPageCache('site creative settings changed');

            return $this->creativeSettings();
        }

    public function resetToDefaults(): array
    {
        $statement = $this->pdo->prepare(
            "REPLACE INTO site_settings (\n"
            . "                id,\n"
            . "                site_title,\n"
            . "                favicon_file_id,\n"
            . "                site_logo_light_url,\n"
            . "                site_logo_dark_url,\n"
            . "                site_logo_alt,\n"
            . "                site_language,\n"
            . "                timezone,\n"
            . "                date_format,\n"
            . "                time_format,\n"
            . "                analytics_enabled,\n"
            . "                analytics_retention_days,\n"
            . "                analytics_head_script,\n"
            . "                updated_at\n"
            . "             ) VALUES (\n"
            . "                1,\n"
            . "                :site_title,\n"
            . "                NULL,\n"
            . "                '',\n"
            . "                '',\n"
            . "                '',\n"
            . "                :site_language,\n"
            . "                :timezone,\n"
            . "                :date_format,\n"
            . "                :time_format,\n"
            . "                1,\n"
            . "                90,\n"
            . "                '',\n"
            . "                :updated_at\n"
            . "             )"
        );
            $statement->execute([
                ':site_title' => 'MyronCMS',
                ':site_language' => 'en-US',
                ':timezone' => 'UTC',
                ':date_format' => 'F j, Y',
                ':time_format' => 'g:i a',
                ':updated_at' => myronNow(),
            ]);

        unset($GLOBALS['myron_site_settings_cache']);
        myronInvalidatePublicPageCache('site settings reset');

            return $this->getSettings();
        }

    /**
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function decorateSettings(array $row): array
    {
        $settings = [
            'site_title' => trim((string) ($row['site_title'] ?? 'MyronCMS')) ?: 'MyronCMS',
            'favicon_file_id' => trim((string) ($row['favicon_file_id'] ?? '')),
            'site_logo_light_url' => $this->normalizeLogoUrl((string) ($row['site_logo_light_url'] ?? ''), false),
            'site_logo_dark_url' => $this->normalizeLogoUrl((string) ($row['site_logo_dark_url'] ?? ''), false),
            'site_logo_alt' => trim((string) ($row['site_logo_alt'] ?? '')),
            'site_language' => trim((string) ($row['site_language'] ?? 'en-US')) ?: 'en-US',
            'timezone' => trim((string) ($row['timezone'] ?? 'UTC')) ?: 'UTC',
            'date_format' => trim((string) ($row['date_format'] ?? 'F j, Y')) ?: 'F j, Y',
            'time_format' => trim((string) ($row['time_format'] ?? 'g:i a')) ?: 'g:i a',
            'analytics_enabled' => (int) ($row['analytics_enabled'] ?? 1) === 1,
            'analytics_retention_days' => in_array((int) ($row['analytics_retention_days'] ?? 90), [30, 90, 180, 365], true)
                ? (int) ($row['analytics_retention_days'] ?? 90)
                : 90,
            'analytics_head_script' => trim((string) ($row['analytics_head_script'] ?? '')),
            'seo_title_suffix' => trim((string) (($row['seo_title_suffix'] ?? '') ?: ($row['site_title'] ?? 'MyronCMS'))),
            'seo_default_description' => trim((string) ($row['seo_default_description'] ?? '')),
            'seo_default_og_image' => trim((string) ($row['seo_default_og_image'] ?? '')),
            'seo_twitter_card_type' => in_array((string) ($row['seo_twitter_card_type'] ?? 'summary_large_image'), ['summary_large_image', 'summary'], true)
                ? (string) ($row['seo_twitter_card_type'] ?? 'summary_large_image')
                : 'summary_large_image',
            'seo_org_name' => trim((string) (($row['seo_org_name'] ?? '') ?: ($row['site_title'] ?? 'MyronCMS'))),
            'seo_org_url' => trim((string) ($row['seo_org_url'] ?? '')),
            'seo_org_logo' => trim((string) ($row['seo_org_logo'] ?? '')),
            'seo_social_profiles' => $this->decodeUrlList((string) ($row['seo_social_profiles_json'] ?? '[]')),
            'seo_local_business_enabled' => (int) ($row['seo_local_business_enabled'] ?? 0) === 1,
            'seo_local_business_name' => trim((string) ($row['seo_local_business_name'] ?? '')),
            'seo_local_business_address' => trim((string) ($row['seo_local_business_address'] ?? '')),
            'seo_local_business_telephone' => trim((string) ($row['seo_local_business_telephone'] ?? '')),
            'seo_local_business_hours' => trim((string) ($row['seo_local_business_hours'] ?? '')),
            'seo_local_business_image' => trim((string) ($row['seo_local_business_image'] ?? '')),
            'seo_enable_canonical_output' => (int) ($row['seo_enable_canonical_output'] ?? 1) === 1,
            'seo_enable_json_ld_output' => (int) ($row['seo_enable_json_ld_output'] ?? 1) === 1,
            'seo_enable_social_output' => (int) ($row['seo_enable_social_output'] ?? 1) === 1,
            'seo_enable_robots_output' => (int) ($row['seo_enable_robots_output'] ?? 1) === 1,
            'seo_robots_txt_override' => (string) ($row['seo_robots_txt_override'] ?? ''),
            'creative_nav_style' => $this->normalizeCreativeStyle((string) ($row['creative_nav_style'] ?? '')),
            'creative_nav_params' => $this->decodeCreativeNavParams($row['creative_nav_params_json'] ?? null),
            'creative_button_style' => $this->normalizeCreativeStyle((string) ($row['creative_button_style'] ?? '')),
            'public_mode' => $this->normalizePublicMode((string) ($row['public_mode'] ?? 'off')),
            'updated_at' => trim((string) ($row['updated_at'] ?? '')),
            'favicon_url' => null,
            'favicon_file' => null,
        ];

        if ($settings['favicon_file_id'] !== '') {
            $file = $this->files->getFileById($settings['favicon_file_id']);
            if (is_array($file)) {
                $settings['favicon_file'] = $file;
                $settings['favicon_url'] = myronBuildFileStreamUrl((string) $file['id']);
            }
        }

        return $settings;
    }

    /**
     * @return array<string, mixed>
     */
    private function defaultSettings(): array
    {
        return [
            'site_title' => 'MyronCMS',
            'favicon_file_id' => '',
            'site_logo_light_url' => '',
            'site_logo_dark_url' => '',
            'site_logo_alt' => '',
            'site_language' => 'en-US',
            'timezone' => 'UTC',
            'date_format' => 'F j, Y',
            'time_format' => 'g:i a',
            'analytics_enabled' => true,
            'analytics_retention_days' => 90,
            'analytics_head_script' => '',
            'seo_title_suffix' => 'MyronCMS',
            'seo_default_description' => '',
            'seo_default_og_image' => '',
            'seo_twitter_card_type' => 'summary_large_image',
            'seo_org_name' => 'MyronCMS',
            'seo_org_url' => '',
            'seo_org_logo' => '',
            'seo_social_profiles' => [],
            'seo_local_business_enabled' => false,
            'seo_local_business_name' => '',
            'seo_local_business_address' => '',
            'seo_local_business_telephone' => '',
            'seo_local_business_hours' => '',
            'seo_local_business_image' => '',
            'seo_enable_canonical_output' => true,
            'seo_enable_json_ld_output' => true,
            'seo_enable_social_output' => true,
            'seo_enable_robots_output' => true,
            'seo_robots_txt_override' => '',
            'creative_nav_style' => '',
            'creative_nav_params' => [],
            'creative_button_style' => '',
            'public_mode' => 'off',
            'updated_at' => '',
            'favicon_url' => null,
            'favicon_file' => null,
        ];
    }

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    private function normalizeSeoSettings(array $input, string $siteTitle, array $current = []): array
    {
        $twitterCard = trim((string) ($input['seo_twitter_card_type'] ?? ($current['seo_twitter_card_type'] ?? 'summary_large_image')));
        if (!in_array($twitterCard, ['summary_large_image', 'summary'], true)) {
            throw new InvalidArgumentException('Choose a supported Twitter card type.');
        }

        return [
            'seo_title_suffix' => trim((string) (($input['seo_title_suffix'] ?? ($current['seo_title_suffix'] ?? '')) ?: $siteTitle)),
            'seo_default_description' => trim((string) ($input['seo_default_description'] ?? ($current['seo_default_description'] ?? ''))),
            'seo_default_og_image' => $this->normalizeLogoUrl((string) ($input['seo_default_og_image'] ?? ($current['seo_default_og_image'] ?? '')), true),
            'seo_twitter_card_type' => $twitterCard,
            'seo_org_name' => trim((string) (($input['seo_org_name'] ?? ($current['seo_org_name'] ?? '')) ?: $siteTitle)),
            'seo_org_url' => $this->normalizeOptionalUrl((string) ($input['seo_org_url'] ?? ($current['seo_org_url'] ?? ''))),
            'seo_org_logo' => $this->normalizeLogoUrl((string) ($input['seo_org_logo'] ?? ($current['seo_org_logo'] ?? '')), true),
            'seo_social_profiles' => $this->normalizeUrlList($input['seo_social_profiles'] ?? ($current['seo_social_profiles'] ?? [])),
            'seo_local_business_enabled' => array_key_exists('seo_local_business_enabled', $input) ? !empty($input['seo_local_business_enabled']) : !empty($current['seo_local_business_enabled']),
            'seo_local_business_name' => trim((string) ($input['seo_local_business_name'] ?? ($current['seo_local_business_name'] ?? ''))),
            'seo_local_business_address' => trim((string) ($input['seo_local_business_address'] ?? ($current['seo_local_business_address'] ?? ''))),
            'seo_local_business_telephone' => trim((string) ($input['seo_local_business_telephone'] ?? ($current['seo_local_business_telephone'] ?? ''))),
            'seo_local_business_hours' => trim((string) ($input['seo_local_business_hours'] ?? ($current['seo_local_business_hours'] ?? ''))),
            'seo_local_business_image' => $this->normalizeLogoUrl((string) ($input['seo_local_business_image'] ?? ($current['seo_local_business_image'] ?? '')), true),
            'seo_enable_canonical_output' => array_key_exists('seo_enable_canonical_output', $input) ? !empty($input['seo_enable_canonical_output']) : (bool) ($current['seo_enable_canonical_output'] ?? true),
            'seo_enable_json_ld_output' => array_key_exists('seo_enable_json_ld_output', $input) ? !empty($input['seo_enable_json_ld_output']) : (bool) ($current['seo_enable_json_ld_output'] ?? true),
            'seo_enable_social_output' => array_key_exists('seo_enable_social_output', $input) ? !empty($input['seo_enable_social_output']) : (bool) ($current['seo_enable_social_output'] ?? true),
            'seo_enable_robots_output' => array_key_exists('seo_enable_robots_output', $input) ? !empty($input['seo_enable_robots_output']) : (bool) ($current['seo_enable_robots_output'] ?? true),
            'seo_robots_txt_override' => trim((string) ($input['seo_robots_txt_override'] ?? ($current['seo_robots_txt_override'] ?? ''))),
        ];
    }

    /**
     * @return list<string>
     */
    private function normalizeUrlList(mixed $value): array
    {
        $items = is_array($value) ? $value : preg_split('/\R+/', (string) $value);
        $urls = [];
        foreach ($items ?: [] as $item) {
            $url = $this->normalizeOptionalUrl((string) $item);
            if ($url !== '') {
                $urls[$url] = true;
            }
        }
        return array_keys($urls);
    }

    /**
     * @return list<string>
     */
    private function decodeUrlList(string $json): array
    {
        $decoded = json_decode($json, true);
        return is_array($decoded) ? array_values(array_filter(array_map('strval', $decoded))) : [];
    }

    private function normalizeOptionalUrl(string $value): string
    {
        $value = trim($value);
        if ($value === '') {
            return '';
        }
        if (!filter_var($value, FILTER_VALIDATE_URL)) {
            throw new InvalidArgumentException('SEO URL fields must contain valid URLs.');
        }
        return $value;
    }

    private function normalizeLogoUrl(string $value, bool $strict): string
    {
        $value = trim($value);
        if ($value === '') {
            return '';
        }
        if (strlen($value) > 500) {
            if (!$strict) {
                return '';
            }
            throw new InvalidArgumentException('Site logo URL must be 500 characters or fewer.');
        }
        if (str_starts_with($value, '/') && !str_starts_with($value, '//')) {
            return $value;
        }

        $parts = parse_url($value);
        if (!is_array($parts)) {
            if (!$strict) {
                return '';
            }
            throw new InvalidArgumentException('Site logo URL must be a site-relative path or http(s) image URL.');
        }

        $scheme = strtolower((string) ($parts['scheme'] ?? ''));
        if (!in_array($scheme, ['http', 'https'], true) || trim((string) ($parts['host'] ?? '')) === '') {
            if (!$strict) {
                return '';
            }
            throw new InvalidArgumentException('Site logo URL must be a site-relative path or http(s) image URL.');
        }

        return $value;
    }

    private function normalizeCreativeStyle(string $value): string
    {
        if (trim($value) === '') {
            return '';
        }
        $value = myronNormalizeSlug($value);
        return preg_match('/^[a-z0-9][a-z0-9-]{0,63}$/', $value) === 1 ? $value : '';
    }

    /**
     * @return array<string, mixed>
     */
    private function decodeCreativeNavParams(mixed $value): array
    {
        if (is_array($value)) {
            return $value;
        }
        if (!is_string($value) || trim($value) === '') {
            return [];
        }

        $decoded = json_decode($value, true);
        return is_array($decoded) ? $decoded : [];
    }

    /**
     * @param array<string, mixed>|mixed $params
     * @return array<string, string>
     */
    private function normalizeCreativeNavParams(string $navStyle, mixed $params): array
    {
        if ($navStyle !== 'floating-nav') {
            return [];
        }

        $params = $this->decodeCreativeNavParams($params);
        $radius = myronNormalizeSlug((string) ($params['navRadius'] ?? 'pill'));
        if (!in_array($radius, ['pill', 'lg', 'md', 'sm', 'none'], true)) {
            $radius = 'pill';
        }

        return ['navRadius' => $radius];
    }

    private function normalizePublicMode(string $value): string
    {
        $value = strtolower(trim($value));
        return in_array($value, ['off', 'coming_soon', 'maintenance'], true) ? $value : 'off';
    }
}
