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

use APD\Domain\Product\ProductConfigBinder;
use APD\Front\FrontContextBuilder;
use APD\Support\UserDesignAccess;
use APD\Support\RequestData;
use WC_Product;

defined('ABSPATH') || exit;

class ProductIntegration
{
    public function boot() : void
    {
        $binding = new ProductBindingMetaBox();

        add_action('save_post_product', [$binding, 'save'], 10, 2);
        add_filter('woocommerce_product_data_tabs', [$binding, 'registerProductDataTab']);
        add_action('woocommerce_product_data_panels', [$binding, 'renderProductDataPanel']);
        add_action('woocommerce_product_after_variable_attributes', [$binding, 'renderVariationField'], 20, 3);
        add_action('woocommerce_save_product_variation', [$binding, 'saveVariation'], 10, 2);

        add_filter('body_class', [$this, 'filterBodyClasses']);
        add_action('woocommerce_before_single_product_summary', [$this, 'maybeReplaceProductMedia'], 1);
        add_action('woocommerce_after_single_product', [$this, 'renderCondensedDesignerMediaReplacement'], 1);
        add_action('woocommerce_single_product_summary', [$this, 'renderModalTriggerAbovePrice'], 9);
        add_action('woocommerce_single_product_summary', [$this, 'renderModalTriggerBelowPrice'], 11);
        add_action('woocommerce_after_single_product', [$this, 'renderModalMarkup'], 20);
    }

    public function getCurrentProduct() : ?WC_Product
    {
        if (! is_product()) {
            return null;
        }

        global $product;
        if ($product instanceof WC_Product) {
            return $product;
        }

        $productId = (int) get_queried_object_id();
        if ($productId <= 0) {
            return null;
        }

        $resolved = wc_get_product($productId);

        return $resolved instanceof WC_Product ? $resolved : null;
    }

    public function filterBodyClasses(array $classes) : array
    {
        $product = $this->getCurrentProduct();
        if (! $product) {
            return $classes;
        }

        $context = $this->getDisplayContext($product);
        if (($context['parentConfigId'] <= 0 && empty($context['variationBindings'])) || $context['displayMode'] !== 'full_width') {
            return $classes;
        }

        $classes[] = 'apd-mode-full-width';

        return array_values(array_unique($classes));
    }

    public function getDisplayContext(WC_Product $product) : array
    {
        $binder = new ProductConfigBinder();
        $productId = $product->get_id();
        $parentConfigId = $binder->getExactConfigId($productId);
        $variationBindings = $binder->getVariationBindings($productId);
        $displayMode = $binder->getDisplayMode($productId);
        $modalPosition = $binder->getModalButtonPosition($productId);
        $resumeDesign = $this->resolveResumeDesign($productId, $parentConfigId, $variationBindings);

        return [
            'productId' => $productId,
            'parentConfigId' => $parentConfigId,
            'variationBindings' => $variationBindings,
            'displayMode' => $displayMode,
            'modalPosition' => $modalPosition,
            'resumeDesign' => $resumeDesign,
        ];
    }

    public function buildShells(int $productId, int $parentConfigId, array $variationBindings, array $resumeDesign = []) : array
    {
        $shells = [];
        if ($parentConfigId > 0) {
            $shells['parent'] = $this->renderDesignerShortcode($parentConfigId, $productId, $resumeDesign, 0);
        }
        foreach ($variationBindings as $variationId => $configId) {
            $shells['variation:' . (int) $variationId] = $this->renderDesignerShortcode((int) $configId, $productId, $resumeDesign, (int) $variationId);
        }

        return $shells;
    }

    public function buildShellPayloads(int $productId, int $parentConfigId, array $variationBindings, array $resumeDesign = []) : array
    {
        $builder = new FrontContextBuilder();
        $payloads = [];

        if ($parentConfigId > 0) {
            $payloads['parent'] = $this->buildShellPayload($builder, $productId, $parentConfigId, 0, $resumeDesign);
        }

        foreach ($variationBindings as $variationId => $configId) {
            $payloads['variation:' . (int) $variationId] = $this->buildShellPayload($builder, $productId, (int) $configId, (int) $variationId, $resumeDesign);
        }

        return $payloads;
    }

