<?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\Installation;

use APD\Admin\AdminSettings;
use APD\PostTypes\ProductConfigPostType;
use APD\PostTypes\TemplatePostType;
use APD\Support\AssetCatalog;
use APD\Support\RuntimeDataSchema;
use APD\Theme\DesignerTheme;

defined('ABSPATH') || exit;

class UpgradeManager
{
    public const DATA_SCHEMA_OPTION = 'apd_data_schema_version';
    public const PLUGIN_VERSION_OPTION = 'apd_plugin_version';
    public const CLIENT_PERMISSION_DEFAULTS_OPTION = 'apd_client_permission_defaults_version';
    public const CLIENT_PERMISSION_DEFAULTS_VERSION = 1;

    public static function maybeUpgrade() : void
    {
        (new DesignerTheme())->migrateLegacyStorage();

        $currentSchema = (int) get_option(self::DATA_SCHEMA_OPTION, 0);
        if ($currentSchema < RuntimeDataSchema::VERSION) {
            self::removeDeprecatedSettings();
            self::migrateAssetCatalog();
            self::migrateConfigPosts();

            update_option(self::DATA_SCHEMA_OPTION, RuntimeDataSchema::VERSION, false);
        }

        $currentPermissionDefaults = (int) get_option(self::CLIENT_PERMISSION_DEFAULTS_OPTION, 0);
        if ($currentPermissionDefaults < self::CLIENT_PERMISSION_DEFAULTS_VERSION) {
            self::migrateLegacyClientPermissionDefaults();
            update_option(
                self::CLIENT_PERMISSION_DEFAULTS_OPTION,
                self::CLIENT_PERMISSION_DEFAULTS_VERSION,
                false
            );
        }

        $currentPluginVersion = (string) get_option(self::PLUGIN_VERSION_OPTION, '');
        if ($currentPluginVersion !== ASUKA_PRODUCT_DESIGNER_VERSION) {
            update_option(self::PLUGIN_VERSION_OPTION, ASUKA_PRODUCT_DESIGNER_VERSION, false);
        }
    }

    private static function migrateAssetCatalog() : void
    {
        $raw = get_option(AssetCatalog::OPTION, []);
        $raw = is_array($raw) ? $raw : [];
        $raw = self::prepareLegacyCatalogForCanonicalSchema($raw);
        update_option(AssetCatalog::OPTION, RuntimeDataSchema::sanitizeCatalog($raw, []), false);
    }

    private static function removeDeprecatedSettings() : void
    {
        $settings = get_option(AdminSettings::OPTION, []);
        if (! is_array($settings) || ! array_key_exists('vexels_api_key', $settings)) {
            return;
        }

        unset($settings['vexels_api_key']);
        update_option(AdminSettings::OPTION, $settings, false);
    }

    private static function migrateConfigPosts() : void
    {
        $postTypes = [ProductConfigPostType::TYPE, TemplatePostType::TYPE];
        $posts = get_posts([
            'post_type' => $postTypes,
            'post_status' => ['publish', 'draft', 'private', 'pending', 'future'],
            'posts_per_page' => -1,
            'fields' => 'ids',
            'no_found_rows' => true,
        ]);

        foreach ($posts as $postId) {
            self::migrateRuntimeMeta((int) $postId);
        }
    }

    private static function migrateLegacyClientPermissionDefaults() : void
    {
        $posts = get_posts([
            'post_type' => [ProductConfigPostType::TYPE, TemplatePostType::TYPE],
            'post_status' => ['publish', 'draft', 'private', 'pending', 'future'],
            'posts_per_page' => -1,
            'fields' => 'ids',
            'no_found_rows' => true,
        ]);

        foreach ($posts as $postId) {
            self::migrateLegacyClientPermissions((int) $postId);
        }
    }

