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

use APD\Support\MetaKeys;

defined('ABSPATH') || exit;

final class PortableProductDocument
{
    public const PRODUCT_FORMAT = 'asuka-product';
    public const TEMPLATE_FORMAT = 'asuka-template';
    public const THEME_FORMAT = 'asuka-theme';
    public const SCHEMA_VERSION = 1;

    private const HOST_RUNTIME_KEYS = [
        'billing', 'cart', 'checkout', 'context', 'customers', 'integrations',
        'license', 'licensetoken', 'orders', 'subscription',
    ];

    private const HOST_IDENTIFIER_KEYS = [
        'billingid', 'cartid', 'checkoutid', 'customerid', 'customerids',
        'lineitemid', 'lineitemids', 'merchandiseid', 'orderid', 'orderids',
        'shopdomain', 'shopid', 'sku', 'skus', 'storeid', 'subscriptionid',
        'variantid', 'variantids',
    ];

    private const SECRET_KEYS = [
        'accesstoken', 'apikey', 'authorization', 'credentials', 'licensekey',
        'licensetoken', 'password', 'refreshtoken', 'secret',
    ];

    private const META_TO_FIELD = [
        MetaKeys::LEGACY_THEME => 'theme',
        MetaKeys::LEGACY_LAYOUT => 'layout',
        MetaKeys::LEGACY_MODULES => 'modules',
        MetaKeys::LEGACY_PERMISSIONS => 'permissions',
        MetaKeys::LEGACY_VIEWS => 'views',
        MetaKeys::LEGACY_DRAWING_ZONES => 'drawingAreas',
        MetaKeys::LEGACY_FONTS => 'fonts',
        MetaKeys::LEGACY_UPLOAD => 'upload',
        MetaKeys::LEGACY_COLORS => 'colors',
        MetaKeys::LEGACY_PRICING_RULES => 'pricingRules',
        MetaKeys::LEGACY_SELECTION_MENU => 'selectionMenu',
        '_apd_ui' => 'ui',
        MetaKeys::LEGACY_FRONT_BEHAVIOR => 'frontBehavior',
        MetaKeys::LEGACY_MOBILE_BEHAVIOR => 'mobileBehavior',
        MetaKeys::LEGACY_INITIAL_DOCUMENT => 'initialDocument',
        MetaKeys::LEGACY_INITIAL_STATE => 'state',
        MetaKeys::TEMPLATE_LAYOUT_CONFIG => 'templateLayout',
        MetaKeys::TEMPLATE_EDITOR_STATE => 'templateEditorState',
        MetaKeys::PRODUCT_CONFIG_CONTENT_STATE => 'contentState',
        MetaKeys::PRODUCT_CONFIG_EDITOR_STATE => 'editorState',
        MetaKeys::PRODUCT_CONFIG_THEME_CONFIG => 'themeConfig',
        MetaKeys::PRODUCT_CONFIG_PRICING_CONFIG => 'pricingConfig',
    ];

    public function productFromWordPress(array $payload, ?string $templateReference, array $themeReferences) : array
    {
        return [
            'format' => self::PRODUCT_FORMAT,
            'schemaVersion' => self::SCHEMA_VERSION,
            'kind' => 'product',
            'metadata' => $this->metadata((array) ($payload['post'] ?? []), __('Imported custom product', 'asuka-product-designer')),
            'designer' => $this->designerFromMeta((array) ($payload['meta'] ?? []), $themeReferences),
            'dependencies' => [
                'template' => $templateReference,
                'themes' => array_values(array_unique(array_values($themeReferences))),
            ],
        ];
    }

    public function templateFromWordPress(array $payload, array $themeReferences) : array
    {
        return [
            'format' => self::TEMPLATE_FORMAT,
            'schemaVersion' => self::SCHEMA_VERSION,
            'kind' => 'template',
            'metadata' => $this->metadata((array) ($payload['post'] ?? []), __('Imported template', 'asuka-product-designer')),
            'designer' => $this->designerFromMeta((array) ($payload['meta'] ?? []), $themeReferences),
            'dependencies' => [
                'themes' => array_values(array_unique(array_values($themeReferences))),
            ],
        ];
    }

    public function themeFromWordPress(array $post, array $payload, string $reference) : array
    {
        return [
            'format' => self::THEME_FORMAT,
            'schemaVersion' => self::SCHEMA_VERSION,
            'kind' => 'theme',
            'reference' => $reference,
            'metadata' => $this->metadata($post, __('Imported theme', 'asuka-product-designer')),
            'theme' => $payload,
        ];
    }