    protected function buildShellPayload(FrontContextBuilder $builder, int $productId, int $configId, int $variationId, array $resumeDesign = []) : array
    {
        $atts = [
            'id' => (string) $configId,
            'product_id' => (string) $productId,
            'variation_id' => (string) $variationId,
            'mode' => 'customer',
        ];

        if (! empty($resumeDesign) && (int) ($resumeDesign['configId'] ?? 0) === $configId) {
            $atts['resume_design_id'] = (string) (int) ($resumeDesign['id'] ?? 0);
            $atts['resume_token'] = (string) ($resumeDesign['accessToken'] ?? '');
        }

        $context = $builder->build($atts);
        $config = is_array($context['config'] ?? null) ? $context['config'] : [];
        $document = $this->extractDocumentStateFromRuntimeConfig($config);
        $state = $this->extractFullProductStateFromRuntimeConfig($config, $document);
        $runtimeContext = is_array($config['context'] ?? null) ? $config['context'] : [];
        $query = RequestData::query();
        $isResumePayload = absint($runtimeContext['userDesignId'] ?? 0) > 0
            || absint($runtimeContext['resumeDesignId'] ?? 0) > 0
            || absint($query['apd_resume_design'] ?? 0) > 0;
        $config = $this->canonicalizeShellPayloadConfig($config, ! empty($state), $isResumePayload);

        return [
            'productId' => $productId,
            'configId' => $configId,
            'variationId' => $variationId,
            'document' => $document,
            'state' => $state,
            'config' => $config,
        ];
    }