    private static function migrateLegacyClientPermissions(int $postId) : void
    {
        $permissions = self::decodeMeta($postId, '_apd_permissions');
        if ($permissions === []) {
            return;
        }

        $moduleOpen = is_array($permissions['moduleOpen'] ?? null)
            ? (array) $permissions['moduleOpen']
            : [];

        $variationWasDisabled = self::hasModuleValue(
            $moduleOpen,
            ['element-variation-picker', 'elementVariationPicker', 'variations'],
            false
        );
        $elementEditorWasEnabled = self::hasModuleValue(
            $moduleOpen,
            ['element-editor', 'elementEditor'],
            true
        );
        $pricingWasEnabled = self::hasModuleValue($moduleOpen, ['pricing'], true);
        $pricingRulesWereEnabled = self::hasModuleValue(
            $moduleOpen,
            ['pricingRules', 'pricing-rules'],
            true
        );

        // This exact combination was emitted by the legacy customer preset.
        // It exposed configuration tools while hiding the safe variation picker.
        if (
            ! $variationWasDisabled
            || ! $elementEditorWasEnabled
            || ! $pricingWasEnabled
            || ! $pricingRulesWereEnabled
            || ($permissions['canViewPricing'] ?? null) !== true
            || ($permissions['canEditElements'] ?? null) !== true
        ) {
            return;
        }

        foreach ([
            'canUseStateTools',
            'canExportJson',
            'canImportJson',
            'canViewPricing',
            'canEditPricingBasePrice',
            'canEditPricingRules',
            'canAddPricingRule',
            'canRemovePricingRule',
            'canAddElements',
            'canEditElements',
            'canEditElementVariations',
            'canDeleteElements',
        ] as $permissionKey) {
            $permissions[$permissionKey] = false;
        }
        $permissions['canViewElements'] = true;

        self::setModuleValues($moduleOpen, ['state'], false);
        self::setModuleValues($moduleOpen, ['pricing'], false);
        self::setModuleValues($moduleOpen, ['pricingRules', 'pricing-rules'], false);
        self::setModuleValues($moduleOpen, ['elements'], false);
        self::setModuleValues($moduleOpen, ['element-editor', 'elementEditor'], false);
        self::setModuleValues(
            $moduleOpen,
            ['element-variation-picker', 'elementVariationPicker', 'variations'],
            true
        );
        $permissions['moduleOpen'] = $moduleOpen;

        update_post_meta(
            $postId,
            '_apd_permissions',
            wp_slash(self::encode($permissions))
        );
    }

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