    public function productToWordPress(array $document, array $themeMap, array $templateMap) : array|\WP_Error
    {
        if (! $this->isDocument($document, self::PRODUCT_FORMAT)) {
            return new \WP_Error('portable_product_invalid', __('The portable product document is invalid.', 'asuka-product-designer'));
        }
        $metadata = (array) ($document['metadata'] ?? []);
        $meta = $this->metaFromDesigner((array) ($document['designer'] ?? []), $themeMap);
        $templateReference = (string) ($document['dependencies']['template'] ?? '');
        if ($templateReference !== '' && isset($templateMap[$templateReference])) {
            $meta[MetaKeys::LEGACY_TEMPLATE_ID] = (string) $templateMap[$templateReference];
            $meta[MetaKeys::PRODUCT_CONFIG_TEMPLATE_ID] = (string) $templateMap[$templateReference];
        }
        return [
            'schema' => 'apd-product-config',
            'version' => defined('ASUKA_PRODUCT_DESIGNER_VERSION') ? ASUKA_PRODUCT_DESIGNER_VERSION : '',
            'post' => [
                'title' => sanitize_text_field((string) ($metadata['name'] ?? __('Imported custom product', 'asuka-product-designer'))),
                'slug' => sanitize_title((string) ($metadata['slug'] ?? '')),
                'status' => 'draft',
            ],
            'meta' => $meta,
        ];
    }

    public function templateToWordPress(array $document, array $themeMap) : array|\WP_Error
    {
        if (! $this->isDocument($document, self::TEMPLATE_FORMAT)) {
            return new \WP_Error('portable_template_invalid', __('The portable template document is invalid.', 'asuka-product-designer'));
        }
        $metadata = (array) ($document['metadata'] ?? []);
        return [
            'schema' => 'apd-template',
            'version' => defined('ASUKA_PRODUCT_DESIGNER_VERSION') ? ASUKA_PRODUCT_DESIGNER_VERSION : '',
            'post' => [
                'title' => sanitize_text_field((string) ($metadata['name'] ?? __('Imported template', 'asuka-product-designer'))),
                'slug' => sanitize_title((string) ($metadata['slug'] ?? '')),
                'status' => 'draft',
            ],
            'meta' => $this->metaFromDesigner((array) ($document['designer'] ?? []), $themeMap),
        ];
    }

    public function isDocument(array $document, string $format) : bool
    {
        return ($document['format'] ?? '') === $format
            && (int) ($document['schemaVersion'] ?? 0) === self::SCHEMA_VERSION
            && is_array($document['metadata'] ?? null);
    }

    public static function templateReference(string $slug) : string
    {
        $slug = sanitize_title($slug);
        return $slug !== '' ? 'template:' . $slug : '';
    }

    public static function themeReference(string $slug) : string
    {
        $slug = sanitize_title($slug);
        return $slug !== '' ? 'theme:' . $slug : '';
    }

    private function designerFromMeta(array $meta, array $themeReferences) : array
    {
        $designer = [];
        foreach (self::META_TO_FIELD as $metaKey => $field) {
            if (! array_key_exists($metaKey, $meta)) {
                continue;
            }
            $value = $meta[$metaKey];
            if (in_array($metaKey, array_merge(MetaKeys::jsonKeys(), ['_apd_ui']), true) && is_string($value)) {
                $decoded = json_decode($value, true);
                if (json_last_error() === JSON_ERROR_NONE) {
                    $value = $decoded;
                }
            }
            if ($value === '' || $value === null) {
                continue;
            }
            $value = $this->sanitizePortableField($field, $value);
            if ($value === null || $value === [] || $value === '') {
                continue;
            }
            $designer[$field] = $this->replaceThemeIdsWithReferences($value, $themeReferences);
        }
        ksort($designer, SORT_STRING);
        return $designer;
    }

    private function metaFromDesigner(array $designer, array $themeMap) : array
    {
        if (! array_key_exists('state', $designer) && array_key_exists('initialState', $designer)) {
            $designer['state'] = $designer['initialState'];
        }
        unset($designer['initialState']);
        $fieldToMeta = array_flip(self::META_TO_FIELD);
        $meta = [];
        foreach ($designer as $field => $value) {
            if (! isset($fieldToMeta[$field])) {
                continue;
            }
            $meta[$fieldToMeta[$field]] = $this->replaceThemeReferencesWithIds($value, $themeMap);
        }
        return $meta;
    }