    public function renderShellSwitcher(array $shells, array $payloads, string $defaultKey, string $wrapperClass, string $placeholderText, array $resumeContext = []) : void
    {
        $registryId = 'apd-registry-' . wp_generate_password(8, false, false);
        ?>
        <div class="<?php echo esc_attr($wrapperClass); ?>" data-apd-shell-switcher data-apd-registry-id="<?php echo esc_attr($registryId); ?>" data-default-key="<?php echo esc_attr($defaultKey); ?>"<?php echo ! empty($resumeContext['variationId']) ? ' data-apd-resume-variation-id="' . esc_attr((string) (int) $resumeContext['variationId']) . '"' : ''; ?><?php echo ! empty($resumeContext['attributes']) ? ' data-apd-resume-attributes="' . esc_attr(wp_json_encode($resumeContext['attributes'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) . '"' : ''; ?>>
            <div class="apd-product-gallery-host" data-apd-active-shell>
                <?php if ($defaultKey && ! empty($shells[$defaultKey])) : ?>
                    <?php echo $shells[$defaultKey]; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
                <?php else : ?>
                    <div class="apd-product-gallery-placeholder" data-apd-gallery-placeholder>
                        <?php echo esc_html($placeholderText); ?>
                    </div>
                <?php endif; ?>
            </div>
            <script type="application/json" class="apd-shell-payload-map">
                <?php echo wp_json_encode($payloads, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>
            </script>
            <?php foreach ($shells as $key => $html) : ?>
                <template data-apd-shell-template="<?php echo esc_attr($key); ?>">
                    <?php echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
                </template>
            <?php endforeach; ?>
        </div>
        <?php
    }

    public function maybeReplaceProductMedia() : void
    {
        $product = $this->getCurrentProduct();
        if (! $product) {
            return;
        }

        $context = $this->getDisplayContext($product);
        if (($context['parentConfigId'] <= 0 && empty($context['variationBindings'])) || ! in_array($context['displayMode'], ['condensed', 'full_width'], true)) {
            return;
        }

        if ($context['parentConfigId'] <= 0) {
            return;
        }

        remove_action('woocommerce_before_single_product_summary', 'woocommerce_show_product_images', 20);
    }

    public function renderCondensedDesignerMediaReplacement() : void
    {
        $product = $this->getCurrentProduct();
        if (! $product) {
            return;
        }

        $context = $this->getDisplayContext($product);
        if (($context['parentConfigId'] <= 0 && empty($context['variationBindings'])) || ! in_array($context['displayMode'], ['condensed', 'full_width'], true)) {
            return;
        }

        $shells = $this->buildShells($context['productId'], $context['parentConfigId'], $context['variationBindings'], $context['resumeDesign']);
        $payloads = $this->buildShellPayloads($context['productId'], $context['parentConfigId'], $context['variationBindings'], $context['resumeDesign']);
        $defaultKey = $this->resolveDefaultShellKey($context['parentConfigId'], $context['variationBindings'], $context['resumeDesign']);
        echo '<div class="apd-condensed-source" data-apd-condensed-source data-apd-display-mode="' . esc_attr($context['displayMode']) . '" hidden>';
        $this->renderShellSwitcher($shells, $payloads, $defaultKey, 'apd-product-gallery-replacement', __('Choose a variation to load the linked custom product designer.', 'asuka-product-designer'), $this->extractResumeUiContext($context['resumeDesign']));
        echo '</div>';
    }

    public function renderModalTriggerAbovePrice() : void
    {
        $this->renderModalTriggerForPosition('above_price');
    }

    public function renderModalTriggerBelowPrice() : void
    {
        $this->renderModalTriggerForPosition('below_price');
    }

    protected function renderModalTriggerForPosition(string $position) : void
    {
        $product = $this->getCurrentProduct();
        if (! $product) {
            return;
        }

        $context = $this->getDisplayContext($product);
        if (($context['parentConfigId'] <= 0 && empty($context['variationBindings'])) || $context['displayMode'] !== 'modal' || $context['modalPosition'] !== $position) {
            return;
        }
        ?>
        <div class="apd-modal-trigger-wrap apd-modal-trigger-wrap--<?php echo esc_attr($position); ?>">
            <button type="button" class="button alt apd-open-designer-modal" data-apd-open-modal>
                <?php esc_html_e('Customize this product', 'asuka-product-designer'); ?>
            </button>
        </div>
        <?php
    }

    public function renderModalMarkup() : void
    {
        $product = $this->getCurrentProduct();
        if (! $product) {
            return;
        }

        $context = $this->getDisplayContext($product);
        if (($context['parentConfigId'] <= 0 && empty($context['variationBindings'])) || $context['displayMode'] !== 'modal') {
            return;
        }

        $shells = $this->buildShells($context['productId'], $context['parentConfigId'], $context['variationBindings'], $context['resumeDesign']);
        $payloads = $this->buildShellPayloads($context['productId'], $context['parentConfigId'], $context['variationBindings'], $context['resumeDesign']);
        $defaultKey = $this->resolveDefaultShellKey($context['parentConfigId'], $context['variationBindings'], $context['resumeDesign']);
        $query = RequestData::query();
        $autoOpen = ! empty($context['resumeDesign']) && ! empty($query['apd_resume_open']);
        ?>
        <div class="apd-designer-modal" data-apd-modal data-apd-display-mode="modal" <?php echo $autoOpen ? 'data-apd-auto-open="1"' : ''; ?> hidden>
            <div class="apd-designer-modal__backdrop" data-apd-close-modal></div>
            <div class="apd-designer-modal__dialog" role="dialog" aria-modal="true" aria-label="<?php esc_attr_e('Product designer', 'asuka-product-designer'); ?>">
                <div class="apd-designer-modal__header">
                    <button type="button" class="apd-designer-modal__close" data-apd-close-modal aria-label="<?php esc_attr_e('Close designer', 'asuka-product-designer'); ?>">&times;</button>
                </div>
                <div class="apd-designer-modal__body">
                    <?php $this->renderShellSwitcher($shells, $payloads, $defaultKey, 'apd-modal-designer', __('Choose a variation to load the linked custom product designer.', 'asuka-product-designer'), $this->extractResumeUiContext($context['resumeDesign'])); ?>
                </div>
            </div>
        </div>
        <?php
    }

    protected function renderDesignerShortcode(int $configId, int $productId, array $resumeDesign = [], int $variationId = 0) : string
    {
        $attributes = [
            'id' => (string) $configId,
            'product_id' => (string) $productId,
            'mode' => 'customer',
        ];

        if ($variationId > 0) {
            $attributes['variation_id'] = (string) $variationId;
        }

        if (! empty($resumeDesign) && (int) ($resumeDesign['configId'] ?? 0) === $configId) {
            $attributes['resume_design_id'] = (string) (int) ($resumeDesign['id'] ?? 0);
            $attributes['resume_token'] = (string) ($resumeDesign['accessToken'] ?? '');
        }

        $parts = [];
        foreach ($attributes as $key => $value) {
            if ($value === '') {
                continue;
            }
            $parts[] = $key . '="' . esc_attr($value) . '"';
        }

        return do_shortcode('[asuka_designer ' . implode(' ', $parts) . ']');
    }



    protected function canonicalizeShellPayloadConfig(array $config, bool $hasFullState = false, bool $preserveBootPayload = false) : array
    {
        $config['context'] = is_array($config['context'] ?? null) ? $config['context'] : [];

        if ($preserveBootPayload) {
            $document = $this->extractDocumentStateFromRuntimeConfig($config);
            unset($config['initialState'], $config['documentState'], $config['objectsByView'], $config['documentObjectsByView'], $config['snapshotsByView'], $config['viewSnapshots']);
            if ($document !== []) {
                $config['initialDocument'] = $document;
            }
            $config['context']['apdBootStrategy'] = 'direct_boot_payload';
            return $config;
        }

        $config['context']['apdBootStrategy'] = $hasFullState ? 'variation_state' : 'variation_document';
        unset(
            $config['initialDocument'],
            $config['initialState'],
            $config['documentState'],
            $config['views'],
            $config['drawingZones'],
            $config['objectsByView'],
            $config['documentObjectsByView'],
            $config['snapshotsByView'],
            $config['viewSnapshots']
        );

        return $config;
    }

    protected function extractFullProductStateFromRuntimeConfig(array $config, array $documentState = []) : array
    {
        $state = is_array($config['initialState'] ?? null) ? $config['initialState'] : [];
        $runtimeLayout = $this->configLayoutToRuntimeStateLayout(is_array($config['layout'] ?? null) ? $config['layout'] : []);

        if ($state === [] && $runtimeLayout === []) {
            return [];
        }

        if ($state === []) {
            $state = [
                'format' => 'asuka-product-designer-state',
                'schemaVersion' => 1,
                'exportedAt' => gmdate('c'),
                'scope' => [],
            ];
        }

        unset(
            $state['document'],
            $state['initialDocument'],
            $state['documentState'],
            $state['objectsByView'],
            $state['documentObjectsByView'],
            $state['snapshotsByView'],
            $state['viewSnapshots'],
            $state['canvas'],
            $state['legacy']
        );

        $state['format'] = 'asuka-product-designer-state';
        $state['schemaVersion'] = 1;
        $state['scope'] = is_array($state['scope'] ?? null) ? $state['scope'] : [];
        $state['scope']['document'] = false;

        if ($runtimeLayout !== []) {
            $state['layout'] = $runtimeLayout;
            $state['scope']['layout'] = true;
            if (isset($state['ui']) && is_array($state['ui'])) {
                $state['ui']['layout'] = $runtimeLayout;
            }
        } else {
            $state = $this->stripRuntimeLayoutFromStatePayload($state);
        }

        return $state;
    }

    protected function stripRuntimeLayoutFromStatePayload(array $state) : array
    {
        unset($state['layout']);

        if (isset($state['ui']) && is_array($state['ui'])) {
            unset($state['ui']['layout']);
        }

        if (isset($state['scope']) && is_array($state['scope'])) {
            $state['scope']['layout'] = false;
        }

        if (isset($state['legacy']) && is_array($state['legacy'])) {
            if (isset($state['legacy']['state']) && is_array($state['legacy']['state'])) {
                $state['legacy']['state'] = $this->stripRuntimeLayoutFromStatePayload($state['legacy']['state']);
            }
            if (isset($state['legacy']['scope']) && is_array($state['legacy']['scope'])) {
                $state['legacy']['scope']['layout'] = false;
            }
        }

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

        return $state;
    }

    protected function configLayoutToRuntimeStateLayout(array $layout) : array
    {
        if ($layout === []) {
            return [];
        }

        if (isset($layout['blocksByZone']) && is_array($layout['blocksByZone'])) {
            return $layout;
        }

        $blocksByZone = [];
        $zoneCollapsed = [];
        $zoneCollapsible = [];
        $zoneType = [];
        $hasItems = false;

        foreach (['left', 'right', 'top', 'bottom'] as $zone) {
            $zoneConfig = isset($layout[$zone]) && is_array($layout[$zone]) ? $layout[$zone] : [];
            $items = isset($zoneConfig['items']) && is_array($zoneConfig['items']) ? $zoneConfig['items'] : [];
            usort($items, static function ($a, $b) : int {
                $a = is_array($a) ? $a : [];
                $b = is_array($b) ? $b : [];
                $aOrder = is_numeric($a['order'] ?? null) ? (int) $a['order'] : 0;
                $bOrder = is_numeric($b['order'] ?? null) ? (int) $b['order'] : 0;
                return $aOrder <=> $bOrder;
            });

            $blocksByZone[$zone] = [];
            foreach ($items as $index => $item) {
                if (! is_array($item)) {
                    continue;
                }
                $kind = (string) ($item['kind'] ?? ($item['module'] ?? ($item['id'] ?? '')));
                if ($kind === '') {
                    continue;
                }
                $id = (string) ($item['id'] ?? $kind);
                $blocksByZone[$zone][] = [
                    'id' => $id !== '' ? $id : $kind,
                    'kind' => $kind,
                    'label' => (string) ($item['label'] ?? $kind),
                    'icon' => (string) ($item['icon'] ?? ''),
                    'order' => $index,
                    'type' => (string) ($item['type'] ?? 'floating'),
                    'collapsed' => (bool) ($item['collapsed'] ?? false),
                    'collapsible' => array_key_exists('collapsible', $item) ? (bool) $item['collapsible'] : true,
                ];
                $hasItems = true;
            }

            $zoneCollapsed[$zone] = (bool) ($zoneConfig['collapsed'] ?? false);
            $zoneCollapsible[$zone] = array_key_exists('collapsible', $zoneConfig) ? (bool) $zoneConfig['collapsible'] : true;
            $zoneType[$zone] = $this->mapConfigZoneTypeToRuntimeZoneType((string) ($zoneConfig['type'] ?? 'floating'));
        }

        if (! $hasItems) {
            return [];
        }

        return [
            'blocksByZone' => $blocksByZone,
            'zoneCollapsed' => $zoneCollapsed,
            'zoneCollapsible' => $zoneCollapsible,
            'zoneType' => $zoneType,
        ];
    }

    protected function mapConfigZoneTypeToRuntimeZoneType(string $type) : string
    {
        $type = sanitize_key($type);
        if ($type === 'panels' || $type === 'panel' || strpos($type, 'panel-') === 0) {
            return 'panels';
        }
        if ($type === 'inline') {
            return 'inline';
        }
        return 'default';
    }

    protected function extractDocumentStateFromRuntimeConfig(array $config) : array
    {
        $topLevelDocument = [
            'views' => $config['views'] ?? null,
            'drawingAreas' => $config['drawingAreas'] ?? null,
            'drawingZones' => $config['drawingZones'] ?? null,
            'snapshots' => $config['snapshots'] ?? null,
            'snapshotsByView' => $config['snapshotsByView'] ?? null,
            'viewSnapshots' => $config['viewSnapshots'] ?? null,
            'objects' => $config['objects'] ?? null,
            'objectsByView' => $config['objectsByView'] ?? null,
            'documentObjectsByView' => $config['documentObjectsByView'] ?? null,
            'viewportByView' => $config['viewportByView'] ?? null,
            'selection' => $config['selection'] ?? null,
            'scenes3d' => $config['scenes3d'] ?? ($config['document3dScenesByView'] ?? null),
            'asukaDocument' => $config['asukaDocument'] ?? null,
        ];
        $topLevelDocument = array_filter($topLevelDocument, static fn($value) => $value !== null && $value !== []);

        $candidates = [
            $config['initialDocument'] ?? null,
            $config['documentState'] ?? null,
            is_array($config['initialState'] ?? null) ? ($config['initialState']['initialDocument'] ?? null) : null,
            is_array($config['initialState'] ?? null) ? ($config['initialState']['document'] ?? null) : null,
            is_array($config['initialState'] ?? null) ? ($config['initialState']['documentState'] ?? null) : null,
            $topLevelDocument,
            $config['initialState'] ?? null,
        ];

        $best = [];
        $bestScore = -1;
        foreach ($candidates as $candidate) {
            if (! is_array($candidate) || ! $this->payloadHasViews($candidate)) {
                continue;
            }
            $score = ($this->payload3dProductCount($candidate) * 1000000)
                + ($this->payloadObjectCount($candidate) * 1000)
                + $this->payloadViewCount($candidate);
            if ($score > $bestScore) {
                $best = $candidate;
                $bestScore = $score;
            }
        }

        return $this->normalizeDocumentPayloadForRuntime($best);
    }

    protected function normalizeDocumentPayloadForRuntime(array $document) : array
    {
        if ($document === []) {
            return [];
        }

        if (! $this->payloadHasViews($document) && isset($document['document']) && is_array($document['document'])) {
            $document = $document['document'];
        } elseif (! $this->payloadHasViews($document) && isset($document['initialDocument']) && is_array($document['initialDocument'])) {
            $document = $document['initialDocument'];
        }

        $views = $document['views'] ?? [];
        if (is_array($views) && isset($views['views']) && is_array($views['views'])) {
            $viewList = array_values($views['views']);
            $activeViewId = (string) ($views['activeViewId'] ?? ($document['activeViewId'] ?? ''));
        } elseif (is_array($views) && array_is_list($views)) {
            $viewList = array_values($views);
            $activeViewId = (string) ($document['activeViewId'] ?? '');
        } else {
            $viewList = [];
            $activeViewId = (string) ($document['activeViewId'] ?? '');
        }

        if ($activeViewId === '' && isset($viewList[0]) && is_array($viewList[0])) {
            $activeViewId = (string) ($viewList[0]['id'] ?? '');
        }

        $snapshotsByView = $this->payloadSnapshotsByView($document);
        $objectsByView = $this->deriveObjectsByViewFromSnapshots($snapshotsByView, $this->payloadObjectsByView($document));
        $rootObjects = method_exists($this, 'payloadRootObjects') ? $this->payloadRootObjects($document) : [];
        if ($rootObjects !== []) {
            $viewId = $activeViewId !== '' ? $activeViewId : $this->payloadActiveViewId($document);
            if (! isset($objectsByView[$viewId]) || ! is_array($objectsByView[$viewId]) || $objectsByView[$viewId] === []) {
                $objectsByView[$viewId] = $rootObjects;
            }
        }

        $canonical = [
            'schemaVersion' => 1,
            'views' => [
                'views' => $viewList,
                'activeViewId' => $activeViewId,
            ],
            'activeViewId' => $activeViewId,
        ];

        if ($objectsByView !== []) {
            $canonical['objects'] = ['byView' => $objectsByView];
        }
        if (isset($document['viewportByView']) && is_array($document['viewportByView'])) {
            $canonical['viewportByView'] = $document['viewportByView'];
        } elseif (isset($document['documentViewportByView']) && is_array($document['documentViewportByView'])) {
            $canonical['viewportByView'] = $document['documentViewportByView'];
        }
        if (isset($document['selection']) && is_array($document['selection'])) {
            $canonical['selection'] = $document['selection'];
        }
        if (isset($document['drawingAreas']) && is_array($document['drawingAreas'])) {
            $canonical['drawingAreas'] = $document['drawingAreas'];
            if (isset($document['drawingAreas']['byView']) && is_array($document['drawingAreas']['byView'])) {
                $canonical['drawingAreasByView'] = $document['drawingAreas']['byView'];
                $canonical['drawingZones'] = $this->flattenDrawingAreasByView($document['drawingAreas']['byView']);
            } elseif (isset($document['drawingAreas']['zones']) && is_array($document['drawingAreas']['zones'])) {
                $canonical['drawingZones'] = array_values(array_filter($document['drawingAreas']['zones'], 'is_array'));
            }
        } elseif (isset($document['drawingAreasByView']) && is_array($document['drawingAreasByView'])) {
            $canonical['drawingAreasByView'] = $document['drawingAreasByView'];
            $canonical['drawingZones'] = $this->flattenDrawingAreasByView($document['drawingAreasByView']);
            $canonical['drawingAreas'] = [
                'byView' => $document['drawingAreasByView'],
                'zones' => $canonical['drawingZones'],
                'activeDrawingAreaId' => $document['activeDrawingAreaId'] ?? null,
            ];
        } elseif (isset($document['drawingZones']) && is_array($document['drawingZones'])) {
            $canonical['drawingZones'] = array_values(array_filter($document['drawingZones'], 'is_array'));
            $canonical['drawingAreas'] = [
                'zones' => $canonical['drawingZones'],
                'activeDrawingAreaId' => $document['activeDrawingAreaId'] ?? null,
            ];
        }
        if (isset($document['elements']) && is_array($document['elements']) && $document['elements'] !== []) {
            $canonical['elements'] = array_values($document['elements']);
        }
        if (isset($document['fixedSlots']) && is_array($document['fixedSlots']) && $document['fixedSlots'] !== []) {
            $canonical['fixedSlots'] = array_values($document['fixedSlots']);
            $canonical['fixedSlotActiveId'] = $document['fixedSlotActiveId'] ?? null;
        }
        $scenes3d = $document['scenes3d'] ?? ($document['document3dScenesByView'] ?? null);
        if (is_array($scenes3d) && $scenes3d !== []) {
            $canonical['scenes3d'] = $scenes3d;
        }
        if (isset($document['asukaDocument']) && is_array($document['asukaDocument']) && $document['asukaDocument'] !== []) {
            $canonical['asukaDocument'] = $document['asukaDocument'];
        }

        return $canonical;
    }

    protected function payloadObjectsByView(array $payload) : array
    {
        $canvas = isset($payload['canvas']) && is_array($payload['canvas']) ? $payload['canvas'] : [];
        $candidates = [
            $payload['objects']['byView'] ?? null,
            $payload['documentObjectsByView'] ?? null,
            $payload['objectsByView'] ?? null,
            $payload['byView'] ?? null,
            $canvas['objects']['byView'] ?? null,
            $canvas['documentObjectsByView'] ?? null,
            $canvas['objectsByView'] ?? null,
            $canvas['byView'] ?? null,
        ];

        foreach ($candidates as $candidate) {
            if (is_array($candidate) && $candidate !== []) {
                return $candidate;
            }
        }

        return [];
    }

    protected function payloadSnapshotsByView(array $payload) : array
    {
        $canvas = isset($payload['canvas']) && is_array($payload['canvas']) ? $payload['canvas'] : [];
        $candidates = [
            $payload['snapshotsByView'] ?? null,
            $payload['snapshots']['snapshotsByView'] ?? null,
            $payload['viewSnapshots']['snapshotsByView'] ?? null,
            $payload['viewSnapshots'] ?? null,
            $canvas['snapshotsByView'] ?? null,
            $canvas['snapshots']['snapshotsByView'] ?? null,
            $canvas['viewSnapshots']['snapshotsByView'] ?? null,
            $canvas['viewSnapshots'] ?? null,
        ];

        foreach ($candidates as $candidate) {
            if (is_array($candidate) && $candidate !== []) {
                return $candidate;
            }
        }

        return [];
    }

    protected function deriveObjectsByViewFromSnapshots(array $snapshotsByView, array $objectsByView = []) : array
    {
        foreach ($snapshotsByView as $viewId => $snapshot) {
            $key = (string) $viewId;
            if (isset($objectsByView[$key]) && is_array($objectsByView[$key]) && $objectsByView[$key] !== []) {
                continue;
            }
            if (is_array($snapshot) && isset($snapshot['objects']) && is_array($snapshot['objects'])) {
                $objectsByView[$key] = $snapshot['objects'];
            }
        }

        return $objectsByView;
    }

    protected function payloadViewCount(mixed $payload) : int
    {
        if (! is_array($payload) || $payload === []) {
            return 0;
        }

        $views = $payload['views'] ?? null;
        if (is_array($views)) {
            if (array_is_list($views)) {
                return count($views);
            }
            if (isset($views['views']) && is_array($views['views'])) {
                return count($views['views']);
            }
        }

        foreach (['document', 'initialDocument', 'documentState', 'state', 'legacy'] as $key) {
            if (isset($payload[$key]) && is_array($payload[$key])) {
                $nestedCount = $this->payloadViewCount($payload[$key]);
                if ($nestedCount > 0) {
                    return $nestedCount;
                }
            }
        }

        return 0;
    }

    protected function payloadHasViews(mixed $payload) : bool
    {
        if (! is_array($payload) || $payload === []) {
            return false;
        }

        $views = $payload['views'] ?? null;
        if (is_array($views)) {
            if (array_is_list($views) && $views !== []) {
                return true;
            }
            if (isset($views['views']) && is_array($views['views']) && $views['views'] !== []) {
                return true;
            }
        }

        foreach (['document', 'initialDocument', 'state', 'legacy'] as $key) {
            if (isset($payload[$key]) && $this->payloadHasViews($payload[$key])) {
                return true;
            }
        }

        return false;
    }



    protected function flattenDrawingAreasByView(array $byView) : array
    {
        $zones = [];
        foreach ($byView as $viewId => $items) {
            if (! is_array($items)) {
                continue;
            }
            foreach ($items as $item) {
                if (! is_array($item)) {
                    continue;
                }
                if (! isset($item['viewId']) && ! isset($item['view'])) {
                    $item['viewId'] = (string) $viewId;
                }
                $zones[] = $item;
            }
        }

        return $zones;
    }

    protected function payloadActiveViewId(mixed $payload) : string
    {
        if (! is_array($payload)) {
            return '';
        }

        $activeViewId = (string) ($payload['activeViewId'] ?? '');
        if ($activeViewId !== '') {
            return $activeViewId;
        }

        $views = $payload['views'] ?? null;
        if (is_array($views)) {
            $nestedActiveViewId = (string) ($views['activeViewId'] ?? '');
            if ($nestedActiveViewId !== '') {
                return $nestedActiveViewId;
            }

            $viewList = isset($views['views']) && is_array($views['views']) ? $views['views'] : $views;
            if (is_array($viewList) && isset($viewList[0]) && is_array($viewList[0])) {
                return (string) ($viewList[0]['id'] ?? '');
            }
        }

        return '';
    }

    protected function payloadRootObjects(mixed $payload) : array
    {
        if (! is_array($payload)) {
            return [];
        }

        if (isset($payload['objects']) && is_array($payload['objects']) && array_is_list($payload['objects'])) {
            return array_values($payload['objects']);
        }

        return [];
    }

    protected function payloadObjectCount(mixed $payload) : int
    {
        if (! is_array($payload) || $payload === []) {
            return 0;
        }

        $count = 0;
        if (isset($payload['objects']) && is_array($payload['objects']) && array_is_list($payload['objects'])) {
            $count += count($payload['objects']);
        }

        $objectsByView = $this->payloadObjectsByView($payload);
        foreach ($objectsByView as $objects) {
            if (is_array($objects)) {
                $count += count($objects);
            }
        }

        $snapshotsByView = $this->payloadSnapshotsByView($payload);
        foreach ($snapshotsByView as $viewId => $snapshot) {
            $key = (string) $viewId;
            if (isset($objectsByView[$key]) && is_array($objectsByView[$key]) && $objectsByView[$key] !== []) {
                continue;
            }
            if (is_array($snapshot) && isset($snapshot['objects']) && is_array($snapshot['objects'])) {
                $count += count($snapshot['objects']);
            }
        }

        foreach (['document', 'initialDocument', 'documentState', 'state', 'legacy'] as $key) {
            if (isset($payload[$key]) && is_array($payload[$key])) {
                $count += $this->payloadObjectCount($payload[$key]);
            }
        }

        return $count;
    }

    protected function payload3dProductCount(mixed $payload) : int
    {
        if (! is_array($payload) || $payload === []) {
            return 0;
        }

        foreach (['scenes3d', 'document3dScenesByView'] as $sceneKey) {
            $scenes = $payload[$sceneKey] ?? null;
            if (! is_array($scenes) || $scenes === []) {
                continue;
            }

            $count = 0;
            foreach ($scenes as $scene) {
                if (is_array($scene) && isset($scene['products']) && is_array($scene['products'])) {
                    $count += count($scene['products']);
                }
            }
            if ($count > 0) {
                return $count;
            }
        }

        $best = 0;
        foreach (['document', 'initialDocument', 'documentState', 'initialState', 'state', 'legacy', 'asukaDocument'] as $key) {
            if (isset($payload[$key]) && is_array($payload[$key])) {
                $best = max($best, $this->payload3dProductCount($payload[$key]));
            }
        }

        return $best;
    }


    protected function resolveDefaultShellKey(int $parentConfigId, array $variationBindings, array $resumeDesign = []) : string
    {
        $resumeConfigId = absint($resumeDesign['configId'] ?? 0);
        if ($resumeConfigId > 0) {
            if ($parentConfigId > 0 && $resumeConfigId === $parentConfigId) {
                return 'parent';
            }
            foreach ($variationBindings as $variationId => $configId) {
                if ((int) $configId === $resumeConfigId) {
                    return 'variation:' . (int) $variationId;
                }
            }
        }

        return $parentConfigId > 0 ? 'parent' : '';
    }

    protected function extractResumeUiContext(array $resumeDesign) : array
    {
        $query = RequestData::query();
        $production = is_array($resumeDesign['productionData'] ?? null) ? $resumeDesign['productionData'] : [];
        $variationId = absint($query['apd_variation_id'] ?? ($production['variationId'] ?? 0));
        $attributes = [];

        if (is_array($production['variationAttributes'] ?? null)) {
            $attributes = $production['variationAttributes'];
        }

        foreach ($query as $key => $value) {
            $name = sanitize_key((string) $key);
            if ($name === '' || strpos($name, 'attribute_') !== 0) {
                continue;
            }
            $attributes[$name] = wc_clean((string) $value);
        }

        return [
            'variationId' => $variationId,
            'attributes' => $attributes,
        ];
    }

    protected function resolveResumeDesign(int $productId, int $parentConfigId, array $variationBindings) : array
    {
        $query = RequestData::query();
        $resumeDesignId = absint($query['apd_resume_design'] ?? 0);
        if ($resumeDesignId <= 0) {
            return [];
        }

        $accessToken = UserDesignAccess::sanitizeToken((string) ($query['apd_access_token'] ?? ''));
        $item = UserDesignAccess::findAccessibleItem($resumeDesignId, $accessToken);
        if (empty($item)) {
            return [];
        }

        if ((int) ($item['productId'] ?? 0) !== $productId) {
            return [];
        }

        $allowedConfigIds = array_values(array_unique(array_filter(array_merge([$parentConfigId], array_map('intval', $variationBindings)))));
        $resumeConfigId = absint($item['configId'] ?? 0);
        if ($resumeConfigId <= 0 || ! in_array($resumeConfigId, $allowedConfigIds, true)) {
            return [];
        }

        return $item;
    }
}