        return false;
    }

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

    private static function migrateRuntimeMeta(int $postId) : void
    {
        $colors = self::decodeMeta($postId, '_apd_colors');
        if (is_array($colors)) {
            $colors = self::prepareLegacyProductColorsForCanonicalSchema($colors);
            update_post_meta($postId, '_apd_colors', wp_slash(self::encode(RuntimeDataSchema::sanitizeProductColorsConfig($colors))));
        }

        $fonts = self::decodeMeta($postId, '_apd_fonts');
        if (is_array($fonts)) {
            $fonts = self::prepareLegacyFontsForCanonicalSchema($fonts);
            update_post_meta($postId, '_apd_fonts', wp_slash(self::encode(RuntimeDataSchema::sanitizeProductFontsConfig($fonts))));
        }

        $pricing = self::decodeMeta($postId, '_apd_pricing_rules');
        if (is_array($pricing)) {
            $pricing = self::prepareLegacyPricingForCanonicalSchema($pricing);
            update_post_meta($postId, '_apd_pricing_rules', wp_slash(self::encode(RuntimeDataSchema::sanitizeProductPricingConfig($pricing))));
        }

        $filters = self::decodeMeta($postId, '_apd_asset_filters');
        if (is_array($filters)) {
            update_post_meta($postId, '_apd_asset_filters', wp_slash(self::encode(RuntimeDataSchema::sanitizeProductAssetFilters($filters))));
        }

        self::migrateContextualSelectionPermissions($postId);
        self::migrateLegacyImageProviderPermissions($postId);
        self::migrateLegacyStudioApplyPermission($postId);
    }

    private static function migrateContextualSelectionPermissions(int $postId) : void
    {
        $permissions = self::decodeMeta($postId, '_apd_permissions');
        if (! is_array($permissions)) {
            return;
        }

        $moduleOpen = is_array($permissions['moduleOpen'] ?? null)
            ? (array) $permissions['moduleOpen']
            : [];
        $modulesAllowed = ($permissions['canOpenModule'] ?? true) !== false;
        $textEditingAllowed = ($permissions['canEditText'] ?? true) !== false;

        $moduleOpen['font-family'] = $modulesAllowed
            && $textEditingAllowed
            && ($permissions['canEditFontFamily'] ?? true) !== false;
        $moduleOpen['text-formatting'] = $modulesAllowed
            && $textEditingAllowed
            && ($permissions['canEditTextFormatting'] ?? true) !== false;
        $moduleOpen['text-curve'] = $modulesAllowed
            && $textEditingAllowed
            && ($permissions['canEditTextCurve'] ?? true) !== false;
        $moduleOpen['transform'] = $modulesAllowed && (
            ($permissions['canMoveObject'] ?? true) !== false
            || ($permissions['canResizeObject'] ?? true) !== false
            || ($permissions['canRotateObject'] ?? true) !== false
        );
        $permissions['moduleOpen'] = $moduleOpen;

        update_post_meta(
            $postId,
            '_apd_permissions',
            wp_slash(self::encode($permissions))
        );

        $selectionMenu = self::decodeMeta($postId, '_apd_selection_menu');
        $items = array_values(array_filter(array_map(
            'sanitize_key',
            (array) ($selectionMenu['items'] ?? [])
        )));
        foreach (['font-family', 'text-formatting', 'text-curve', 'colors', 'transform'] as $moduleId) {
            if (($moduleOpen[$moduleId] ?? true) === true && ! in_array($moduleId, $items, true)) {
                $items[] = $moduleId;
            }
        }
        $selectionMenu['type'] = (string) ($selectionMenu['type'] ?? 'popover');
        $selectionMenu['items'] = $items;
        $selectionMenu['open'] = false;

        update_post_meta(
            $postId,
            '_apd_selection_menu',
            wp_slash(self::encode($selectionMenu))
        );
    }

    private static function migrateLegacyImageProviderPermissions(int $postId) : void
    {
        $permissions = self::decodeMeta($postId, '_apd_permissions');
        if (! is_array($permissions)) {
            return;
        }

        $moduleOpen = is_array($permissions['moduleOpen'] ?? null)
            ? (array) $permissions['moduleOpen']
            : [];
        $legacyProviders = [
            'canUseFacebookImages' => ['image-facebook', 'facebookImage', 'facebook-image'],
            'canUsePixabayImages' => ['image-pixabay', 'pixabayImage', 'pixabay-image'],
            'canUsePexelsImages' => ['image-pexels', 'pexelsImage', 'pexels-image'],
        ];

        $allProviderPermissionsDisabled = true;
        $allProviderModulesDisabled = true;
        $hasProviderModuleFlags = false;
        foreach ($legacyProviders as $permissionKey => $aliases) {
            $allProviderPermissionsDisabled = $allProviderPermissionsDisabled
                && ($permissions[$permissionKey] ?? null) === false;
            foreach ($aliases as $alias) {
                if (! array_key_exists($alias, $moduleOpen)) {
                    continue;
                }
                $hasProviderModuleFlags = true;
                $allProviderModulesDisabled = $allProviderModulesDisabled
                    && $moduleOpen[$alias] === false;
            }
        }

        $imageModuleAllowed = ($permissions['canOpenModule'] ?? true) !== false
            && ($permissions['canAddImage'] ?? true) !== false
            && ($permissions['canOpenImages'] ?? true) !== false
            && ($permissions['canUseExternalImageLibraries'] ?? true) !== false
            && ($moduleOpen['image'] ?? $moduleOpen['images'] ?? true) !== false;

        // Before providers became tabs inside the Images module, generated
        // product configs disabled every provider module by default. That exact
        // signature is migrated once; individual provider choices made after
        // schema 5 remain authoritative.
        if (
            ! $imageModuleAllowed
            || ! $allProviderPermissionsDisabled
            || ! $hasProviderModuleFlags
            || ! $allProviderModulesDisabled
        ) {
            return;
        }

        foreach ($legacyProviders as $permissionKey => $aliases) {
            $permissions[$permissionKey] = true;
            foreach ($aliases as $alias) {
                $moduleOpen[$alias] = true;
            }
        }
        $permissions['moduleOpen'] = $moduleOpen;

        update_post_meta(
            $postId,
            '_apd_permissions',
            wp_slash(self::encode($permissions))
        );
    }

    private static function migrateLegacyStudioApplyPermission(int $postId) : void
    {
        $permissions = self::decodeMeta($postId, '_apd_permissions');
        if (! is_array($permissions)) {
            return;
        }

        $moduleOpen = is_array($permissions['moduleOpen'] ?? null)
            ? (array) $permissions['moduleOpen']
            : [];
        $studioIsEditable = ($permissions['canOpenModule'] ?? true) !== false
            && ($moduleOpen['studio'] ?? false) === true
            && ($permissions['canUseStudio'] ?? false) === true
            && ($permissions['canEditObjectStudio'] ?? true) !== false;

        // Older generated configurations could expose an editable Studio while
        // leaving its final apply permission disabled. Repair that signature
        // once through the schema migration; later explicit choices stay intact.
        if (
            ! $studioIsEditable
            || ($permissions['canApplyStudioEffects'] ?? null) !== false
        ) {
            return;
        }

        $permissions['canApplyStudioEffects'] = true;
        update_post_meta(
            $postId,
            '_apd_permissions',
            wp_slash(self::encode($permissions))
        );
    }

    private static function prepareLegacyCatalogForCanonicalSchema(array $catalog) : array
    {
        foreach ((array) ($catalog['designs'] ?? []) as $index => $item) {
            if (! is_array($item)) {
                continue;
            }
            if (! isset($item['svgText']) && isset($item['svg'])) {
                $item['svgText'] = $item['svg'];
            }
            $catalog['designs'][$index] = $item;
        }

        foreach ((array) ($catalog['printing_methods'] ?? []) as $index => $item) {
            if (! is_array($item)) {
                continue;
            }
            if (! isset($item['colorIds']) && isset($item['colors'])) {
                $item['colorIds'] = $item['colors'];
            }
            $catalog['printing_methods'][$index] = $item;
        }

        foreach ((array) ($catalog['shapes'] ?? []) as $index => $item) {
            if (! is_array($item)) {
                continue;
            }
            if (! isset($item['svgText']) && isset($item['svg'])) {
                $item['svgText'] = $item['svg'];
            }
            $catalog['shapes'][$index] = $item;
        }

        return $catalog;
    }

    private static function prepareLegacyProductColorsForCanonicalSchema(array $payload) : array
    {
        if (array_is_list($payload)) {
            return ['items' => $payload, 'printingMethods' => []];
        }

        if (! isset($payload['items']) && isset($payload['colors']) && is_array($payload['colors'])) {
            $payload['items'] = $payload['colors'];
        }
        if (! isset($payload['printingMethods']) && isset($payload['printing_methods']) && is_array($payload['printing_methods'])) {
            $payload['printingMethods'] = $payload['printing_methods'];
        }

        foreach ((array) ($payload['printingMethods'] ?? []) as $index => $item) {
            if (! is_array($item)) {
                continue;
            }
            if (! isset($item['colorIds']) && isset($item['colors'])) {
                $item['colorIds'] = $item['colors'];
            }
            $payload['printingMethods'][$index] = $item;
        }

        return $payload;
    }

    private static function prepareLegacyFontsForCanonicalSchema(array $payload) : array
    {
        if (isset($payload['system']) || isset($payload['google']) || isset($payload['custom'])) {
            return $payload;
        }

        $rows = [];
        if (array_is_list($payload)) {
            $rows = $payload;
        } elseif (is_array($payload['items'] ?? null)) {
            $rows = (array) $payload['items'];
        } elseif (is_array($payload['fonts'] ?? null)) {
            $rows = (array) $payload['fonts'];
        }

        $grouped = [
            'system' => [],
            'google' => [],
            'custom' => [],
        ];
        foreach ($rows as $font) {
            if (! is_array($font)) {
                continue;
            }
            $source = sanitize_key((string) ($font['source'] ?? 'system'));
            if (! in_array($source, ['system', 'google', 'custom'], true)) {
                $source = 'system';
            }
            $grouped[$source][] = $font;
        }

        return $grouped;
    }

    private static function prepareLegacyPricingForCanonicalSchema(array $payload) : array
    {
        if (array_is_list($payload)) {
            return ['pricingRules' => $payload];
        }
        if (! isset($payload['pricingRules']) && isset($payload['rules']) && is_array($payload['rules'])) {
            $payload['pricingRules'] = $payload['rules'];
        }
        return $payload;
    }

    private static function decodeMeta(int $postId, string $key) : mixed
    {
        $raw = (string) get_post_meta($postId, $key, true);
        if (trim($raw) === '') {
            return [];
        }
        $decoded = json_decode($raw, true);
        return is_array($decoded) ? $decoded : [];
    }

    private static function encode(array $payload) : string
    {
        return (string) wp_json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    }
}