    private function replaceThemeIdsWithReferences(mixed $value, array $themeReferences, string $key = '') : mixed
    {
        if (is_array($value)) {
            $rewritten = [];
            foreach ($value as $childKey => $childValue) {
                $rewritten[$childKey] = $this->replaceThemeIdsWithReferences($childValue, $themeReferences, (string) $childKey);
            }
            return $rewritten;
        }
        if (in_array(strtolower($key), ['themeid', 'theme_id'], true)) {
            $themeId = absint($value);
            return isset($themeReferences[$themeId]) ? $themeReferences[$themeId] : $value;
        }
        return $value;
    }

    private function replaceThemeReferencesWithIds(mixed $value, array $themeMap, string $key = '') : mixed
    {
        if (is_array($value)) {
            $restored = [];
            foreach ($value as $childKey => $childValue) {
                $restored[$childKey] = $this->replaceThemeReferencesWithIds($childValue, $themeMap, (string) $childKey);
            }
            return $restored;
        }
        if (in_array(strtolower($key), ['themeid', 'theme_id'], true) && is_string($value) && isset($themeMap[$value])) {
            return (string) $themeMap[$value];
        }
        return $value;
    }

    private function metadata(array $post, string $fallbackName) : array
    {
        $name = sanitize_text_field((string) ($post['title'] ?? $post['name'] ?? $fallbackName));
        $slug = sanitize_title((string) ($post['slug'] ?? $name));
        return [
            'name' => $name !== '' ? $name : $fallbackName,
            'slug' => $slug,
        ];
    }

    private function sanitizePortableField(string $field, mixed $value) : mixed
    {
        if ($field === 'upload') {
            if (! is_array($value)) {
                return null;
            }
            foreach (['endpoint', 'headers', 'credentials', 'token', 'apiKey', 'licenseToken'] as $key) {
                unset($value[$key]);
            }
            $strategy = sanitize_key((string) ($value['strategy'] ?? ''));
            if (in_array($strategy, ['remote', 'server', 'blob'], true)) {
                unset($value['strategy'], $value['method'], $value['fieldName'], $value['source']);
            }
            return $this->stripHostRuntimeState($value);
        }

        if (in_array($field, ['state', 'initialState', 'initialDocument', 'contentState', 'editorState'], true)) {
            return $this->stripHostRuntimeState($value);
        }

        return $this->stripHostRuntimeState($value);
    }

    private function stripHostRuntimeState(mixed $value, array $path = []) : mixed
    {
        if (! is_array($value)) {
            return $value;
        }

        $clean = [];
        $parent = $this->contractKey((string) ($path[count($path) - 1] ?? ''));
        foreach ($value as $key => $child) {
            if (is_int($key)) {
                $clean[$key] = $this->stripHostRuntimeState($child, $path);
                continue;
            }

            $rawKey = strtolower($key);
            $normalized = $this->contractKey($key);
            $isConfigurationRuntime = $parent === 'configuration'
                && in_array($normalized, ['ai', 'api', 'credentials', 'exports', 'integrations', 'license', 'licensetoken', 'token', 'upload'], true);
            $isRootRuntime = $path === []
                && in_array($normalized, ['ai', 'billing', 'cart', 'checkout', 'context', 'customers', 'exports', 'integrations', 'license', 'licensetoken', 'orders', 'shopify', 'subscription', 'woocommerce'], true);
            $isHostProductId = $normalized === 'productid'
                && is_string($child)
                && preg_match('/^(?:gid:\/\/shopify\/|wc[:_-]|woocommerce[:_-])/i', trim($child)) === 1;

            if (
                str_starts_with($rawKey, '_apd_')
                || str_starts_with($rawKey, '_shopify_')
                || str_starts_with($rawKey, '_woocommerce_')
                || str_contains($normalized, 'shopify')
                || str_contains($normalized, 'woocommerce')
                || in_array($normalized, self::HOST_RUNTIME_KEYS, true)
                || in_array($normalized, self::HOST_IDENTIFIER_KEYS, true)
                || in_array($normalized, self::SECRET_KEYS, true)
                || $isHostProductId
                || $isConfigurationRuntime
                || $isRootRuntime
            ) {
                continue;
            }

            $clean[$key] = $this->stripHostRuntimeState($child, [...$path, $key]);
        }
        return $clean;
    }

    private function contractKey(string $value) : string
    {
        return strtolower((string) preg_replace('/[^a-z0-9]/i', '', $value));
    }
}
