<?php
namespace DS_Plugin;

if (!defined('ABSPATH')) { exit; }

class Woo {
    const PRODUCT_META_ENABLE = '_ds_enable_design';
    const PRODUCT_META_TEMPLATE = '_ds_template_id';
    const PRODUCT_META_TEMPLATES = '_ds_template_ids';
    const PRODUCT_META_PRICING_MODE = '_ds_pricing_mode';
    const PRODUCT_META_ZONE_PRICES = '_ds_zone_prices';

    const CART_KEY = 'ds_design_uuid';
    const CART_KEY_PNG = 'ds_design_png';
    const CART_KEY_POST = 'ds_design_post_id';
    const CART_KEY_ZONES = 'ds_print_zones';
    const CART_KEY_SURCHARGE = 'ds_print_surcharge';
    const CART_KEY_BASE_PRICE = 'ds_base_price';
    const CART_KEY_PRICE_APPLIED = 'ds_price_applied';

    const ORDER_META_UUID = '_ds_design_uuid';
    const ORDER_META_PNG = '_ds_design_png';
    const ORDER_META_POST = '_ds_design_post_id';
    const ORDER_META_ZONES = '_ds_print_zones';
    const ORDER_META_ZONES_PNG = '_ds_print_zones_png';
    const ORDER_META_SURCHARGE = '_ds_print_surcharge';

    private static $rendered_product_id = 0;
    private static $footer_sync_script_added = false;

    public static function maybe_init() {
        if (class_exists('WooCommerce')) {
            self::init();
            return;
        }
        add_action('plugins_loaded', function() {
            if (class_exists('WooCommerce')) self::init();
        });
    }

    public static function init() {
        // Keep one source of truth in sidebar product meta box.
        add_action('add_meta_boxes', [__CLASS__, 'add_product_meta_box']);
        add_action('save_post_product', [__CLASS__, 'save_product_meta']);
        add_action('woocommerce_product_options_general_product_data', [__CLASS__, 'render_product_data_fields']);

        add_action('woocommerce_after_add_to_cart_form', [__CLASS__, 'render_editor_if_enabled']);
        add_action('wp_footer', [__CLASS__, 'render_editor_footer_fallback'], 5);
        add_action('wp_footer', [__CLASS__, 'render_cart_preview_modal'], 40);

        add_filter('woocommerce_add_to_cart_validation', [__CLASS__, 'validate_design_before_add_to_cart'], 10, 3);
        add_filter('woocommerce_add_cart_item_data', [__CLASS__, 'add_cart_item_data'], 10, 3);
        add_action('woocommerce_before_calculate_totals', [__CLASS__, 'apply_zone_surcharge_to_cart'], 20, 1);
        add_filter('woocommerce_get_item_data', [__CLASS__, 'render_cart_item_data'], 10, 2);
        add_action('woocommerce_checkout_create_order_line_item', [__CLASS__, 'add_order_item_meta'], 10, 4);
        add_action('woocommerce_new_order_item', [__CLASS__, 'bind_new_order_item_to_design'], 10, 3);
        add_action('woocommerce_after_order_itemmeta', [__CLASS__, 'admin_order_itemmeta_links'], 10, 3);
    }

    public static function add_product_meta_box() {
        add_meta_box(
            'ds_product_design',
            __('Design Studio', 'ds'),
            [__CLASS__, 'render_product_meta_box'],
            'product',
            'side',
            'default'
        );
    }

    public static function render_product_meta_box($post) {
        wp_nonce_field('ds_product_meta', 'ds_product_meta_nonce');

        $enabled = (bool) get_post_meta($post->ID, self::PRODUCT_META_ENABLE, true);
        $template_ids = self::get_product_template_ids((int) $post->ID);
        $pricing_mode = (string) get_post_meta($post->ID, self::PRODUCT_META_PRICING_MODE, true);
        if (!in_array($pricing_mode, ['sum', 'max'], true)) $pricing_mode = 'sum';
        $zone_prices = get_post_meta($post->ID, self::PRODUCT_META_ZONE_PRICES, true);
        if (!is_array($zone_prices)) $zone_prices = [];
        $templates = get_posts([
            'post_type' => 'ds_template',
            'numberposts' => -1,
            'post_status' => 'publish'
        ]);
        $zones = self::get_product_zone_labels((int) $post->ID, $zone_prices);
        $template_zone_map = [];
        foreach ($templates as $t) {
            $template_zone_map[(string) ((int) $t->ID)] = self::get_template_zone_labels((int) $t->ID);
        }
        $template_zone_map_json = wp_json_encode($template_zone_map, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($template_zone_map_json) || $template_zone_map_json === '') $template_zone_map_json = '{}';
        $zone_prices_json = wp_json_encode($zone_prices, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($zone_prices_json) || $zone_prices_json === '') $zone_prices_json = '{}';
        ?>
        <p>
            <label>
                <input type="checkbox" name="ds_enable_design" value="1" <?php checked($enabled); ?>>
                <?php _e('Enable Design Studio for this product', 'ds'); ?>
            </label>
        </p>
        <p>
            <label for="ds_template_ids"><?php _e('Templates', 'ds'); ?></label><br/>
            <select name="ds_template_ids[]" id="ds_template_ids" style="width:100%;min-height:104px;" multiple>
                <?php foreach ($templates as $t): ?>
                    <?php $is_selected = in_array((int) $t->ID, $template_ids, true); ?>
                    <option value="<?php echo esc_attr($t->ID); ?>" <?php selected($is_selected, true); ?>>
                        <?php echo esc_html($t->post_title); ?>
                    </option>
                <?php endforeach; ?>
            </select>
            <small style="display:block;margin-top:6px;color:#6b7280;">
                <?php _e('Hold Ctrl/Cmd to select multiple templates for this product.', 'ds'); ?>
            </small>
        </p>
        <hr/>
        <p><strong><?php _e('Print Zone Pricing', 'ds'); ?></strong></p>
        <p>
            <label for="ds_pricing_mode"><?php _e('Pricing Mode', 'ds'); ?></label><br/>
            <select name="ds_pricing_mode" id="ds_pricing_mode" style="width:100%">
                <option value="sum" <?php selected($pricing_mode, 'sum'); ?>><?php _e('Sum of all selected zones', 'ds'); ?></option>
                <option value="max" <?php selected($pricing_mode, 'max'); ?>><?php _e('Highest selected zone only', 'ds'); ?></option>
            </select>
        </p>
        <div
            id="ds-zone-pricing-dynamic-root"
            data-template-zones="<?php echo esc_attr($template_zone_map_json); ?>"
            data-zone-prices="<?php echo esc_attr($zone_prices_json); ?>"
            data-empty-text="<?php echo esc_attr__('Select template(s) to configure zone prices.', 'ds'); ?>"
        >
            <div id="ds_zone_price_fields">
                <?php foreach ($zones as $zone_key => $zone_label): ?>
                    <?php $zone_val = isset($zone_prices[$zone_key]) ? (float) $zone_prices[$zone_key] : 0; ?>
                    <p>
                        <label for="ds_zone_price_<?php echo esc_attr($zone_key); ?>"><?php echo esc_html($zone_label); ?></label><br/>
                        <input type="number" step="0.01" min="0" name="ds_zone_price_<?php echo esc_attr($zone_key); ?>" id="ds_zone_price_<?php echo esc_attr($zone_key); ?>" value="<?php echo esc_attr($zone_val); ?>" style="width:100%" />
                    </p>
                <?php endforeach; ?>
            </div>
        </div>
        <script>
        (function(){
            var root = document.getElementById('ds-zone-pricing-dynamic-root');
            var templateSelect = document.getElementById('ds_template_ids');
            var fieldsWrap = document.getElementById('ds_zone_price_fields');
            if (!root || !templateSelect || !fieldsWrap) return;

            function parseJsonAttr(attrName) {
                try {
                    var raw = root.getAttribute(attrName) || '';
                    return raw ? JSON.parse(raw) : {};
                } catch (e) {
                    return {};
                }
            }

            function escHtml(v) {
                return String(v == null ? '' : v).replace(/[&<>"']/g, function(ch){
                    return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[ch] || ch;
                });
            }

            function escAttr(v) {
                return escHtml(v);
            }

            function sanitizeZoneKey(v) {
                return String(v == null ? '' : v).toLowerCase().replace(/[^a-z0-9_-]/g, '');
            }

            var templateZones = parseJsonAttr('data-template-zones');
            var zonePrices = parseJsonAttr('data-zone-prices');
            var emptyText = root.getAttribute('data-empty-text') || 'Select template(s) to configure zone prices.';

            function snapshotCurrentInputValues() {
                var inputs = fieldsWrap.querySelectorAll('input[name^="ds_zone_price_"]');
                for (var i = 0; i < inputs.length; i++) {
                    var input = inputs[i];
                    var key = sanitizeZoneKey(String(input.name || '').replace(/^ds_zone_price_/, ''));
                    if (!key) continue;
                    zonePrices[key] = input.value;
                }
            }

            function collectSelectedTemplateZones() {
                var out = {};
                var opts = templateSelect.options || [];
                for (var i = 0; i < opts.length; i++) {
                    var opt = opts[i];
                    if (!opt.selected) continue;
                    var tid = String(opt.value || '');
                    var map = (templateZones && typeof templateZones === 'object') ? templateZones[tid] : null;
                    if (!map || typeof map !== 'object') continue;
                    var keys = Object.keys(map);
                    for (var j = 0; j < keys.length; j++) {
                        var rawKey = keys[j];
                        var zoneKey = sanitizeZoneKey(rawKey);
                        if (!zoneKey || Object.prototype.hasOwnProperty.call(out, zoneKey)) continue;
                        var label = String(map[rawKey] == null ? '' : map[rawKey]).trim();
                        out[zoneKey] = label || zoneKey;
                    }
                }
                return out;
            }

            function renderZonePriceInputs() {
                snapshotCurrentInputValues();
                var zones = collectSelectedTemplateZones();
                var zoneKeys = Object.keys(zones);
                if (!zoneKeys.length) {
                    fieldsWrap.innerHTML = '<p class="description" style="margin:4px 0 8px;">' + escHtml(emptyText) + '</p>';
                    return;
                }
                var html = '';
                for (var i = 0; i < zoneKeys.length; i++) {
                    var key = zoneKeys[i];
                    var label = zones[key];
                    var val = Object.prototype.hasOwnProperty.call(zonePrices, key) ? zonePrices[key] : 0;
                    html += '<p>'
                        + '<label for="ds_zone_price_' + escAttr(key) + '">' + escHtml(label) + '</label><br/>'
                        + '<input type="number" step="0.01" min="0" name="ds_zone_price_' + escAttr(key) + '" id="ds_zone_price_' + escAttr(key) + '" value="' + escAttr(val) + '" style="width:100%" />'
                        + '</p>';
                }
                fieldsWrap.innerHTML = html;
            }

            templateSelect.addEventListener('change', renderZonePriceInputs);
            renderZonePriceInputs();
        })();
        </script>
        <?php
    }

    public static function render_product_data_fields() {
        global $post;
        if (!$post || $post->post_type !== 'product') return;

        $enabled = (bool) get_post_meta($post->ID, self::PRODUCT_META_ENABLE, true);
        $template_ids = self::get_product_template_ids((int) $post->ID);
        $template_labels = [];
        foreach ($template_ids as $tid) {
            $template = get_post((int) $tid);
            if ($template && $template->post_type === 'ds_template') {
                $template_labels[] = '#' . (int) $tid . ' - ' . $template->post_title;
            }
        }

        echo '<div class="options_group">';
        echo '<p class="form-field">';
        echo '<label>' . esc_html__('Design Studio Enabled', 'ds') . '</label>';
        echo '<span style="display:inline-block;padding-top:7px;">' . ($enabled ? esc_html__('Yes', 'ds') : esc_html__('No', 'ds')) . '</span>';
        echo '</p>';

        echo '<p class="form-field">';
        echo '<label>' . esc_html__('Design Templates', 'ds') . '</label>';
        if (!empty($template_labels)) {
            echo '<span style="display:inline-block;padding-top:7px;">' . esc_html(implode(', ', $template_labels)) . '</span>';
        } else {
            echo '<span style="display:inline-block;padding-top:7px;">' . esc_html__('Not selected', 'ds') . '</span>';
        }
        echo '</p>';

        echo '<p class="form-field">';
        echo '<label></label>';
        echo '<span style="display:inline-block;color:#6b7280;">' . esc_html__('Edit these values in the Design Studio box in the right sidebar.', 'ds') . '</span>';
        echo '</p>';
        echo '</div>';
    }

    public static function save_product_meta($post_id) {
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
        if (!current_user_can('edit_post', $post_id)) return;
        if (!isset($_POST['ds_product_meta_nonce']) || !wp_verify_nonce($_POST['ds_product_meta_nonce'], 'ds_product_meta')) return;
        self::persist_product_design_settings($post_id);
    }

    public static function save_product_data_fields($post_id) {
        if (!current_user_can('edit_post', $post_id)) return;
        self::persist_product_design_settings($post_id);
    }

    public static function save_product_object_fields($product) {
        if (!$product || !is_a($product, '\WC_Product')) return;
        $post_id = (int) $product->get_id();
        if ($post_id <= 0 || !current_user_can('edit_post', $post_id)) return;
        self::persist_product_design_settings($post_id);
    }

    private static function persist_product_design_settings($post_id) {
        $has_zone_price_field = false;
        foreach (array_keys((array) $_POST) as $field_name) {
            if (strpos((string) $field_name, 'ds_zone_price_') === 0) {
                $has_zone_price_field = true;
                break;
            }
        }
        $has_wc_fields = isset($_POST['ds_enable_design']) || isset($_POST['ds_enable_design_wc']) || isset($_POST['ds_template_id_wc']) || isset($_POST['ds_template_id']) || isset($_POST['ds_template_ids']) || isset($_POST['ds_pricing_mode']) || $has_zone_price_field;
        if (!$has_wc_fields) return;

        if (isset($_POST['ds_enable_design'])) {
            $raw_enabled = sanitize_text_field(wp_unslash($_POST['ds_enable_design']));
        } else {
            $raw_enabled = isset($_POST['ds_enable_design_wc']) ? sanitize_text_field(wp_unslash($_POST['ds_enable_design_wc'])) : '';
        }
        $enabled = in_array($raw_enabled, ['yes', '1', 'on', 'true'], true) ? '1' : '';
        update_post_meta($post_id, self::PRODUCT_META_ENABLE, $enabled);

        $template_ids = [];
        if (isset($_POST['ds_template_ids']) && is_array($_POST['ds_template_ids'])) {
            foreach ((array) wp_unslash($_POST['ds_template_ids']) as $tid_raw) {
                $tid = (int) $tid_raw;
                if ($tid > 0 && !in_array($tid, $template_ids, true)) $template_ids[] = $tid;
            }
        } else {
            $raw_template = null;
            if (isset($_POST['ds_template_id'])) {
                // Backward compatibility: single-select field.
                $raw_template = wp_unslash($_POST['ds_template_id']);
            } elseif (isset($_POST['ds_template_id_wc'])) {
                $raw_template = wp_unslash($_POST['ds_template_id_wc']);
            }
            if ($raw_template !== null) {
                $tid = (int) $raw_template;
                if ($tid > 0) $template_ids[] = $tid;
            }
        }
        update_post_meta($post_id, self::PRODUCT_META_TEMPLATES, array_values($template_ids));
        update_post_meta($post_id, self::PRODUCT_META_TEMPLATE, !empty($template_ids) ? (int) $template_ids[0] : 0);

        $pricing_mode = isset($_POST['ds_pricing_mode']) ? sanitize_text_field(wp_unslash($_POST['ds_pricing_mode'])) : 'sum';
        if (!in_array($pricing_mode, ['sum', 'max'], true)) $pricing_mode = 'sum';
        update_post_meta($post_id, self::PRODUCT_META_PRICING_MODE, $pricing_mode);

        if ($has_zone_price_field) {
            $zone_prices = [];
            foreach ((array) $_POST as $field_name => $raw_value) {
                $name = (string) $field_name;
                if (strpos($name, 'ds_zone_price_') !== 0) continue;
                $zone_key = sanitize_key(substr($name, strlen('ds_zone_price_')));
                if ($zone_key === '') continue;
                $raw = wp_unslash($raw_value);
                $val = max(0, (float) $raw);
                $zone_prices[$zone_key] = round($val, 2);
            }
            update_post_meta($post_id, self::PRODUCT_META_ZONE_PRICES, $zone_prices);
        }
    }

    private static function zone_labels() {
        return [
            'front_full' => __('Front Full', 'ds'),
            'top' => __('Top', 'ds'),
            'back_full' => __('Back Full', 'ds'),
            'chest_left' => __('Chest Left', 'ds'),
            'chest_right' => __('Chest Right', 'ds'),
            'sleeve_left' => __('Sleeve Left', 'ds'),
            'sleeve_right' => __('Sleeve Right', 'ds'),
            'side_left' => __('Side Left', 'ds'),
            'side_right' => __('Side Right', 'ds'),
        ];
    }

    private static function humanize_zone_key($zone_key) {
        $zone_key = sanitize_key((string) $zone_key);
        if ($zone_key === '') return '';
        return ucwords(str_replace(['_', '-'], ' ', $zone_key));
    }

    private static function get_template_zone_labels($template_id) {
        $template_id = (int) $template_id;
        if ($template_id <= 0) return [];
        $labels = [];
        $raw = get_post_meta($template_id, 'zones_json', true);
        if (is_string($raw) && $raw !== '') {
            $decoded = json_decode($raw, true);
            if (is_array($decoded)) {
                foreach ($decoded as $zone_row) {
                    if (!is_array($zone_row)) continue;
                    $zone_key = '';
                    if (!empty($zone_row['id'])) {
                        $zone_key = sanitize_key((string) $zone_row['id']);
                    }
                    if ($zone_key === '' && !empty($zone_row['zone_id'])) {
                        $zone_key = sanitize_key((string) $zone_row['zone_id']);
                    }
                    if ($zone_key === '') continue;
                    $label = '';
                    if (isset($zone_row['label'])) {
                        $label = trim((string) $zone_row['label']);
                    }
                    if ($label === '') {
                        $label = self::humanize_zone_key($zone_key);
                    }
                    if ($label === '') {
                        $label = $zone_key;
                    }
                    if (!isset($labels[$zone_key])) {
                        $labels[$zone_key] = $label;
                    }
                }
            }
        }

        // 2D multi-area templates usually keep canonical zone mapping in preview_config_json.mockups[].areas[].
        $preview_zone_labels = self::get_template_mockup_zone_labels($template_id);
        if (!empty($preview_zone_labels)) {
            $merged = $preview_zone_labels;
            foreach ($labels as $zone_key => $zone_label) {
                if (!isset($merged[$zone_key])) {
                    $merged[$zone_key] = $zone_label;
                }
            }
            return $merged;
        }
        return $labels;
    }

    private static function get_template_mockup_zone_labels($template_id) {
        $template_id = (int) $template_id;
        if ($template_id <= 0) return [];
        $raw = get_post_meta($template_id, 'preview_config_json', true);
        if (!is_string($raw) || $raw === '') return [];
        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) return [];
        $mockups = isset($decoded['mockups']) && is_array($decoded['mockups']) ? $decoded['mockups'] : [];
        if (empty($mockups)) return [];

        $labels = [];
        foreach ($mockups as $mockup_row) {
            if (!is_array($mockup_row) || empty($mockup_row['areas']) || !is_array($mockup_row['areas'])) continue;
            foreach ($mockup_row['areas'] as $zone_row) {
                if (!is_array($zone_row)) continue;
                $zone_key = '';
                if (!empty($zone_row['zone_id'])) {
                    $zone_key = sanitize_key((string) $zone_row['zone_id']);
                }
                if ($zone_key === '' && !empty($zone_row['id'])) {
                    $zone_key = sanitize_key((string) $zone_row['id']);
                }
                if ($zone_key === '') continue;
                $label = '';
                if (isset($zone_row['label'])) {
                    $label = trim((string) $zone_row['label']);
                }
                if ($label === '') {
                    $label = self::humanize_zone_key($zone_key);
                }
                if ($label === '') {
                    $label = $zone_key;
                }
                if (!isset($labels[$zone_key])) {
                    $labels[$zone_key] = $label;
                }
            }
        }
        return $labels;
    }

    private static function get_product_zone_labels($product_id, $stored_zone_prices = null) {
        $product_id = (int) $product_id;
        $labels = [];
        foreach (self::get_product_template_ids($product_id) as $template_id) {
            $template_labels = self::get_template_zone_labels((int) $template_id);
            foreach ($template_labels as $zone_key => $zone_label) {
                if (!isset($labels[$zone_key])) {
                    $labels[$zone_key] = $zone_label;
                }
            }
        }

        // Only use stored keys as fallback when no template zones are available.
        if (empty($labels) && is_array($stored_zone_prices)) {
            foreach (array_keys($stored_zone_prices) as $zone_key_raw) {
                $zone_key = sanitize_key((string) $zone_key_raw);
                if ($zone_key === '') continue;
                $fallback_label = self::humanize_zone_key($zone_key);
                $labels[$zone_key] = $fallback_label !== '' ? $fallback_label : $zone_key;
            }
        }

        if (empty($labels)) {
            return self::zone_labels();
        }
        return $labels;
    }

    private static function get_design_zone_labels($design_post_id) {
        $design_post_id = (int) $design_post_id;
        if ($design_post_id <= 0) return self::zone_labels();
        $template_id = (int) get_post_meta($design_post_id, 'template_id', true);
        $labels = self::get_template_zone_labels($template_id);
        return !empty($labels) ? $labels : self::zone_labels();
    }

    private static function get_product_zone_prices($product_id) {
        $stored = get_post_meta($product_id, self::PRODUCT_META_ZONE_PRICES, true);
        $out = [];
        if (is_array($stored)) {
            foreach ($stored as $zone_key_raw => $zone_price_raw) {
                $zone_key = sanitize_key((string) $zone_key_raw);
                if ($zone_key === '') continue;
                $out[$zone_key] = max(0, (float) $zone_price_raw);
            }
        }
        if (empty($out)) {
            foreach (array_keys(self::zone_labels()) as $zone_key) {
                $out[$zone_key] = 0.0;
            }
        }
        return $out;
    }

    private static function get_product_pricing_mode($product_id) {
        $mode = (string) get_post_meta($product_id, self::PRODUCT_META_PRICING_MODE, true);
        return in_array($mode, ['sum', 'max'], true) ? $mode : 'sum';
    }

    public static function get_product_template_ids($product_id) {
        $product_id = (int) $product_id;
        if ($product_id <= 0) return [];
        $ids = [];
        $raw = get_post_meta($product_id, self::PRODUCT_META_TEMPLATES, true);
        if (is_array($raw)) {
            foreach ($raw as $tid_raw) {
                $tid = (int) $tid_raw;
                if ($tid > 0 && !in_array($tid, $ids, true)) $ids[] = $tid;
            }
        } elseif (is_string($raw) && $raw !== '') {
            $decoded = json_decode($raw, true);
            if (is_array($decoded)) {
                foreach ($decoded as $tid_raw) {
                    $tid = (int) $tid_raw;
                    if ($tid > 0 && !in_array($tid, $ids, true)) $ids[] = $tid;
                }
            }
        }
        $single = (int) get_post_meta($product_id, self::PRODUCT_META_TEMPLATE, true);
        if ($single > 0 && !in_array($single, $ids, true)) $ids[] = $single;
        return array_values($ids);
    }

    private static function get_product_primary_template_id($product_id) {
        $ids = self::get_product_template_ids((int) $product_id);
        return !empty($ids) ? (int) $ids[0] : 0;
    }

    public static function product_uses_template($product_id, $template_id) {
        $tid = (int) $template_id;
        if ($tid <= 0) return false;
        return in_array($tid, self::get_product_template_ids((int) $product_id), true);
    }

    private static function get_design_zones_used($design_post_id) {
        $zones_raw = get_post_meta($design_post_id, 'zones_used', true);
        $zones = [];

        if (is_array($zones_raw)) {
            $zones = $zones_raw;
        } elseif (is_string($zones_raw) && $zones_raw !== '') {
            $decoded = json_decode($zones_raw, true);
            if (is_array($decoded)) $zones = $decoded;
        }

        if (empty($zones)) {
            // Backward compatibility with single-canvas designs.
            return ['front_full'];
        }

        $out = [];
        foreach ($zones as $z) {
            $key = trim((string) $z);
            if ($key === '') continue;
            $out[$key] = $key;
        }
        return array_values($out);
    }

    private static function get_design_zones_png_urls($design_post_id) {
        $raw = get_post_meta((int) $design_post_id, 'zones_png_urls', true);
        if (!is_array($raw)) return [];
        $out = [];
        foreach ($raw as $zid => $url) {
            $key = trim((string) $zid);
            $u = esc_url_raw((string) $url);
            if ($key === '' || $u === '') continue;
            $out[$key] = $u;
        }
        return $out;
    }

    private static function get_design_preview_images($design_post_id) {
        $raw = get_post_meta((int) $design_post_id, 'preview_images', true);
        if (!is_array($raw)) return [];
        $rows = [];
        foreach ($raw as $key => $row) {
            $id = sanitize_key((string) $key);
            if ($id === '') continue;
            $name = '';
            $url = '';
            $index = 9999;
            if (is_array($row)) {
                $name = sanitize_text_field((string) ($row['name'] ?? ''));
                $url = esc_url_raw((string) ($row['url'] ?? ''));
                $index = isset($row['index']) ? (int) $row['index'] : 9999;
            } elseif (is_string($row)) {
                $url = esc_url_raw($row);
            }
            if ($name === '') $name = ucfirst(str_replace('_', ' ', $id));
            if ($url === '') continue;
            $rows[] = [
                'id' => $id,
                'name' => $name,
                'url' => $url,
                'index' => $index,
            ];
        }
        usort($rows, function($a, $b){
            $ai = isset($a['index']) ? (int) $a['index'] : 9999;
            $bi = isset($b['index']) ? (int) $b['index'] : 9999;
            if ($ai === $bi) return strcmp((string) ($a['name'] ?? ''), (string) ($b['name'] ?? ''));
            return ($ai < $bi) ? -1 : 1;
        });
        return $rows;
    }

    private static function get_template_preview_mockups($template_id) {
        $template_id = (int) $template_id;
        if ($template_id <= 0) return [];
        $raw = (string) get_post_meta($template_id, 'preview_config_json', true);
        if ($raw === '') return [];
        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) return [];
        $mockups = isset($decoded['mockups']) && is_array($decoded['mockups']) ? $decoded['mockups'] : [];
        return is_array($mockups) ? $mockups : [];
    }

    private static function is_template_3d_preview($template_id) {
        $template_id = (int) $template_id;
        if ($template_id <= 0) return false;
        $mode = strtolower(trim((string) get_post_meta($template_id, 'preview_mode', true)));
        if ($mode === '3d') return true;
        if ($mode === '2d') return false;
        $type = strtolower(trim((string) get_post_meta($template_id, 'preview_type', true)));
        return in_array($type, ['mug', 'bottle', 'tshirt'], true);
    }

    private static function get_template_preview_profile($template_id) {
        $template_id = (int) $template_id;
        if ($template_id <= 0) return [];
        $preview_type = strtolower(trim((string) get_post_meta($template_id, 'preview_type', true)));
        $preview_mode = strtolower(trim((string) get_post_meta($template_id, 'preview_mode', true)));
        $template_width_mm = (float) get_post_meta($template_id, 'width_mm', true);
        $template_height_mm = (float) get_post_meta($template_id, 'height_mm', true);
        $raw_cfg = (string) get_post_meta($template_id, 'preview_config_json', true);
        $cfg = [];
        if ($raw_cfg !== '') {
            $decoded = json_decode($raw_cfg, true);
            if (is_array($decoded)) $cfg = $decoded;
        }
        $out_cfg = [
            'circumference_mm' => isset($cfg['circumference_mm']) ? (float) $cfg['circumference_mm'] : 0.0,
            'height_mm' => isset($cfg['height_mm']) ? (float) $cfg['height_mm'] : 0.0,
            'blank_handle_mm' => isset($cfg['blank_handle_mm']) ? (float) $cfg['blank_handle_mm'] : -1.0,
            'canvas_start_offset_mm' => isset($cfg['canvas_start_offset_mm']) ? (float) $cfg['canvas_start_offset_mm'] : -1.0,
            'canvas_height_pct' => isset($cfg['canvas_height_pct']) ? (float) $cfg['canvas_height_pct'] : 0.0,
            'model_scale' => isset($cfg['model_scale']) ? (float) $cfg['model_scale'] : 1.0,
            'model_rotation_y' => isset($cfg['model_rotation_y']) ? (float) $cfg['model_rotation_y'] : 0.0,
            'model_offset_x' => isset($cfg['model_offset_x']) ? (float) $cfg['model_offset_x'] : 0.0,
            'model_offset_z' => isset($cfg['model_offset_z']) ? (float) $cfg['model_offset_z'] : 0.0,
        ];
        return [
            'preview_type' => $preview_type ?: 'mug',
            'preview_mode' => $preview_mode ?: '',
            'template_width_mm' => $template_width_mm > 0 ? $template_width_mm : 0.0,
            'template_height_mm' => $template_height_mm > 0 ? $template_height_mm : 0.0,
            'model_url' => isset($cfg['model_url']) ? esc_url_raw((string) $cfg['model_url']) : '',
            'config' => $out_cfg,
        ];
    }

    private static function normalize_zone_id($value) {
        return trim((string) $value);
    }

    private static function normalize_zone_id_legacy($value) {
        return sanitize_key((string) $value);
    }

    private static function read_zone_map_value($map, $zone_id, $default = '') {
        if (!is_array($map)) return $default;
        $raw = self::normalize_zone_id($zone_id);
        if ($raw === '') return $default;
        if (array_key_exists($raw, $map)) return $map[$raw];
        $legacy = self::normalize_zone_id_legacy($raw);
        if ($legacy === '') return $default;
        $hits = [];
        foreach ($map as $k => $v) {
            if (self::normalize_zone_id_legacy($k) === $legacy) $hits[] = $v;
        }
        return count($hits) === 1 ? $hits[0] : $default;
    }

    private static function zone_in_used_set($used_set, $zone_id) {
        $raw = self::normalize_zone_id($zone_id);
        if ($raw === '') return false;
        if (!empty($used_set[$raw])) return true;
        $legacy = self::normalize_zone_id_legacy($raw);
        if ($legacy === '') return false;
        $hits = 0;
        foreach ($used_set as $k => $_v) {
            if (self::normalize_zone_id_legacy($k) === $legacy) $hits++;
        }
        return $hits === 1;
    }

    private static function get_design_preview_payload($design_post_id) {
        $design_post_id = (int) $design_post_id;
        if ($design_post_id <= 0) return [];

        $template_id = (int) get_post_meta($design_post_id, 'template_id', true);
        $preview_images = self::get_design_preview_images($design_post_id);

        // Cart preview should mirror what user saw in Designer Preview.
        // Saved preview_images are flattened per-mockup snapshots and are the
        // most reliable representation across multi-zone/multi-mockup templates.
        if (!empty($preview_images)) {
            $mockups = [];
            foreach ($preview_images as $i => $row) {
                $img = esc_url_raw((string) ($row['url'] ?? ''));
                if ($img === '') continue;
                $mockups[] = [
                    'name' => (string) ($row['name'] ?? ('Mockup ' . ($i + 1))),
                    'image_url' => $img,
                    'areas' => [],
                ];
            }
            if (!empty($mockups)) {
                return [
                    'design_post_id' => $design_post_id,
                    'template_id' => $template_id,
                    'mockups' => $mockups,
                ];
            }
        }

        $zones_png = self::get_design_zones_png_urls($design_post_id);
        $zones_png_map = [];
        if (is_array($zones_png)) {
            foreach ($zones_png as $zid => $url) {
                if (!is_string($url) || $url === '') continue;
                $raw = self::normalize_zone_id($zid);
                if ($raw !== '') $zones_png_map[$raw] = $url;
            }
        }
        $mockups_raw = get_post_meta($design_post_id, 'preview_mockups_snapshot', true);
        if (!is_array($mockups_raw) || empty($mockups_raw)) {
            $mockups_raw = self::get_template_preview_mockups($template_id);
        }
        if (empty($mockups_raw) || empty($zones_png_map)) return [];

        $out_mockups = [];
        $idx = 0;
        foreach ($mockups_raw as $m) {
            if (!is_array($m)) continue;
            $image_url = esc_url_raw((string) ($m['image_url'] ?? ''));
            if ($image_url === '') continue;
            $areas = isset($m['areas']) && is_array($m['areas']) ? $m['areas'] : [];
            $out_areas = [];
            foreach ($areas as $a) {
                if (!is_array($a)) continue;
                $zone_id_raw = self::normalize_zone_id($a['zone_id'] ?? '');
                $area_id_raw = self::normalize_zone_id($a['id'] ?? '');
                // Deterministic mapping lock:
                // - If zone_id is present, it is authoritative.
                // - area.id fallback is used only when zone_id is missing.
                $zone_id = $zone_id_raw !== '' ? $zone_id_raw : $area_id_raw;
                if ($zone_id === '') continue;
                $lookup_id = $zone_id_raw !== '' ? $zone_id_raw : $area_id_raw;
                $zone_png = (string) self::read_zone_map_value($zones_png_map, $lookup_id, '');
                if ($zone_png === '') continue;

                $x_pct = max(0, min(1, (float) ($a['x_pct'] ?? 0)));
                $y_pct = max(0, min(1, (float) ($a['y_pct'] ?? 0)));
                $w_pct = max(0.01, min(1, (float) ($a['w_pct'] ?? 0.25)));
                $h_pct = max(0.01, min(1, (float) ($a['h_pct'] ?? 0.25)));
                $label = (string) ($a['label'] ?? $zone_id);

                $out_areas[] = [
                    'zone_id' => $zone_id,
                    'label' => $label,
                    'x_pct' => $x_pct,
                    'y_pct' => $y_pct,
                    'w_pct' => $w_pct,
                    'h_pct' => $h_pct,
                    'png' => esc_url_raw($zone_png),
                ];
            }
            if (empty($out_areas)) continue;
            $out_mockups[] = [
                'name' => (string) ($m['name'] ?? ('Mockup ' . ($idx + 1))),
                'image_url' => $image_url,
                'areas' => $out_areas,
            ];
            $idx += 1;
        }

        if (!empty($out_mockups)) {
            return [
                'design_post_id' => $design_post_id,
                'template_id' => $template_id,
                'mockups' => $out_mockups,
            ];
        }

        return [];
    }

    private static function find_designer_page_url() {
        $pages = get_posts([
            'post_type' => 'page',
            'post_status' => 'publish',
            'numberposts' => 200,
        ]);
        foreach ($pages as $p) {
            $content = (string) $p->post_content;
            if (strpos($content, '[design_studio') !== false) {
                $url = get_permalink($p->ID);
                if ($url) return $url;
            }
        }
        return '';
    }

    private static function get_designer_edit_url($design_post_id) {
        $design_post_id = (int) $design_post_id;
        if ($design_post_id <= 0) return '';
        $base = self::find_designer_page_url();
        if (!$base) return '';
        $args = [
            'ds_design' => $design_post_id,
            'ds_embed' => 1,
            '_dsr' => time(),
        ];
        $template_id = (int) get_post_meta($design_post_id, 'template_id', true);
        if ($template_id > 0) $args['ds_template'] = $template_id;
        return add_query_arg($args, $base);
    }

    private static function get_designer_preview_url($design_post_id) {
        $url = self::get_designer_edit_url((int) $design_post_id);
        if ($url === '') return '';
        return add_query_arg([
            'ds_cart_preview' => 1,
            '_dsp' => time(),
        ], $url);
    }

    private static function compute_surcharge_for_product($product_id, $zones_used) {
        if (!is_array($zones_used) || empty($zones_used)) return 0.0;
        $prices = self::get_product_zone_prices($product_id);
        $mode = self::get_product_pricing_mode($product_id);

        $values = [];
        foreach ($zones_used as $z) {
            $key = sanitize_key((string) $z);
            $values[] = isset($prices[$key]) ? (float) $prices[$key] : 0.0;
        }
        if (empty($values)) return 0.0;

        if ($mode === 'max') return round(max($values), 2);
        return round(array_sum($values), 2);
    }

    public static function render_editor_if_enabled_summary_fallback() {
        self::render_editor_if_enabled();
    }

    public static function render_editor_footer_fallback() {
        if (!function_exists('is_product') || !is_product()) return;
        $pid = self::resolve_current_product_id();
        if ($pid <= 0) return;

        // If editor was already rendered in this request, do not output hidden fallback.
        if (self::$rendered_product_id === $pid) return;

        $enabled = (bool) get_post_meta($pid, self::PRODUCT_META_ENABLE, true);
        $template_ids = self::get_product_template_ids((int) $pid);
        if (!$enabled || empty($template_ids)) return;

        $markup = self::get_product_editor_markup($pid);
        if ($markup === '') return;

        // Hidden fallback: moved by JS only if no visible picker exists.
        echo '<div id="ds-footer-fallback" style="display:none">';
        echo $markup;
        echo '</div>';
        ?>
        <script>
        (function(){
          function moveFallback() {
            var fallback = document.getElementById('ds-footer-fallback');
            if (!fallback) return;
            if (document.querySelector('.ds-woo-editor')) {
              // Already rendered somewhere on page.
              fallback.remove();
              return;
            }
            var picker = fallback.querySelector('.ds-woo-editor');
            if (!picker) return;
            var cart = document.querySelector('form.cart');
            if (cart && cart.parentNode) {
              cart.parentNode.insertBefore(picker, cart.nextSibling);
            } else {
              document.body.appendChild(picker);
            }
            fallback.remove();
          }
          if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', moveFallback);
          } else {
            moveFallback();
          }
        })();
        </script>
        <?php
    }

    public static function inject_editor_into_product_content($content) {
        if (is_admin()) return $content;
        if (!function_exists('is_product') || !is_product()) return $content;
        if (!is_main_query() || !in_the_loop()) return $content;

        $pid = (int) get_the_ID();
        if ($pid <= 0) return $content;
        if (self::$rendered_product_id === $pid) return $content;

        $markup = self::get_product_editor_markup($pid);
        if ($markup === '') return $content;
        return $content . $markup;
    }

    public static function render_editor_if_enabled() {
        if (!function_exists('is_product') || !is_product()) return;
        $pid = self::resolve_current_product_id();
        if ($pid <= 0) return;
        $markup = self::get_product_editor_markup($pid);
        if ($markup !== '') echo $markup;
    }

    private static function resolve_current_product_id() {
        global $product, $post;
        if ($product && is_a($product, '\\WC_Product')) return (int) $product->get_id();
        if ($post && isset($post->ID) && get_post_type($post->ID) === 'product') return (int) $post->ID;
        $id = (int) get_the_ID();
        if ($id > 0 && get_post_type($id) === 'product') return $id;
        return 0;
    }

    private static function resolve_studio_page_url($template_id, $return_url, $product_id) {
        $base = '';
        $pages = get_posts([
            'post_type' => 'page',
            'post_status' => 'publish',
            'numberposts' => 1,
            's' => '[design_studio'
        ]);
        if (!empty($pages) && !empty($pages[0]->ID)) {
            $base = get_permalink((int) $pages[0]->ID);
        }
        if (!$base) {
            $base = home_url('/');
        }
        $args = [];
        if ((int) $template_id > 0) $args['ds_template'] = (int) $template_id;
        if ((int) $product_id > 0) $args['ds_product'] = (int) $product_id;
        if (!empty($return_url)) $args['ds_return'] = $return_url;
        return add_query_arg($args, $base);
    }

    private static function get_product_editor_markup($pid) {
        $enabled = (bool) get_post_meta($pid, self::PRODUCT_META_ENABLE, true);
        $template_ids = self::get_product_template_ids((int) $pid);
        if (!$enabled || empty($template_ids)) return '';
        if (self::$rendered_product_id === $pid) return '';

        self::$rendered_product_id = $pid;
        self::output_footer_sync_script_once();

        $return_url = get_permalink($pid);
        if (!$return_url) $return_url = home_url('/');
        $login_url = wp_login_url($return_url);
        $primary_template_id = count($template_ids) === 1 ? (int) $template_ids[0] : 0;
        $studio_url = self::resolve_studio_page_url($primary_template_id, $return_url, $pid);
        $preselect_design_id = isset($_GET['ds_design']) ? absint(wp_unslash($_GET['ds_design'])) : 0;
        $preselect_design_uuid = isset($_GET[self::CART_KEY]) ? sanitize_text_field(wp_unslash($_GET[self::CART_KEY])) : '';
        $preselect_png = isset($_GET['ds_design_png']) ? esc_url_raw(wp_unslash($_GET['ds_design_png'])) : '';
        $preselect_design_title = '';
        $preselect_zones_used = [];
        if ($preselect_design_id > 0) {
            $pre_t = get_the_title($preselect_design_id);
            if (is_string($pre_t) && $pre_t !== '') $preselect_design_title = $pre_t;
            $preselect_zones_used = self::get_design_zones_used($preselect_design_id);
        }
        $product_obj = function_exists('wc_get_product') ? wc_get_product((int) $pid) : null;
        $base_price = ($product_obj && is_a($product_obj, '\WC_Product')) ? (float) wc_get_price_to_display($product_obj) : 0.0;
        $boot = [
            'template_id' => (int) $primary_template_id,
            'template_ids' => array_values(array_map('intval', $template_ids)),
            'product_id' => (int) $pid,
            'studio_url' => esc_url_raw($studio_url),
            'return_url' => esc_url_raw($return_url),
            'rest_base' => esc_url_raw(rest_url(\DS_Plugin\Rest::NS)),
            'rest_nonce' => wp_create_nonce('wp_rest'),
            'logged_in' => is_user_logged_in(),
            'login_url' => $login_url,
            'preselect_design_id' => $preselect_design_id,
            'preselect_design_uuid' => $preselect_design_uuid,
            'preselect_png' => $preselect_png,
            'preselect_design_title' => $preselect_design_title,
            'preselect_zones_used' => array_values($preselect_zones_used),
            'pricing_mode' => self::get_product_pricing_mode((int) $pid),
            'zone_prices' => self::get_product_zone_prices((int) $pid),
            'base_price' => $base_price,
            'currency_symbol' => html_entity_decode(get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8'),
            'currency_pos' => get_option('woocommerce_currency_pos', 'left'),
            'price_decimals' => (int) wc_get_price_decimals(),
            'price_decimal_sep' => wc_get_price_decimal_separator(),
            'price_thousand_sep' => wc_get_price_thousand_separator(),
            'cart_key' => self::CART_KEY,
            'cart_key_png' => self::CART_KEY_PNG,
            'cart_key_post' => self::CART_KEY_POST,
        ];
        $boot_json = wp_json_encode($boot);

        ob_start();
        ?>
        <div class="ds-woo-editor ds-woo-picker" data-role="ds-woo-picker" data-product-id="<?php echo esc_attr((string) $pid); ?>">
            <style>
                .ds-woo-picker{margin:14px 0 18px;padding:12px;border:1px solid #d3dde8;border-radius:12px;background:#f8fbff}
                .ds-woo-picker-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;flex-wrap:wrap}
                .ds-woo-picker-head-main{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
                .ds-woo-picker-title{margin:0;font-size:14px;font-weight:700;color:#122033}
                .ds-woo-picker-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
                .ds-woo-btn{height:34px;border:1px solid #c5d1df;background:#fff;border-radius:8px;padding:0 12px;font-size:12px;font-weight:600;cursor:pointer;color:#122033}
                .ds-woo-btn-primary{background:#0a7f73;border-color:#0a7f73;color:#fff}
                .ds-woo-picked{font-size:12px;color:#5f7188}
                .ds-woo-picked-badge{display:inline-flex;align-items:center;min-height:24px;padding:4px 10px;border-radius:999px;border:1px solid #b9c9dd;background:#fff;font-size:11px;font-weight:700;color:#334a64}
                .ds-woo-picked-badge.is-selected{border-color:#8bb9a4;background:#ecfbf4;color:#146345}
                .ds-woo-picked-thumb{width:28px;height:28px;border-radius:6px;border:1px solid #cdd8e6;background:#fff center/contain no-repeat;display:none}
                .ds-woo-price-estimate{margin:8px 0 2px;font-size:12px;color:#22415f}
                .ds-woo-price-estimate strong{color:#0f172a}
                .ds-woo-picker-modal{position:fixed;inset:0;background:rgba(15,23,35,.45);display:none;align-items:center;justify-content:center;padding:16px;z-index:9999}
                .ds-woo-picker-modal.is-open{display:flex}
                .ds-woo-picker-dialog{width:min(920px,100%);max-height:calc(100vh - 28px);overflow:hidden;border:1px solid #bfcfe2;border-radius:12px;background:#f8fbff;box-shadow:0 18px 44px rgba(12,26,44,.26);padding:10px;display:flex;flex-direction:column}
                .ds-woo-picker-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}
                .ds-woo-picker-top h3{margin:0;font-size:14px;color:#2d3f56}
                .ds-woo-picker-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:10px;flex:1 1 auto;min-height:0;overflow:auto}
                .ds-woo-picker-item{border:1px solid #d6e1ee;border-radius:10px;background:#fff;padding:8px;display:flex;flex-direction:column;gap:8px}
                .ds-woo-picker-thumb{width:100%;height:140px;border:1px solid #d3dde8;border-radius:8px;background:#f5f8fc;overflow:hidden;display:flex;align-items:center;justify-content:center}
                .ds-woo-picker-thumb img{width:100%;height:100%;object-fit:contain;display:block;background:#fff}
                .ds-woo-picker-thumb-set{height:auto;min-height:140px;align-items:stretch;justify-content:stretch;padding:6px}
                .ds-woo-picker-set-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(62px,1fr));gap:6px;width:100%}
                .ds-woo-picker-set-item{border:1px solid #d3dde8;border-radius:6px;overflow:hidden;background:#fff}
                .ds-woo-picker-set-item img{width:100%;height:58px;object-fit:contain;display:block;background:#fff}
                .ds-woo-picker-set-item span{display:block;padding:2px 4px;font-size:10px;line-height:1.2;color:#5f7188;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
                .ds-woo-picker-meta{font-size:12px;color:#2d3f56}
                .ds-woo-picker-meta span{display:block;color:#6b7d95;font-size:11px}
                .ds-woo-picker-item .ds-woo-actions{display:flex;flex-direction:column;gap:6px}
                .ds-woo-picker-empty{border:1px dashed #c9d6e6;border-radius:10px;background:#fff;padding:12px;color:#5f7188;font-size:12px}
                .ds-woo-3d-modal{position:fixed;inset:0;background:rgba(15,23,35,.45);display:none;align-items:center;justify-content:center;padding:16px;z-index:10000}
                .ds-woo-3d-modal.is-open{display:flex}
                .ds-woo-3d-dialog{width:min(760px,100%);max-height:calc(100vh - 28px);overflow:hidden;border:1px solid #bfcfe2;border-radius:12px;background:#f8fbff;box-shadow:0 18px 44px rgba(12,26,44,.26);padding:10px;display:flex;flex-direction:column}
                .ds-woo-3d-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}
                .ds-woo-3d-top h3{margin:0;font-size:14px;color:#2d3f56}
                .ds-woo-3d-canvas{height:min(60vh,430px);border:1px solid #cdd8e6;border-radius:10px;overflow:hidden;background:radial-gradient(circle at 35% 20%, #fefefe 0%, #dde6f0 65%, #cdd9e8 100%);flex:1 1 auto;min-height:240px}
                .ds-woo-preview-note{margin:8px 0 0;padding:7px 10px;border:1px solid #d7e3f2;border-radius:8px;background:rgba(233,241,251,.62);color:#45607d;font-size:11px;line-height:1.35}
                body.admin-bar .ds-woo-picker-modal,
                body.admin-bar .ds-woo-3d-modal{padding-top:calc(16px + var(--wp-admin--admin-bar--height,32px))}
                body.admin-bar .ds-woo-picker-dialog,
                body.admin-bar .ds-woo-3d-dialog{max-height:calc(100dvh - 48px - var(--wp-admin--admin-bar--height,32px))}
                .ds-woo-picker-top,
                .ds-woo-3d-top{position:sticky;top:0;z-index:4;background:#f8fbff}
                @media screen and (max-width:782px){
                    body.admin-bar .ds-woo-picker-modal,
                    body.admin-bar .ds-woo-3d-modal{padding-top:calc(16px + var(--wp-admin--admin-bar--height,46px))}
                    body.admin-bar .ds-woo-picker-dialog,
                    body.admin-bar .ds-woo-3d-dialog{max-height:calc(100dvh - 48px - var(--wp-admin--admin-bar--height,46px))}
                }
            </style>

            <div class="ds-woo-picker-head">
                <div class="ds-woo-picker-head-main">
                    <p class="ds-woo-picker-title"><?php echo esc_html__('Design for this product', 'ds'); ?></p>
                    <span class="ds-woo-picked ds-woo-picked-badge" data-role="picked-design"><?php echo esc_html__('No design selected yet.', 'ds'); ?></span>
                    <span class="ds-woo-picked-thumb" data-role="picked-thumb" aria-hidden="true"></span>
                </div>
                <div class="ds-woo-picker-actions">
                    <button type="button" class="ds-woo-btn ds-woo-btn-primary" data-action="open-design-picker" data-role="choose-design-btn"><?php echo esc_html__('Choose Design', 'ds'); ?></button>
                    <button type="button" class="ds-woo-btn" data-action="open-design-creator"><?php echo esc_html__('Create New Design', 'ds'); ?></button>
                </div>
            </div>

            <input type="hidden" name="<?php echo esc_attr(self::CART_KEY); ?>" value="" />
            <input type="hidden" name="<?php echo esc_attr(self::CART_KEY_PNG); ?>" value="" />
            <input type="hidden" name="<?php echo esc_attr(self::CART_KEY_POST); ?>" value="" />
            <p class="ds-woo-hint"><?php echo esc_html__('Select one of your saved designs that matches one of this product templates.', 'ds'); ?></p>
            <p class="ds-woo-price-estimate" data-role="price-estimate"></p>

            <div class="ds-woo-picker-modal" data-role="design-picker-modal" aria-hidden="true">
                <div class="ds-woo-picker-dialog" role="dialog" aria-modal="true" aria-label="<?php echo esc_attr__('Choose Design', 'ds'); ?>">
                    <div class="ds-woo-picker-top">
                        <h3><?php echo esc_html__('My Designs for this Product', 'ds'); ?></h3>
                        <button type="button" class="ds-woo-btn" data-action="close-design-picker"><?php echo esc_html__('Close', 'ds'); ?></button>
                    </div>
                    <div class="ds-woo-picker-list" data-role="design-picker-list"></div>
                </div>
            </div>
            <div class="ds-woo-3d-modal" data-role="design-3d-modal" aria-hidden="true">
                <div class="ds-woo-3d-dialog" role="dialog" aria-modal="true" aria-label="<?php echo esc_attr__('3D Preview', 'ds'); ?>">
                    <div class="ds-woo-3d-top">
                        <h3><?php echo esc_html__('3D Preview', 'ds'); ?></h3>
                        <button type="button" class="ds-woo-btn" data-action="close-design-3d"><?php echo esc_html__('Close', 'ds'); ?></button>
                    </div>
                    <div class="ds-woo-3d-canvas" data-role="design-3d-canvas"></div>
                    <p class="ds-woo-preview-note"><?php echo esc_html__('Preview is approximate and for print placement guidance only. Final product may differ.', 'ds'); ?></p>
                </div>
            </div>

            <script type="application/json" data-role="design-picker-boot"><?php echo $boot_json; ?></script>
            <!-- Picker behavior is handled by footer fallback script for maximum theme/cache compatibility. -->
        </div>
        <?php
        return ob_get_clean();
    }
    private static function output_footer_sync_script_once() {
        if (self::$footer_sync_script_added) return;
        self::$footer_sync_script_added = true;
        add_action('wp_footer', function(){ ?>
            <script>
            (function(){
              var KEY_UUID = '<?php echo esc_js(self::CART_KEY); ?>';
              var KEY_PNG  = '<?php echo esc_js(self::CART_KEY_PNG); ?>';
              var KEY_POST = '<?php echo esc_js(self::CART_KEY_POST); ?>';
              window.DS_LAST_SAVED = window.DS_LAST_SAVED || null;

              function ensureField(form, name) {
                var el = form.querySelector('input[name="' + name + '"]');
                if (el) return el;
                el = document.createElement('input');
                el.type = 'hidden';
                el.name = name;
                el.value = '';
                form.appendChild(el);
                return el;
              }

              function syncForm(form, data) {
                if (!form || !data) return;
                ensureField(form, KEY_UUID).value = data.id || '';
                ensureField(form, KEY_PNG).value = data.png || '';
                ensureField(form, KEY_POST).value = data.design_post_id || '';
              }

              function cartForms() {
                var forms = Array.prototype.slice.call(document.querySelectorAll('form.cart'));
                if (!forms.length) {
                  forms = Array.prototype.slice.call(document.querySelectorAll('form[action*="add-to-cart"], form[action*="add_to_cart"]'));
                }
                return forms;
              }

              function syncAll() {
                if (!window.DS_LAST_SAVED) return;
                cartForms().forEach(function(f){ syncForm(f, window.DS_LAST_SAVED); });
              }

              if (!window.__DS_WOO_PICKER_FALLBACK__) {
                window.__DS_WOO_PICKER_FALLBACK__ = true;

                function q(sel, root){ return (root || document).querySelector(sel); }
                function qq(sel, root){ return Array.prototype.slice.call((root || document).querySelectorAll(sel)); }
                function closest(el, sel){ return el && el.closest ? el.closest(sel) : null; }
                function esc(v){
                  return String(v == null ? '' : v).replace(/[&<>"']/g, function(ch){
                    return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[ch] || ch;
                  });
                }
                function n(v, d){
                  var x = parseFloat(v);
                  return isFinite(x) ? x : (typeof d === 'number' ? d : 0);
                }
                function normalizeZoneKey(v){
                  return String(v == null ? '' : v).toLowerCase().replace(/[^a-z0-9_-]/g, '');
                }
                function formatMoney(amount, boot){
                  var value = n(amount, 0);
                  var decimals = Math.max(0, parseInt((boot && boot.price_decimals) || 2, 10) || 0);
                  var decSep = String((boot && boot.price_decimal_sep) || '.');
                  var thouSep = String((boot && boot.price_thousand_sep) || ',');
                  var symbol = String((boot && boot.currency_symbol) || '');
                  var pos = String((boot && boot.currency_pos) || 'left');
                  var neg = value < 0 ? '-' : '';
                  var abs = Math.abs(value);
                  var fixed = abs.toFixed(decimals);
                  var parts = fixed.split('.');
                  var intPart = parts[0];
                  var fracPart = parts.length > 1 ? parts[1] : '';
                  intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, thouSep);
                  var numberText = decimals > 0 ? (intPart + decSep + fracPart) : intPart;
                  if (pos === 'right') return neg + numberText + symbol;
                  if (pos === 'left_space') return neg + symbol + ' ' + numberText;
                  if (pos === 'right_space') return neg + numberText + ' ' + symbol;
                  return neg + symbol + numberText;
                }
                function computeSurchargeForZones(boot, zonesUsed){
                  var prices = (boot && boot.zone_prices && typeof boot.zone_prices === 'object') ? boot.zone_prices : {};
                  var mode = String((boot && boot.pricing_mode) || 'sum').toLowerCase() === 'max' ? 'max' : 'sum';
                  var zones = Array.isArray(zonesUsed) ? zonesUsed : [];
                  if (!zones.length) return 0;
                  var vals = [];
                  zones.forEach(function(z){
                    var key = normalizeZoneKey(z);
                    if (!key) return;
                    var v = n(prices[key], 0);
                    vals.push(v > 0 ? v : 0);
                  });
                  if (!vals.length) return 0;
                  if (mode === 'max') {
                    var m = 0;
                    vals.forEach(function(v){ if (v > m) m = v; });
                    return m;
                  }
                  var s = 0;
                  vals.forEach(function(v){ s += v; });
                  return s;
                }
                function getCurrentBasePrice(root, boot){
                  if (root && typeof root.__dsCurrentBasePrice === 'number' && isFinite(root.__dsCurrentBasePrice)) {
                    return root.__dsCurrentBasePrice;
                  }
                  return n(boot && boot.base_price, 0);
                }
                function updatePickedPrice(root, boot, zonesUsed){
                  if (!root) return;
                  var host = q('[data-role="price-estimate"]', root);
                  if (!host) return;
                  var base = getCurrentBasePrice(root, boot);
                  var surcharge = computeSurchargeForZones(boot, zonesUsed);
                  var total = Math.max(0, base + surcharge);
                  if (surcharge > 0) {
                    host.innerHTML = '<strong>Estimated total:</strong> ' + esc(formatMoney(total, boot)) + ' <span style="color:#607086;">(base ' + esc(formatMoney(base, boot)) + ' + print ' + esc(formatMoney(surcharge, boot)) + ')</span>';
                  } else {
                    host.innerHTML = '<strong>Estimated total:</strong> ' + esc(formatMoney(base, boot));
                  }
                }
                function attachVariationPriceSync(root, boot){
                  if (!root || root.__dsVariationSyncBound) return;
                  root.__dsVariationSyncBound = true;
                  var baseDefault = n(boot && boot.base_price, 0);
                  root.__dsCurrentBasePrice = baseDefault;
                  if (!window.jQuery || !window.jQuery.fn) return;
                  var form = closest(root, 'form.cart') || q('form.cart') || q('form[action*="add-to-cart"], form[action*="add_to_cart"]');
                  if (!form) return;
                  try {
                    var $f = window.jQuery(form);
                    $f.on('found_variation', function(_ev, variation){
                      if (variation && variation.display_price != null) {
                        root.__dsCurrentBasePrice = n(variation.display_price, baseDefault);
                        updatePickedPrice(root, boot, root.__dsPickedZones || []);
                      }
                    });
                    $f.on('reset_data hide_variation', function(){
                      root.__dsCurrentBasePrice = baseDefault;
                      updatePickedPrice(root, boot, root.__dsPickedZones || []);
                    });
                  } catch (e) {}
                }
                function pickerRoot(el){
                  return closest(el, '[data-role="ds-woo-picker"]') || q('[data-role="ds-woo-picker"]');
                }
                function pickerBoot(root){
                  var el = q('[data-role="design-picker-boot"]', root);
                  if (!el) return null;
                  try { return JSON.parse(el.textContent || '{}'); } catch(e) { return null; }
                }
                function openCreator(root, boot){
                  if (!boot || !boot.studio_url) return;
                  try {
                    var u = new URL(String(boot.studio_url), window.location.origin);
                    if (Number(boot.template_id || 0) > 0) u.searchParams.set('ds_template', String(boot.template_id));
                    if (Number(boot.product_id || 0) > 0) u.searchParams.set('ds_product', String(boot.product_id));
                    if (boot.return_url) u.searchParams.set('ds_return', String(boot.return_url));
                    u.searchParams.set('_dsr', String(Date.now()));
                    window.location.href = u.toString();
                  } catch (e) {
                    window.location.href = String(boot.studio_url);
                  }
                }
                function openPicker(root){
                  var m = q('[data-role="design-picker-modal"]', root);
                  if (!m) return;
                  m.classList.add('is-open');
                  m.setAttribute('aria-hidden', 'false');
                }
                function closePicker(root){
                  var m = q('[data-role="design-picker-modal"]', root);
                  if (!m) return;
                  m.classList.remove('is-open');
                  m.setAttribute('aria-hidden', 'true');
                }
                function listEl(root){ return q('[data-role="design-picker-list"]', root); }
                function updatePickedUi(root, designId, pngUrl, designTitle){
                  var badge = q('[data-role="picked-design"]', root);
                  var chooseBtn = q('[data-role="choose-design-btn"]', root);
                  var hasDesign = Number(designId || 0) > 0;
                  var label = String(designTitle || '').trim();
                  if (badge) {
                    badge.textContent = hasDesign ? ('Selected: ' + (label || ('#' + designId))) : 'No design selected yet.';
                    if (hasDesign) badge.classList.add('is-selected');
                    else badge.classList.remove('is-selected');
                  }
                  if (chooseBtn) {
                    chooseBtn.textContent = hasDesign ? 'Choose Another Design' : 'Choose Design';
                  }
                  var thumb = q('[data-role="picked-thumb"]', root);
                  if (thumb) {
                    if (pngUrl) {
                      thumb.style.backgroundImage = 'url(\"' + String(pngUrl).replace(/\"/g, '\\"') + '\")';
                      thumb.style.display = 'inline-block';
                    } else {
                      thumb.style.backgroundImage = '';
                      thumb.style.display = 'none';
                    }
                  }
                }
                function modal3dEl(root){ return q('[data-role="design-3d-modal"]', root); }
                function canvas3dEl(root){ return q('[data-role="design-3d-canvas"]', root); }
                function open3d(root){
                  var m = modal3dEl(root);
                  if (!m) return;
                  m.classList.add('is-open');
                  m.setAttribute('aria-hidden', 'false');
                }
                function close3d(root){
                  var m = modal3dEl(root);
                  if (!m) return;
                  m.classList.remove('is-open');
                  m.setAttribute('aria-hidden', 'true');
                }
                function ensureThreeLoaded(done){
                  if (window.THREE) { done(); return; }
                  if (window.__DS_WOO_THREE_LOADING__) {
                    var wait = function(){ if (window.THREE) done(); else setTimeout(wait, 60); };
                    wait();
                    return;
                  }
                  window.__DS_WOO_THREE_LOADING__ = true;
                  var s = document.createElement('script');
                  s.src = '<?php echo esc_js(DS_PLUGIN_URL . 'assets/vendor/three.min.js'); ?>';
                  s.onload = function(){ done(); };
                  s.onerror = function(){};
                  document.head.appendChild(s);
                }
                function initThree(root){
                  if (!root || !window.THREE) return null;
                  if (root.__dsThree) return root.__dsThree;
                  var canvasHost = canvas3dEl(root);
                  if (!canvasHost) return null;
                  var THREE = window.THREE;
                  var renderer;
                  try { renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); } catch(e) { return null; }
                  renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
                  canvasHost.innerHTML = '';
                  canvasHost.appendChild(renderer.domElement);
                  try { renderer.domElement.style.touchAction = 'none'; } catch (e) {}

                  var scene = new THREE.Scene();
                  var camera = new THREE.PerspectiveCamera(34, 1, 0.1, 100);
                  camera.position.set(0, -0.35, 5.35);
                  camera.lookAt(0, 0.10, 0);
                  scene.add(new THREE.HemisphereLight(0xffffff, 0xc4ccd8, 1.05));
                  var key = new THREE.DirectionalLight(0xffffff, 1.1); key.position.set(2.3, 3.5, 2.1); scene.add(key);
                  var rim = new THREE.DirectionalLight(0xbfd6ff, 0.7); rim.position.set(-2.1, 1.2, -2.2); scene.add(rim);

                  var mug = new THREE.Group(); scene.add(mug);
                  var mugMat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.28, metalness: 0.08 });
                  var outerR = 0.96, innerR = 0.86, bodyHeight = 2.15, bodyHalf = bodyHeight * 0.5;
                  mug.add(new THREE.Mesh(new THREE.CylinderGeometry(outerR, innerR, bodyHeight, 120, 1, true), mugMat));
                  var topCap = new THREE.Mesh(new THREE.CylinderGeometry(innerR * 0.99, innerR * 0.99, 0.05, 96), mugMat); topCap.position.y = bodyHalf + 0.025; mug.add(topCap);
                  var bottomCap = new THREE.Mesh(new THREE.CylinderGeometry(innerR * 0.92, innerR * 0.92, 0.08, 96), mugMat); bottomCap.position.y = -bodyHalf - 0.04; mug.add(bottomCap);
                  var labelMat = new THREE.MeshStandardMaterial({ transparent: true, roughness: 0.42, metalness: 0.02 });
                  var label = new THREE.Mesh(new THREE.CylinderGeometry(outerR + 0.015, innerR + 0.015, bodyHeight * 0.95, 200, 1, true, 0, Math.PI * 2), labelMat);
                  mug.add(label);

                  function resize() {
                    var w = Math.max(200, canvasHost.clientWidth || 200);
                    var h = Math.max(180, canvasHost.clientHeight || 180);
                    renderer.setSize(w, h, false);
                    camera.aspect = w / h;
                    camera.updateProjectionMatrix();
                  }
                  resize();

                  var dragging = false, lx = 0, ly = 0, yaw = 0.0, pitch = -0.03;
                  var dragYawMul = 1, dragPitchMul = 1;
                  var coarsePointer = !!(window.matchMedia && window.matchMedia('(pointer: coarse)').matches);
                  function clampPitch(v){ return Math.max(-0.34, Math.min(0.34, v)); }
                  renderer.domElement.addEventListener('pointerdown', function(e){
                    dragging = true; lx = e.clientX; ly = e.clientY;
                    var isTouchLike = (e && (e.pointerType === 'touch' || e.pointerType === 'pen')) || coarsePointer;
                    dragYawMul = isTouchLike ? 3.6 : 1;
                    dragPitchMul = isTouchLike ? 2.9 : 1;
                    if (isTouchLike && e && e.cancelable) e.preventDefault();
                    if (renderer.domElement.setPointerCapture) renderer.domElement.setPointerCapture(e.pointerId);
                  });
                  renderer.domElement.addEventListener('pointermove', function(e){
                    if (!dragging) return;
                    if (e && e.cancelable) e.preventDefault();
                    var dx = e.clientX - lx, dy = e.clientY - ly; lx = e.clientX; ly = e.clientY;
                    yaw += dx * 0.008 * dragYawMul;
                    pitch = clampPitch(pitch + (dy * 0.005 * dragPitchMul));
                  });
                  function endDrag(){ dragging = false; dragYawMul = 1; dragPitchMul = 1; }
                  renderer.domElement.addEventListener('pointerup', endDrag);
                  renderer.domElement.addEventListener('pointerleave', endDrag);
                  renderer.domElement.addEventListener('pointercancel', endDrag);

                  var autoYawSpeed = -0.0032;
                  (function tick(){
                    if (!dragging) yaw += autoYawSpeed;
                    mug.rotation.y = yaw;
                    mug.rotation.x = pitch;
                    renderer.render(scene, camera);
                    requestAnimationFrame(tick);
                  })();

                  root.__dsThree = {
                    resize: resize,
                    setImageUrl: function(url){
                      if (!url) return;
                      new THREE.TextureLoader().load(url, function(tex){
                        tex.colorSpace = THREE.SRGBColorSpace;
                        tex.wrapS = THREE.ClampToEdgeWrapping;
                        tex.wrapT = THREE.ClampToEdgeWrapping;
                        tex.repeat.set(1, 1);
                        tex.offset.set(0, 0);
                        labelMat.map = tex;
                        labelMat.needsUpdate = true;
                      });
                    }
                  };
                  window.addEventListener('resize', function(){ if (root.__dsThree && root.__dsThree.resize) root.__dsThree.resize(); });
                  return root.__dsThree;
                }
                function setForCart(root, boot, d){
                  var form = closest(root, 'form.cart') || q('form.cart') || q('form[action*="add-to-cart"], form[action*="add_to_cart"]');
                  if (!form || !boot || !d) return;
                  var uuid = d.uuid ? String(d.uuid) : ('design-' + d.id);
                  ensureField(form, boot.cart_key || KEY_UUID).value = uuid;
                  ensureField(form, boot.cart_key_png || KEY_PNG).value = d.png || '';
                  ensureField(form, boot.cart_key_post || KEY_POST).value = d.id || '';
                  root.__dsPickedZones = Array.isArray(d && d.zones_used) ? d.zones_used.slice() : [];
                  updatePickedUi(root, d.id, d.png || '', (d && d.title) ? String(d.title) : '');
                  updatePickedPrice(root, boot, root.__dsPickedZones || []);
                  window.DS_LAST_SAVED = { id: uuid, png: d.png || '', design_post_id: d.id, design_title: (d && d.title) ? String(d.title) : '' };
                  syncAll();
                }
                function renderPickerList(root, boot, items){
                  var list = listEl(root);
                  if (!list) return;
                  if (!items.length) {
                    list.innerHTML = '<div class="ds-woo-picker-empty">No saved designs for the selected product templates yet.</div>';
                    return;
                  }
                  function renderPickerThumb(d){
                    var zonesUsed = Array.isArray(d && d.zones_used) ? d.zones_used.map(function(z){ return String(z || ''); }).filter(Boolean) : [];
                    var zonesPng = (d && d.zones_png && typeof d.zones_png === 'object') ? d.zones_png : {};
                    if (zonesUsed.length) {
                      var cells = zonesUsed.map(function(zid){
                        var zpng = zonesPng[zid] ? String(zonesPng[zid]) : '';
                        if (!zpng) return '';
                        return '' +
                          '<div class="ds-woo-picker-set-item">' +
                            '<img src="' + esc(zpng) + '" alt="' + esc('Zone ' + zid) + '" loading="lazy">' +
                            '<span>' + esc(zid) + '</span>' +
                          '</div>';
                      }).filter(Boolean).join('');
                      if (cells) {
                        return '<div class="ds-woo-picker-thumb ds-woo-picker-thumb-set"><div class="ds-woo-picker-set-grid">' + cells + '</div></div>';
                      }
                    }
                    return d && d.png ? ('<div class="ds-woo-picker-thumb"><img src="' + esc(d.png) + '" alt="' + esc((d && d.title) ? d.title : ('Design #' + d.id)) + '" loading="lazy"></div>') : '';
                  }
                  list.innerHTML = items.map(function(d){
                    var date = d.created_at ? new Date(String(d.created_at)).toLocaleString() : '';
                    var title = String((d && d.title) ? d.title : ('Design #' + d.id));
                    return '' +
                      '<div class="ds-woo-picker-item">' +
                        renderPickerThumb(d) +
                        '<div class="ds-woo-picker-meta"><strong>' + esc(title) + '</strong><span>#' + esc(d.id) + (date ? (' | ' + esc(date)) : '') + '</span></div>' +
                        '<div class="ds-woo-actions">' +
                          '<button type="button" class="ds-woo-btn ds-woo-btn-primary" data-action="use-design-fallback" data-id="' + esc(d.id) + '">Use This Design</button>' +
                          '<button type="button" class="ds-woo-btn" data-action="preview-design-fallback" data-id="' + esc(d.id) + '">3D Preview</button>' +
                        '</div>' +
                      '</div>';
                  }).join('');
                  qq('[data-action="use-design-fallback"]', list).forEach(function(btn){
                    btn.addEventListener('click', function(){
                      var id = parseInt(btn.getAttribute('data-id') || '0', 10);
                      var chosen = null;
                      for (var i = 0; i < items.length; i++) {
                        if (Number(items[i].id) === Number(id)) { chosen = items[i]; break; }
                      }
                      if (!chosen) return;
                      setForCart(root, boot, chosen);
                      closePicker(root);
                    });
                  });
                  qq('[data-action="preview-design-fallback"]', list).forEach(function(btn){
                    btn.addEventListener('click', function(){
                      var id = parseInt(btn.getAttribute('data-id') || '0', 10);
                      var chosen = null;
                      for (var i = 0; i < items.length; i++) {
                        if (Number(items[i].id) === Number(id)) { chosen = items[i]; break; }
                      }
                      if (!chosen || !chosen.png) return;
                      ensureThreeLoaded(function(){
                        var p = initThree(root);
                        if (!p) return;
                        p.setImageUrl(chosen.png);
                        open3d(root);
                        setTimeout(function(){ if (p && p.resize) p.resize(); }, 40);
                        setTimeout(function(){ if (p && p.resize) p.resize(); }, 140);
                      });
                    });
                  });
                }
                function loadPickerDesigns(root, boot){
                  var list = listEl(root);
                  if (!list) return;
                  if (!boot || !boot.logged_in) {
                    list.innerHTML = '<div class="ds-woo-picker-empty">Please <a href="' + esc((boot && boot.login_url) ? boot.login_url : '#') + '">log in</a> to use your saved designs.</div>';
                    return;
                  }
                  list.innerHTML = '<div class="ds-woo-picker-empty">Loading...</div>';
                  fetch(String(boot.rest_base || '') + '/my-designs', {
                    method: 'GET',
                    headers: { 'X-WP-Nonce': boot.rest_nonce || '' }
                  }).then(function(r){
                    return r.json().catch(function(){ return {}; }).then(function(body){
                      if (!r.ok) throw new Error((body && body.message) ? String(body.message) : ('Request failed (' + r.status + ')'));
                      return body || {};
                    });
                  }).then(function(res){
                    var all = (res && Array.isArray(res.items)) ? res.items : [];
                    var allowedTemplateIds = Array.isArray(boot.template_ids)
                      ? boot.template_ids.map(function(v){ return Number(v || 0); }).filter(function(v){ return v > 0; })
                      : [];
                    if (!allowedTemplateIds.length && Number(boot.template_id || 0) > 0) {
                      allowedTemplateIds = [Number(boot.template_id || 0)];
                    }
                    var filtered = allowedTemplateIds.length
                      ? all.filter(function(d){ return allowedTemplateIds.indexOf(Number(d.template_id || 0)) !== -1; })
                      : all;
                    renderPickerList(root, boot, filtered);
                  }).catch(function(err){
                    list.innerHTML = '<div class="ds-woo-picker-empty">' + esc((err && err.message) ? err.message : 'Error loading designs.') + '</div>';
                  });
                }
                function hydrateZonesForDesign(root, boot, designId){
                  var did = parseInt(designId || '0', 10);
                  if (!root || !boot || !boot.rest_base || !boot.rest_nonce || !(did > 0)) return;
                  fetch(String(boot.rest_base) + '/design/' + did, {
                    method: 'GET',
                    headers: { 'X-WP-Nonce': boot.rest_nonce || '' }
                  }).then(function(r){
                    return r.json().catch(function(){ return {}; }).then(function(body){
                      if (!r.ok) throw new Error((body && body.message) ? String(body.message) : ('Request failed (' + r.status + ')'));
                      return body || {};
                    });
                  }).then(function(res){
                    var zones = Array.isArray(res && res.zones_used) ? res.zones_used.slice() : [];
                    root.__dsPickedZones = zones;
                    updatePickedPrice(root, boot, zones);
                  }).catch(function(){});
                }

                qq('[data-role="ds-woo-picker"]').forEach(function(rootInit){
                  var bootInit = pickerBoot(rootInit) || {};
                  attachVariationPriceSync(rootInit, bootInit);
                  rootInit.__dsPickedZones = [];
                  var idField = q('input[name="' + KEY_POST + '"]', rootInit) || q('input[name="ds_design_post_id"]', rootInit);
                  var pngField = q('input[name="' + KEY_PNG + '"]', rootInit) || q('input[name="ds_design_png"]', rootInit);
                  var designId = idField ? parseInt(idField.value || '0', 10) : 0;
                  var pngUrl = pngField ? (pngField.value || '') : '';
                  if ((!designId || designId <= 0) && Number(bootInit.preselect_design_id || 0) > 0) {
                    setForCart(rootInit, bootInit, {
                      id: Number(bootInit.preselect_design_id || 0),
                      uuid: String(bootInit.preselect_design_uuid || ''),
                      png: String(bootInit.preselect_png || ''),
                      title: String(bootInit.preselect_design_title || ''),
                      zones_used: Array.isArray(bootInit.preselect_zones_used) ? bootInit.preselect_zones_used.slice() : []
                    });
                    designId = Number(bootInit.preselect_design_id || 0);
                    pngUrl = String(bootInit.preselect_png || '');
                  }
                  if (designId > 0 || pngUrl) updatePickedUi(rootInit, designId, pngUrl, String(bootInit.preselect_design_title || ''));
                  else updatePickedUi(rootInit, 0, '', '');
                  if (designId > 0 && (!rootInit.__dsPickedZones || !rootInit.__dsPickedZones.length)) {
                    hydrateZonesForDesign(rootInit, bootInit, designId);
                  }
                  updatePickedPrice(rootInit, bootInit, rootInit.__dsPickedZones || []);
                });

                document.addEventListener('click', function(e){
                  var openBtn = closest(e.target, '[data-action="open-design-picker"]');
                  if (openBtn) {
                    var root = pickerRoot(openBtn);
                    if (!root) return;
                    var boot = pickerBoot(root);
                    openPicker(root);
                    loadPickerDesigns(root, boot);
                    return;
                  }

                  var createBtn = closest(e.target, '[data-action="open-design-creator"]');
                  if (createBtn) {
                    var rootC = pickerRoot(createBtn);
                    if (!rootC) return;
                    var bootC = pickerBoot(rootC);
                    openCreator(rootC, bootC);
                    return;
                  }

                  var closeBtn = closest(e.target, '[data-action="close-design-picker"]');
                  if (closeBtn) {
                    var root2 = pickerRoot(closeBtn);
                    if (root2) closePicker(root2);
                    return;
                  }

                  var overlay = closest(e.target, '[data-role="design-picker-modal"]');
                  if (overlay && e.target === overlay) {
                    var root3 = pickerRoot(overlay);
                    if (root3) closePicker(root3);
                    return;
                  }

                  var close3dBtn = closest(e.target, '[data-action="close-design-3d"]');
                  if (close3dBtn) {
                    var root4 = pickerRoot(close3dBtn);
                    if (root4) close3d(root4);
                    return;
                  }

                  var overlay3d = closest(e.target, '[data-role="design-3d-modal"]');
                  if (overlay3d && e.target === overlay3d) {
                    var root5 = pickerRoot(overlay3d);
                    if (root5) close3d(root5);
                  }
                }, true);
              }

              window.addEventListener('ds:designSaved', function(e){
                var d = (e && e.detail) ? e.detail : {};
                window.DS_LAST_SAVED = d;
                if (window.__DS_WOO_PICKER_FALLBACK__) {
                  var pid = d && d.design_post_id ? parseInt(d.design_post_id, 10) : 0;
                  var png = d && d.png ? String(d.png) : '';
                  if (pid > 0 || png) {
                    document.querySelectorAll('[data-role="ds-woo-picker"]').forEach(function(r){
                      var b = pickerBoot(r) || {};
                      r.__dsPickedZones = Array.isArray(d && d.zones_used) ? d.zones_used.slice() : [];
                      updatePickedUi(r, pid, png, d && d.design_title ? String(d.design_title) : '');
                      if ((!r.__dsPickedZones || !r.__dsPickedZones.length) && pid > 0) {
                        hydrateZonesForDesign(r, b, pid);
                      } else {
                        updatePickedPrice(r, b, r.__dsPickedZones || []);
                      }
                    });
                  }
                }
                syncAll();
              });

              document.addEventListener('submit', function(e){
                var form = e.target;
                if (!form || form.tagName !== 'FORM') return;
                if (window.DS_LAST_SAVED) syncForm(form, window.DS_LAST_SAVED);
              }, true);

              var mo = new MutationObserver(function(){ syncAll(); });
              mo.observe(document.documentElement, { childList: true, subtree: true });
              syncAll();
            })();
            </script>
        <?php });
    }

    public static function validate_design_before_add_to_cart($passed, $product_id, $qty) {
        $enabled = (bool) get_post_meta($product_id, self::PRODUCT_META_ENABLE, true);
        $template_ids = self::get_product_template_ids((int) $product_id);
        if ($enabled && !empty($template_ids)) {
            $uuid = isset($_REQUEST[self::CART_KEY]) ? sanitize_text_field(wp_unslash($_REQUEST[self::CART_KEY])) : '';
            $design_post_id = isset($_REQUEST[self::CART_KEY_POST]) ? intval($_REQUEST[self::CART_KEY_POST]) : 0;
            if ($design_post_id <= 0) {
                wc_add_notice(__('Please create and save a design before adding to cart.', 'ds'), 'error');
                return false;
            }

            $design_post = get_post($design_post_id);
            if (!$design_post || $design_post->post_type !== 'ds_design') {
                wc_add_notice(__('Selected design is invalid. Please save again.', 'ds'), 'error');
                return false;
            }

            $design_template_id = (int) get_post_meta($design_post_id, 'template_id', true);
            if (!in_array($design_template_id, $template_ids, true)) {
                wc_add_notice(__('Design template does not match this product.', 'ds'), 'error');
                return false;
            }

            $owner_id = (int) get_post_meta($design_post_id, 'user_id', true);
            $visibility = (string) get_post_meta($design_post_id, 'visibility', true);
            if ($visibility !== 'shared') $visibility = 'private';
            $current_user_id = (int) get_current_user_id();
            $stored_uuid = (string) get_post_meta($design_post_id, 'design_uuid', true);
            $guest_design_match = false;
            if ($owner_id <= 0) {
                if ($uuid !== '' && $stored_uuid !== '' && hash_equals($stored_uuid, $uuid)) {
                    $guest_design_match = true;
                } elseif ($uuid === ('design-' . $design_post_id)) {
                    $guest_design_match = true;
                }
            }
            $can_use =
                ($current_user_id > 0 && $owner_id > 0 && $owner_id === $current_user_id) ||
                ($visibility === 'shared') ||
                $guest_design_match;
            if (!$can_use) {
                wc_add_notice(__('You do not have access to this design.', 'ds'), 'error');
                return false;
            }
        }
        return $passed;
    }

    public static function add_cart_item_data($cart_item_data, $product_id, $variation_id) {
        $enabled = (bool) get_post_meta($product_id, self::PRODUCT_META_ENABLE, true);
        if (!$enabled) return $cart_item_data;

        $fields = [self::CART_KEY, self::CART_KEY_PNG, self::CART_KEY_POST];
        foreach ($fields as $f) {
            if (isset($_REQUEST[$f])) {
                $cart_item_data[$f] = sanitize_text_field(wp_unslash($_REQUEST[$f]));
            }
        }
        if (empty($cart_item_data[self::CART_KEY]) && !empty($cart_item_data[self::CART_KEY_POST])) {
            $cart_item_data[self::CART_KEY] = 'design-' . (int) $cart_item_data[self::CART_KEY_POST];
        }
        if (!empty($cart_item_data[self::CART_KEY])) {
            $cart_item_data['ds_unique_key'] = md5((string) $cart_item_data[self::CART_KEY] . '|' . microtime(true));
        }

        $design_post_id = !empty($cart_item_data[self::CART_KEY_POST]) ? (int) $cart_item_data[self::CART_KEY_POST] : 0;
        if ($design_post_id > 0) {
            $zones = self::get_design_zones_used($design_post_id);
            $surcharge = self::compute_surcharge_for_product($product_id, $zones);
            if (!empty($zones)) $cart_item_data[self::CART_KEY_ZONES] = array_values($zones);
            if ($surcharge > 0) $cart_item_data[self::CART_KEY_SURCHARGE] = (float) $surcharge;
        }
        return $cart_item_data;
    }

    public static function apply_zone_surcharge_to_cart($cart) {
        if (!is_object($cart) || !method_exists($cart, 'get_cart')) return;
        foreach ($cart->get_cart() as $cart_item_key => $item) {
            if (empty($item['data']) || !is_object($item['data'])) continue;
            if (empty($item[self::CART_KEY_POST])) continue;

            if (!isset($item[self::CART_KEY_BASE_PRICE])) {
                $item[self::CART_KEY_BASE_PRICE] = (float) $item['data']->get_price();
            }

            $base_price = (float) $item[self::CART_KEY_BASE_PRICE];
            $surcharge = isset($item[self::CART_KEY_SURCHARGE]) ? (float) $item[self::CART_KEY_SURCHARGE] : 0.0;
            $new_price = max(0, $base_price + $surcharge);
            $item['data']->set_price($new_price);
            $item[self::CART_KEY_PRICE_APPLIED] = 1;
            $cart->cart_contents[$cart_item_key] = $item;
        }
    }

    public static function render_cart_item_data($item_data, $cart_item) {
        $design_post_id = !empty($cart_item[self::CART_KEY_POST]) ? (int) $cart_item[self::CART_KEY_POST] : 0;
        $design_uuid = !empty($cart_item[self::CART_KEY]) ? sanitize_text_field((string) $cart_item[self::CART_KEY]) : '';
        if ($design_uuid === '' && $design_post_id > 0) {
            $design_uuid = 'design-' . $design_post_id;
        }
        if ($design_uuid === '' && $design_post_id <= 0) return $item_data;
        $design_title = $design_post_id > 0 ? sanitize_text_field((string) get_the_title($design_post_id)) : '';
        if ($design_title === '') {
            $design_title = $design_uuid;
        }

        $item_data[] = [
            'key' => __('Design', 'ds'),
            'value' => $design_title,
            'display' => $design_title,
        ];
        $preview_url = '';
        if (!empty($cart_item[self::CART_KEY_PNG])) {
            $preview_url = esc_url_raw((string) $cart_item[self::CART_KEY_PNG]);
        }
        if ($preview_url === '' && $design_post_id > 0) {
            $preview_url = esc_url_raw((string) get_post_meta($design_post_id, 'png_url', true));
            if ($preview_url === '') {
                $zones_png = self::get_design_zones_png_urls($design_post_id);
                if (!empty($zones_png)) {
                    $preview_url = esc_url_raw((string) reset($zones_png));
                }
            }
        }
        $preview_payload = $design_post_id > 0 ? self::get_design_preview_payload($design_post_id) : [];
        $preview_payload_json = !empty($preview_payload) ? wp_json_encode($preview_payload) : '';
        $is_3d_preview = false;
        $preview_type = '';
        $preview_profile_json = '';
        if ($design_post_id > 0) {
            $template_id = (int) get_post_meta($design_post_id, 'template_id', true);
            $preview_type = strtolower(trim((string) get_post_meta($template_id, 'preview_type', true)));
            $is_3d_preview = self::is_template_3d_preview($template_id);
            $profile = self::get_template_preview_profile($template_id);
            if (!empty($profile)) $preview_profile_json = wp_json_encode($profile);
        }
        if ($preview_url !== '' || $preview_payload_json !== '') {
            $preview_key = $design_post_id > 0
                ? ('post-' . $design_post_id)
                : ('uuid-' . md5((string) $design_uuid));
            $attrs = '';
            if ($preview_url !== '') {
                $attrs .= ' data-ds-preview-src="' . esc_attr(esc_url($preview_url)) . '"';
            }
            if ($preview_payload_json !== '') {
                $attrs .= ' data-ds-preview-payload="' . esc_attr($preview_payload_json) . '"';
            }
            if ($is_3d_preview) {
                $attrs .= ' data-ds-preview-3d="1"';
            }
            if ($preview_type !== '') {
                $attrs .= ' data-ds-preview-type="' . esc_attr($preview_type) . '"';
            }
            if ($preview_profile_json !== '') {
                $attrs .= ' data-ds-preview-profile="' . esc_attr($preview_profile_json) . '"';
            }
            $item_data[] = [
                'key' => __('Design Preview', 'ds'),
                'value' => $preview_url !== '' ? esc_url($preview_url) : sanitize_text_field((string) $cart_item[self::CART_KEY]),
                'display' => '<a href="#ds-preview-' . esc_attr($preview_key) . '" class="ds-cart-preview-link"' . $attrs . ' data-ds-preview-title="' . esc_attr($design_title) . '">' . esc_html__('Open Preview', 'ds') . '</a>',
            ];
        }
        if (!empty($cart_item[self::CART_KEY_PNG])) {
            $url = esc_url($cart_item[self::CART_KEY_PNG]);
            $item_data[] = [
                'key' => __('Design PNG', 'ds'),
                'value' => $url,
                'display' => '<a href="' . $url . '" target="_blank" rel="noopener">' . esc_html__('Open PNG', 'ds') . '</a>',
            ];
        }
        if ($design_post_id > 0) {
            $zones_png = self::get_design_zones_png_urls($design_post_id);
            if (!empty($zones_png)) {
                $labels = self::get_design_zone_labels($design_post_id);
                $links = [];
                foreach ($zones_png as $zid => $zurl) {
                    $zone_key = sanitize_key((string) $zid);
                    $name = isset($labels[$zone_key]) ? $labels[$zone_key] : self::humanize_zone_key($zone_key);
                    if ($name === '') $name = $zone_key !== '' ? $zone_key : (string) $zid;
                    $links[] = '<a href="' . esc_url($zurl) . '" target="_blank" rel="noopener">' . esc_html($name) . '</a>';
                }
                if (!empty($links)) {
                    $item_data[] = [
                        'key' => __('Zone PNGs', 'ds'),
                        'value' => implode(', ', array_keys($zones_png)),
                        'display' => implode(', ', $links),
                    ];
                }
            }
        }
        if (!empty($cart_item[self::CART_KEY_ZONES]) && is_array($cart_item[self::CART_KEY_ZONES])) {
            $labels = $design_post_id > 0 ? self::get_design_zone_labels($design_post_id) : self::zone_labels();
            $named = [];
            foreach ($cart_item[self::CART_KEY_ZONES] as $z) {
                $key = sanitize_key((string) $z);
                $name = isset($labels[$key]) ? $labels[$key] : self::humanize_zone_key($key);
                if ($name === '') $name = $key;
                $named[] = $name;
            }
            if (!empty($named)) {
                $item_data[] = [
                    'key' => __('Print Zones', 'ds'),
                    'value' => implode(', ', array_map('sanitize_text_field', $named)),
                    'display' => esc_html(implode(', ', $named)),
                ];
            }
        }
        if (isset($cart_item[self::CART_KEY_SURCHARGE]) && (float) $cart_item[self::CART_KEY_SURCHARGE] > 0) {
            $item_data[] = [
                'key' => __('Print Surcharge', 'ds'),
                'value' => (float) $cart_item[self::CART_KEY_SURCHARGE],
                'display' => wc_price((float) $cart_item[self::CART_KEY_SURCHARGE]),
            ];
        }
        return $item_data;
    }

    public static function render_cart_preview_modal() {
        if (is_admin()) return;
        if (!function_exists('WC') || !WC()) return;
        $preview_registry = [];
        if (function_exists('WC') && WC() && WC()->cart && method_exists(WC()->cart, 'get_cart')) {
            foreach ((array) WC()->cart->get_cart() as $item) {
                if (!is_array($item)) continue;
                $design_post_id = !empty($item[self::CART_KEY_POST]) ? (int) $item[self::CART_KEY_POST] : 0;
                $design_uuid = !empty($item[self::CART_KEY]) ? sanitize_text_field((string) $item[self::CART_KEY]) : '';
                if ($design_uuid === '' && $design_post_id > 0) $design_uuid = 'design-' . $design_post_id;
                if ($design_uuid === '' && $design_post_id <= 0) continue;
                $design_title = $design_post_id > 0 ? sanitize_text_field((string) get_the_title($design_post_id)) : '';
                if ($design_title === '') $design_title = $design_uuid;
                $preview_key = $design_post_id > 0 ? ('post-' . $design_post_id) : ('uuid-' . md5((string) $design_uuid));
                $preview_url = '';
                if (!empty($item[self::CART_KEY_PNG])) {
                    $preview_url = esc_url_raw((string) $item[self::CART_KEY_PNG]);
                }
                $template_id = $design_post_id > 0 ? (int) get_post_meta($design_post_id, 'template_id', true) : 0;
                $preview_type = $template_id > 0 ? strtolower(trim((string) get_post_meta($template_id, 'preview_type', true))) : '';
                $is_3d_preview = $template_id > 0 ? self::is_template_3d_preview($template_id) : false;
                if ($preview_url === '' && $design_post_id > 0) {
                    $preview_url = esc_url_raw((string) get_post_meta($design_post_id, 'png_url', true));
                    if ($preview_url === '') {
                        $zones_png = self::get_design_zones_png_urls($design_post_id);
                        if (!empty($zones_png)) $preview_url = esc_url_raw((string) reset($zones_png));
                    }
                }
                $payload = $design_post_id > 0 ? self::get_design_preview_payload($design_post_id) : [];
                $preview_registry[$preview_key] = [
                    'title' => $design_title,
                    'src' => $preview_url,
                    'payload' => !empty($payload) ? $payload : null,
                    'is_3d' => $is_3d_preview ? 1 : 0,
                    'preview_type' => $preview_type,
                    'preview_profile' => self::get_template_preview_profile($template_id),
                ];
            }
        }
        $preview_registry_json = wp_json_encode($preview_registry);
        ?>
        <style>
          .ds-cart-preview-modal{position:fixed;inset:0;display:none;align-items:flex-start;justify-content:center;z-index:99999;background:rgba(7,12,22,.66);padding:16px;overflow:auto}
          .ds-cart-preview-modal.is-open{display:flex}
          .ds-cart-preview-panel{width:min(96vw,1100px);max-height:calc(100dvh - 32px);background:#fff;border-radius:14px;border:1px solid rgba(15,23,42,.16);box-shadow:0 24px 60px rgba(0,0,0,.28);overflow:hidden;display:flex;flex-direction:column}
          .ds-cart-preview-head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid #e5e7eb;position:sticky;top:0;z-index:6;background:#fff}
          .ds-cart-preview-title{font-weight:600;color:#111827}
          .ds-cart-preview-close{border:1px solid #d1d5db;background:#fff;border-radius:8px;padding:6px 10px;cursor:pointer}
          .ds-cart-preview-body{padding:12px;display:grid;grid-template-columns:220px minmax(0,1fr);gap:12px;min-height:320px;background:#f8fafc;flex:1 1 auto;min-height:0;overflow:auto}
          .ds-cart-preview-tabs{display:flex;flex-direction:column;gap:8px;overflow:auto;max-height:75vh;padding-right:4px}
          .ds-cart-preview-tab{display:flex;align-items:center;gap:8px;border:1px solid #d3dde8;background:#fff;border-radius:10px;padding:8px;cursor:pointer;text-align:left}
          .ds-cart-preview-tab.is-active{border-color:#0a7f73;box-shadow:0 0 0 2px rgba(10,127,115,.14)}
          .ds-cart-preview-tab-index{width:20px;height:20px;border-radius:999px;background:#edf2f7;color:#334155;font-size:11px;font-weight:700;display:inline-flex;align-items:center;justify-content:center}
          .ds-cart-preview-tab-name{font-size:12px;font-weight:600;color:#1f2937;line-height:1.3}
          .ds-cart-preview-stage-wrap{display:flex;align-items:center;justify-content:center;border:1px solid #e2e8f0;border-radius:12px;background:linear-gradient(180deg,#f8fbff 0%,#eef3f8 100%);overflow:auto;min-height:280px}
          .ds-cart-preview-stage{position:relative;display:inline-block;max-width:100%;max-height:72vh}
          .ds-cart-preview-base{display:block;max-width:min(76vw,820px);max-height:72vh;object-fit:contain}
          .ds-cart-preview-ov{position:absolute;pointer-events:none}
          .ds-cart-preview-ov img{width:100%;height:100%;object-fit:contain;display:block}
          .ds-cart-preview-img{max-width:min(76vw,820px);max-height:72vh;display:none;object-fit:contain;background:#fff;border:1px solid #e2e8f0;border-radius:12px}
          .ds-cart-preview-note{margin:10px 12px 12px;padding:7px 10px;border:1px solid #d7e3f2;border-radius:8px;background:rgba(233,241,251,.62);color:#45607d;font-size:11px;line-height:1.35;position:sticky;bottom:0;z-index:5}
          .ds-cart-preview-3d{display:none;width:100%;max-width:940px;height:min(70vh,560px);min-height:260px;margin:0 auto;border:1px solid #d6e1ef;border-radius:12px;overflow:hidden;background:radial-gradient(circle at 35% 20%, #fefefe 0%, #dde6f0 65%, #cdd9e8 100%);position:relative}
          .ds-cart-preview-3d canvas{display:block;width:100% !important;height:100% !important;touch-action:none}
          .ds-cart-preview-modal.ds-cart-preview-3d-mode .ds-cart-preview-body{grid-template-columns:minmax(0,1fr)}
          .ds-cart-preview-modal.ds-cart-preview-3d-mode .ds-cart-preview-tabs{display:none}
          .ds-cart-preview-modal.ds-cart-preview-3d-mode .ds-cart-preview-stage-wrap{width:100%;min-height:min(74vh,600px);padding:10px;overflow:hidden}
          body.admin-bar .ds-cart-preview-modal{padding-top:calc(16px + var(--wp-admin--admin-bar--height,32px))}
          body.admin-bar .ds-cart-preview-panel{max-height:calc(100dvh - 32px - var(--wp-admin--admin-bar--height,32px))}
          @media (max-width: 880px){
            .ds-cart-preview-body{grid-template-columns:1fr;min-height:260px}
            .ds-cart-preview-tabs{flex-direction:row;overflow:auto;max-height:none;padding:0 0 6px}
            .ds-cart-preview-tab{min-width:180px}
            .ds-cart-preview-3d{width:100%;max-width:100%;height:min(58vh,460px);min-height:220px}
            .ds-cart-preview-modal.ds-cart-preview-3d-mode .ds-cart-preview-stage-wrap{min-height:min(62vh,500px);padding:8px}
            body.admin-bar .ds-cart-preview-modal{padding-top:calc(16px + var(--wp-admin--admin-bar--height,46px))}
            body.admin-bar .ds-cart-preview-panel{max-height:calc(100dvh - 32px - var(--wp-admin--admin-bar--height,46px))}
          }
        </style>
        <div class="ds-cart-preview-modal" data-ds-cart-preview-modal aria-hidden="true">
          <div class="ds-cart-preview-panel" role="dialog" aria-modal="true" aria-label="<?php echo esc_attr__('Design Preview', 'ds'); ?>">
            <div class="ds-cart-preview-head">
              <div class="ds-cart-preview-title" data-ds-cart-preview-title><?php echo esc_html__('Design Preview', 'ds'); ?></div>
              <button type="button" class="ds-cart-preview-close" data-ds-cart-preview-close><?php echo esc_html__('Close', 'ds'); ?></button>
            </div>
            <div class="ds-cart-preview-body">
              <div class="ds-cart-preview-tabs" data-ds-cart-preview-tabs></div>
              <div class="ds-cart-preview-stage-wrap">
                <div class="ds-cart-preview-stage" data-ds-cart-preview-stage style="display:none">
                  <img class="ds-cart-preview-base" data-ds-cart-preview-base alt="<?php echo esc_attr__('Mockup Preview', 'ds'); ?>" />
                  <div data-ds-cart-preview-overlays></div>
                </div>
                <div class="ds-cart-preview-3d" data-ds-cart-preview-3d style="display:none"></div>
              </div>
              <img class="ds-cart-preview-img" data-ds-cart-preview-img alt="<?php echo esc_attr__('Design Preview', 'ds'); ?>" />
            </div>
            <p class="ds-cart-preview-note"><?php echo esc_html__('Preview is approximate and for print placement guidance only. Final product may differ.', 'ds'); ?></p>
          </div>
        </div>
        <script>
        (function(){
          if (window.__dsCartPreviewBound) return;
          window.__dsCartPreviewBound = true;
          window.__dsCartPreviewRegistry = <?php echo $preview_registry_json ? $preview_registry_json : '{}'; ?>;
          const modal = document.querySelector('[data-ds-cart-preview-modal]');
          if (!modal) return;
          const img = modal.querySelector('[data-ds-cart-preview-img]');
          const tabs = modal.querySelector('[data-ds-cart-preview-tabs]');
          const stage = modal.querySelector('[data-ds-cart-preview-stage]');
          const base = modal.querySelector('[data-ds-cart-preview-base]');
          const overlays = modal.querySelector('[data-ds-cart-preview-overlays]');
          const stage3d = modal.querySelector('[data-ds-cart-preview-3d]');
          const ttl = modal.querySelector('[data-ds-cart-preview-title]');
          let activePayload = null;
          let activeIndex = 0;
          let active3d = null;

          function esc(v){
            return String(v == null ? '' : v).replace(/[&<>"']/g, function(ch){
              return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[ch] || ch;
            });
          }

          function previewKeyFromHref(href){
            const m = String(href || '').match(/^#ds-preview-(.+)$/);
            return (m && m[1]) ? String(m[1]) : '';
          }

          function parseJsonSafe(raw){
            if (!raw) return null;
            try { return JSON.parse(String(raw)); } catch (e) { return null; }
          }

          function updateRegistryFromLink(link){
            if (!link || !window.__dsCartPreviewRegistry) return;
            const key = previewKeyFromHref(link.getAttribute('href') || '');
            if (!key) return;
            const row = window.__dsCartPreviewRegistry[key] && typeof window.__dsCartPreviewRegistry[key] === 'object'
              ? window.__dsCartPreviewRegistry[key]
              : {};
            const title = String(link.getAttribute('data-ds-preview-title') || '');
            const src = String(link.getAttribute('data-ds-preview-src') || '');
            const payloadRaw = String(link.getAttribute('data-ds-preview-payload') || '');
            const profileRaw = String(link.getAttribute('data-ds-preview-profile') || '');
            const is3d = String(link.getAttribute('data-ds-preview-3d') || '') === '1';
            if (title) row.title = title;
            if (src) row.src = src;
            if (payloadRaw) {
              const p = parseJsonSafe(payloadRaw);
              if (p) row.payload = p;
            }
            if (profileRaw) {
              const prof = parseJsonSafe(profileRaw);
              if (prof) row.preview_profile = prof;
            }
            if (is3d) row.is_3d = 1;
            window.__dsCartPreviewRegistry[key] = row;
          }

          function refreshPreviewRegistryFromDom(scope){
            const root = scope && scope.querySelectorAll ? scope : document;
            const links = root.querySelectorAll ? root.querySelectorAll('.ds-cart-preview-link') : [];
            if (!links || !links.length) return;
            links.forEach(function(link){ updateRegistryFromLink(link); });
          }

          function clearStructuredPreview(){
            activePayload = null;
            activeIndex = 0;
            if (tabs) tabs.innerHTML = '';
            if (stage) stage.style.display = 'none';
            if (base) base.removeAttribute('src');
            if (overlays) overlays.innerHTML = '';
            if (stage3d) stage3d.style.display = 'none';
            modal.classList.remove('ds-cart-preview-3d-mode');
          }

          function closeModal(){
            modal.classList.remove('is-open');
            modal.setAttribute('aria-hidden','true');
            if (img) {
              img.removeAttribute('src');
              img.style.display = 'none';
            }
            if (active3d && active3d.dispose) {
              try { active3d.dispose(); } catch (e) {}
            }
            active3d = null;
            clearStructuredPreview();
          }

          function ensureThreeLoaded(done){
            if (window.THREE) { done(); return; }
            if (window.__DS_CART_THREE_LOADING__) {
              const wait = function(){ if (window.THREE) done(); else setTimeout(wait, 60); };
              wait();
              return;
            }
            window.__DS_CART_THREE_LOADING__ = true;
            const s = document.createElement('script');
            s.src = '<?php echo esc_js(DS_PLUGIN_URL . 'assets/vendor/three.min.js'); ?>';
            s.onload = function(){ done(); };
            s.onerror = function(){};
            document.head.appendChild(s);
          }

          function ensureGltfLoaderLoaded(done){
            if (window.THREE && window.THREE.GLTFLoader) { done(true); return; }
            if (window.THREE && window.GLTFLoader) {
              window.THREE.GLTFLoader = window.GLTFLoader;
              done(true);
              return;
            }
            if (window.__DS_CART_GLTF_LOADING__) {
              const wait = function(){
                if (window.THREE && window.THREE.GLTFLoader) done(true);
                else if (window.THREE && window.GLTFLoader) {
                  window.THREE.GLTFLoader = window.GLTFLoader;
                  done(true);
                }
                else if (window.__DS_CART_GLTF_LOADING__) setTimeout(wait, 60);
                else done(false);
              };
              wait();
              return;
            }
            window.__DS_CART_GLTF_LOADING__ = true;
            const urls = [
              '<?php echo esc_js(DS_PLUGIN_URL . 'assets/vendor/GLTFLoader.min.js'); ?>'
            ];
            let idx = 0;
            const next = function(){
              if (window.THREE && window.THREE.GLTFLoader) {
                window.__DS_CART_GLTF_LOADING__ = false;
                done(true);
                return;
              }
              if (window.THREE && window.GLTFLoader) {
                window.THREE.GLTFLoader = window.GLTFLoader;
                window.__DS_CART_GLTF_LOADING__ = false;
                done(true);
                return;
              }
              if (idx >= urls.length) {
                window.__DS_CART_GLTF_LOADING__ = false;
                done(false);
                return;
              }
              const s = document.createElement('script');
              s.src = urls[idx++];
              s.onload = next;
              s.onerror = next;
              document.head.appendChild(s);
            };
            next();
          }

          function init3dStage(profile){
            if (!stage3d || !window.THREE) return null;
            if (active3d) return active3d;
            const THREE = window.THREE;
            const p = profile && typeof profile === 'object' ? profile : {};
            const pType = String((p.preview_type || 'mug')).toLowerCase();
            const pCfg = (p.config && typeof p.config === 'object') ? p.config : {};
            const modelUrl = String(p.model_url || pCfg.model_url || '').trim();
            const modelScaleCfg = Number(p.model_scale || pCfg.model_scale || 1);
            const modelScale = (isFinite(modelScaleCfg) && modelScaleCfg > 0) ? modelScaleCfg : 1;
            const modelRotYCfg = Number(p.model_rotation_y || pCfg.model_rotation_y || 0);
            const modelRotY = isFinite(modelRotYCfg) ? modelRotYCfg : 0;
            const modelOffsetXCfg = Number(p.model_offset_x || pCfg.model_offset_x || 0);
            const modelOffsetX = isFinite(modelOffsetXCfg) ? modelOffsetXCfg : 0;
            const modelOffsetZCfg = Number(p.model_offset_z || pCfg.model_offset_z || 0);
            const modelOffsetZ = isFinite(modelOffsetZCfg) ? modelOffsetZCfg : 0;
            const templateWidthMm = Math.max(1, Number(p.template_width_mm || 200));
            const templateHeightMm = Math.max(1, Number(p.template_height_mm || 90));
            const cupCircMm = Number(pCfg.circumference_mm || 0) > 0 ? Number(pCfg.circumference_mm || 0) : (pType === 'bottle' ? 250 : 230);
            const cupHeightMm = Number(pCfg.height_mm || 0) > 0 ? Number(pCfg.height_mm || 0) : templateHeightMm;
            const cfgBlank = Number(pCfg.blank_handle_mm);
            const cfgStart = Number(pCfg.canvas_start_offset_mm);
            const cfgHeightPct = Number(pCfg.canvas_height_pct);
            const blankMm = cfgBlank >= 0 ? cfgBlank : Math.max(0, cupCircMm - templateWidthMm);
            const printMm = Math.max(1, cupCircMm - blankMm);
            const startOffsetMm = cfgStart >= 0 ? cfgStart : Math.max(0, (blankMm / 2));
            const labelHeightPct = (cfgHeightPct > 0 && cfgHeightPct <= 1) ? cfgHeightPct : 0.95;
            let renderer;
            try { renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); } catch (e) { return null; }
            renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
            try {
              renderer.domElement.style.width = '100%';
              renderer.domElement.style.height = '100%';
              renderer.domElement.style.display = 'block';
              renderer.domElement.style.touchAction = 'none';
            } catch (e) {}
            stage3d.innerHTML = '';
            stage3d.appendChild(renderer.domElement);

            const scene = new THREE.Scene();
            const camera = new THREE.PerspectiveCamera(34, 1, 0.1, 100);
            camera.position.set(0, -0.35, 5.35);
            camera.lookAt(0, 0.10, 0);
            scene.add(new THREE.HemisphereLight(0xffffff, 0xc4ccd8, 1.05));
            const key = new THREE.DirectionalLight(0xffffff, 1.1); key.position.set(2.3, 3.5, 2.1); scene.add(key);
            const rim = new THREE.DirectionalLight(0xbfd6ff, 0.7); rim.position.set(-2.1, 1.2, -2.2); scene.add(rim);

            const mug = new THREE.Group(); mug.scale.setScalar(0.66); scene.add(mug);
            const mugCore = new THREE.Group(); mug.add(mugCore);
            const mugMat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.62, metalness: 0.05 });
            const mmToUnit = 1 / 48;
            const outerR = (cupCircMm / (Math.PI * 2)) * mmToUnit;
            const innerR = outerR * (pType === 'bottle' ? 0.965 : 0.94);
            const bodyHeight = cupHeightMm * mmToUnit;
            const bodyHalf = bodyHeight * 0.5;
            const labelMat = new THREE.MeshStandardMaterial({
              color: 0xffffff,
              roughness: 0.55,
              metalness: 0.08,
              transparent: true
            });
            const thetaPerMm = (Math.PI * 2) / cupCircMm;
            const handleTheta = 0;
            const startTheta = handleTheta - (startOffsetMm * thetaPerMm);
            const thetaLength = printMm * thetaPerMm;
            const thetaStart = startTheta - thetaLength;
            const thetaGap = Math.max(0, (Math.PI * 2) - thetaLength);
            const gapCenterTheta = thetaStart + thetaLength + (thetaGap * 0.5);
            const labelHeight = bodyHeight * labelHeightPct;
            const label = new THREE.Mesh(
              new THREE.CylinderGeometry(outerR + 0.006, outerR + 0.006, labelHeight, 128, 1, true, thetaStart, thetaLength),
              labelMat
            );
            mug.add(label);

            function frameMugToView(){
              try {
                const fitTarget = (mugCore && mugCore.children && mugCore.children.length) ? mugCore : mug;
                fitTarget.updateMatrixWorld(true);
                const box = new THREE.Box3().setFromObject(fitTarget);
                const sphere = box.getBoundingSphere(new THREE.Sphere());
                const center = sphere.center || new THREE.Vector3(0, 0, 0);
                const radius = Math.max(0.001, Number(sphere.radius || 1));
                // Keep the rotator pivot centered on the visible model
                mug.position.set(-center.x, -center.y, -center.z);
                mug.updateMatrixWorld(true);
                const fov = (camera.fov * Math.PI) / 180;
                const halfV = Math.max(0.001, fov * 0.5);
                const halfH = Math.atan(Math.tan(halfV) * Math.max(0.35, camera.aspect || 1));
                const fitV = radius / Math.sin(halfV);
                const fitH = radius / Math.sin(Math.max(0.001, halfH));
                const fitD = Math.max(fitV, fitH, radius * 2.1);
                const viewPad = (camera.aspect || 1) < 0.90 ? 1.90 : (((camera.aspect || 1) < 1.20) ? 1.76 : 1.66);
                const dist = (isFinite(fitD) ? fitD : 4) * viewPad * 0.77;
                const yLift = (camera.aspect || 1) < 1 ? (radius * 0.01) : (radius * 0.03);
                camera.position.set(0, yLift, dist);
                camera.lookAt(0, 0, 0);
                camera.near = Math.max(0.01, dist / 180);
                camera.far = Math.max(60, dist * 28);
                camera.updateProjectionMatrix();
              } catch (e) {}
            }
            function buildProceduralCup(){
              mugCore.add(new THREE.Mesh(
                new THREE.CylinderGeometry(outerR, pType === 'bottle' ? (innerR * 0.98) : innerR, bodyHeight, 96, 1, true),
                mugMat
              ));
              const topCap = new THREE.Mesh(new THREE.CylinderGeometry(innerR * 0.99, innerR * 0.99, 0.05, 96), mugMat);
              topCap.position.y = bodyHalf + 0.025;
              mugCore.add(topCap);
              const bottomCap = new THREE.Mesh(new THREE.CylinderGeometry(innerR * 0.92, innerR * 0.92, 0.08, 96), mugMat);
              bottomCap.position.y = -bodyHalf - 0.04;
              mugCore.add(bottomCap);

              // Handle intentionally disabled for cart preview; keep cylinder/bottle only.
              frameMugToView();
            }

            function tryLoadModel(){
              if (!modelUrl || !(window.THREE && window.THREE.GLTFLoader)) return false;
              try {
                const loader = new THREE.GLTFLoader();
                loader.load(modelUrl, function(gltf){
                  try {
                    const glbRoot = (gltf && gltf.scene) ? gltf.scene : null;
                    if (!glbRoot) { buildProceduralCup(); return; }

                    // Robust normalize: fit model into predictable bounds + center to origin.
                    glbRoot.updateMatrixWorld(true);
                    const boxA = new THREE.Box3().setFromObject(glbRoot);
                    const sizeA = boxA.getSize(new THREE.Vector3());
                    const srcMax = Math.max(
                      0.0001,
                      Number(sizeA.x || 0),
                      Number(sizeA.y || 0),
                      Number(sizeA.z || 0)
                    );
                    const targetMax = Math.max(bodyHeight * 1.28, outerR * 2.45);
                    const fitScale = (targetMax / srcMax) * modelScale;
                    glbRoot.scale.setScalar(fitScale);
                    glbRoot.rotation.y = modelRotY;

                    glbRoot.updateMatrixWorld(true);
                    const boxB = new THREE.Box3().setFromObject(glbRoot);
                    const centerB = boxB.getCenter(new THREE.Vector3());
                    glbRoot.position.sub(centerB);

                    // Keep mug body vertically centered with preview label.
                    glbRoot.updateMatrixWorld(true);
                    const boxC = new THREE.Box3().setFromObject(glbRoot);
                    const centerC = boxC.getCenter(new THREE.Vector3());
                    glbRoot.position.y -= centerC.y;
                    glbRoot.position.x += modelOffsetX;
                    glbRoot.position.z += modelOffsetZ;

                    glbRoot.traverse(function(n){
                      if (n && n.isMesh) {
                        n.castShadow = false;
                        n.receiveShadow = true;
                        if (n.material && typeof n.material === 'object') {
                          n.material.transparent = !!n.material.transparent;
                          n.material.depthWrite = true;
                        }
                      }
                    });
                    mugCore.add(glbRoot);
                    frameMugToView();
                    try { stage3d.setAttribute('data-ds-glb', 'ok'); } catch (e) {}
                  } catch (e) {
                    try { stage3d.setAttribute('data-ds-glb', 'error'); } catch (er) {}
                    try { console.warn('[DS cart 3D] GLB parse/render failed', e); } catch (er2) {}
                    buildProceduralCup();
                  }
                }, undefined, function(){
                  try { stage3d.setAttribute('data-ds-glb', 'error'); } catch (er) {}
                  try { console.warn('[DS cart 3D] GLB load failed', modelUrl); } catch (er2) {}
                  buildProceduralCup();
                });
                return true;
              } catch (e) {
                try { stage3d.setAttribute('data-ds-glb', 'error'); } catch (er) {}
                try { console.warn('[DS cart 3D] GLTFLoader init failed', e); } catch (er2) {}
                return false;
              }
            }

            if (!tryLoadModel()) {
              if (modelUrl) {
                ensureGltfLoaderLoaded(function(ok){
                  if (!ok || !tryLoadModel()) buildProceduralCup();
                });
              } else {
                buildProceduralCup();
              }
            }

            function resize(){
              const w = Math.max(220, stage3d.clientWidth || 220);
              const h = Math.max(220, stage3d.clientHeight || 220);
              renderer.setSize(w, h, true);
              camera.aspect = w / h;
              camera.updateProjectionMatrix();
              frameMugToView();
            }
            resize();
            let ro = null;
            try {
              ro = new ResizeObserver(function(){ resize(); });
              ro.observe(stage3d);
            } catch (e) {}

            const autoYawSpeed = (pType === 'tshirt') ? -0.0015 : -0.0032;
            let dragging = false, lx = 0, ly = 0, yaw = 0.0, pitch = -0.03;
            let dragYawMul = 1, dragPitchMul = 1;
            const coarsePointer = !!(window.matchMedia && window.matchMedia('(pointer: coarse)').matches);
            function clampPitch(v){ return Math.max(-0.34, Math.min(0.34, v)); }
            renderer.domElement.addEventListener('pointerdown', function(e){
              dragging = true; lx = e.clientX; ly = e.clientY;
              const isTouchLike = (e && (e.pointerType === 'touch' || e.pointerType === 'pen')) || coarsePointer;
              dragYawMul = isTouchLike ? 3.6 : 1;
              dragPitchMul = isTouchLike ? 2.9 : 1;
              if (isTouchLike && e && e.cancelable) e.preventDefault();
              if (renderer.domElement.setPointerCapture) renderer.domElement.setPointerCapture(e.pointerId);
            });
            renderer.domElement.addEventListener('pointermove', function(e){
              if (!dragging) return;
              if (e && e.cancelable) e.preventDefault();
              const dx = e.clientX - lx, dy = e.clientY - ly; lx = e.clientX; ly = e.clientY;
              yaw += dx * 0.008 * dragYawMul;
              pitch = clampPitch(pitch + (dy * 0.005 * dragPitchMul));
            });
            function endDrag(){ dragging = false; dragYawMul = 1; dragPitchMul = 1; }
            renderer.domElement.addEventListener('pointerup', endDrag);
            renderer.domElement.addEventListener('pointercancel', endDrag);

            let raf = 0;
            const tick = function(){
              if (!dragging) yaw += autoYawSpeed;
              mug.rotation.y = yaw;
              mug.rotation.x = pitch;
              renderer.render(scene, camera);
              raf = window.requestAnimationFrame(tick);
            };
            tick();

            active3d = {
              setTextureUrl: function(url){
                if (!url) return;
                new THREE.TextureLoader().load(url, function(tex){
                  tex.colorSpace = THREE.SRGBColorSpace;
                  tex.wrapS = THREE.ClampToEdgeWrapping;
                  tex.wrapT = THREE.ClampToEdgeWrapping;
                  tex.repeat.set(1, 1);
                  tex.offset.set(0, 0);
                  labelMat.map = tex;
                  labelMat.needsUpdate = true;
                });
              },
              resize: resize,
              dispose: function(){
                try { if (raf) window.cancelAnimationFrame(raf); } catch (e) {}
                try { if (ro && ro.disconnect) ro.disconnect(); } catch (e) {}
                try { renderer.dispose(); } catch (e) {}
                try { if (stage3d) stage3d.innerHTML = ''; } catch (e) {}
              }
            };
            window.addEventListener('resize', resize, { passive: true });
            return active3d;
          }

          function firstPngFromPayload(parsed){
            if (!parsed || !Array.isArray(parsed.mockups)) return '';
            for (let i = 0; i < parsed.mockups.length; i++) {
              const m = parsed.mockups[i];
              if (!m) continue;
              const areas = Array.isArray(m.areas) ? m.areas : [];
              for (let j = 0; j < areas.length; j++) {
                const png = String((areas[j] && areas[j].png) || '');
                if (png) return png;
              }
            }
            return '';
          }

          function renderMockup(i){
            if (!activePayload || !Array.isArray(activePayload.mockups)) return false;
            const m = activePayload.mockups[i];
            if (!m || !m.image_url || !base || !overlays || !stage) return false;
            activeIndex = i;
            stage.style.display = 'inline-block';
            base.src = String(m.image_url || '');
            base.alt = String(m.name || ('Mockup ' + (i + 1)));
            if (img) {
              img.removeAttribute('src');
              img.style.display = 'none';
            }
            const areas = Array.isArray(m.areas) ? m.areas : [];
            overlays.innerHTML = areas.map(function(a){
              const x = Math.max(0, Math.min(1, Number(a.x_pct || 0)));
              const y = Math.max(0, Math.min(1, Number(a.y_pct || 0)));
              const w = Math.max(0.01, Math.min(1, Number(a.w_pct || 0.25)));
              const h = Math.max(0.01, Math.min(1, Number(a.h_pct || 0.25)));
              const png = String(a.png || '');
              if (!png) return '';
              return '' +
                '<div class="ds-cart-preview-ov" title="' + esc(String(a.label || a.zone_id || 'Zone')) + '" style="left:' + (x * 100).toFixed(4) + '%;top:' + (y * 100).toFixed(4) + '%;width:' + (w * 100).toFixed(4) + '%;height:' + (h * 100).toFixed(4) + '%;">' +
                  '<img src="' + esc(png) + '" alt="' + esc(String(a.label || a.zone_id || 'Zone')) + '" />' +
                '</div>';
            }).join('');
            if (tabs) {
              const btns = tabs.querySelectorAll('.ds-cart-preview-tab');
              btns.forEach(function(b, idx){ b.classList.toggle('is-active', idx === i); });
            }
            return true;
          }

          function renderTabs(){
            if (!tabs || !activePayload || !Array.isArray(activePayload.mockups)) return;
            tabs.innerHTML = activePayload.mockups.map(function(m, i){
              return '' +
                '<button type="button" class="ds-cart-preview-tab' + (i === 0 ? ' is-active' : '') + '" data-ds-cart-tab="' + i + '">' +
                  '<span class="ds-cart-preview-tab-index">' + (i + 1) + '</span>' +
                  '<span class="ds-cart-preview-tab-name">' + esc(String((m && m.name) || ('Mockup ' + (i + 1)))) + '</span>' +
                '</button>';
            }).join('');
          }

          function parsePayload(raw){
            if (!raw) return null;
            try {
              const p = JSON.parse(String(raw));
              if (!p || !Array.isArray(p.mockups) || !p.mockups.length) return null;
              return p;
            } catch (e) {
              return null;
            }
          }

          function parseProfile(raw){
            if (!raw) return null;
            try {
              const p = JSON.parse(String(raw));
              return (p && typeof p === 'object') ? p : null;
            } catch (e) {
              return null;
            }
          }

          function openModal(src,payload,title,is3d,profileRaw){
            if (ttl) ttl.textContent = title ? ('Design Preview: ' + title) : 'Design Preview';
            clearStructuredPreview();
            const parsed = parsePayload(payload);
            const profile = parseProfile(profileRaw);
            if (is3d) {
              modal.classList.add('ds-cart-preview-3d-mode');
              const texUrl = src || firstPngFromPayload(parsed);
              if (!texUrl || !stage3d) return;
              const stageWrap3d = stage3d.closest ? stage3d.closest('.ds-cart-preview-stage-wrap') : null;
              if (stageWrap3d) {
                try { stageWrap3d.scrollTop = 0; stageWrap3d.scrollLeft = 0; } catch (e) {}
              }
              modal.classList.add('is-open');
              modal.setAttribute('aria-hidden','false');
              if (tabs) tabs.innerHTML = '';
              if (stage) stage.style.display = 'none';
              if (img) {
                img.removeAttribute('src');
                img.style.display = 'none';
              }
              stage3d.style.display = 'block';
              ensureThreeLoaded(function(){
                if (active3d && active3d.dispose) {
                  try { active3d.dispose(); } catch (e) {}
                  active3d = null;
                }
                const p = init3dStage(profile);
                if (!p) return;
                p.setTextureUrl(texUrl);
                setTimeout(function(){ if (p && p.resize) p.resize(); }, 40);
                setTimeout(function(){ if (p && p.resize) p.resize(); }, 140);
                try {
                  window.requestAnimationFrame(function(){
                    window.requestAnimationFrame(function(){
                      if (p && p.resize) p.resize();
                    });
                  });
                } catch (e) {}
              });
            } else if (parsed && parsed.mockups && parsed.mockups.length) {
              modal.classList.remove('ds-cart-preview-3d-mode');
              activePayload = parsed;
              renderTabs();
              if (!renderMockup(0)) return;
            } else if (src && img) {
              modal.classList.remove('ds-cart-preview-3d-mode');
              img.src = src;
              img.style.display = 'block';
            } else {
              return;
            }
            modal.classList.add('is-open');
            modal.setAttribute('aria-hidden','false');
          }
          document.addEventListener('click', function(e){
            const link = e.target && e.target.closest ? e.target.closest('.ds-cart-preview-link') : null;
            if (link) {
              e.preventDefault();
              refreshPreviewRegistryFromDom(document);
              let src = link.getAttribute('data-ds-preview-src') || '';
              let payload = link.getAttribute('data-ds-preview-payload') || '';
              let title = link.getAttribute('data-ds-preview-title') || '';
              let is3d = String(link.getAttribute('data-ds-preview-3d') || '') === '1';
              let profileRaw = link.getAttribute('data-ds-preview-profile') || '';
              if (!src || !payload) {
                const href = String(link.getAttribute('href') || '');
                const m = href.match(/^#ds-preview-(.+)$/);
                if (m && m[1] && window.__dsCartPreviewRegistry && window.__dsCartPreviewRegistry[m[1]]) {
                  const row = window.__dsCartPreviewRegistry[m[1]] || {};
                  if (!title) title = String(row.title || '');
                  if (!src) src = String(row.src || '');
                  if (!is3d) is3d = Number(row.is_3d || 0) === 1;
                  if (!profileRaw && row.preview_profile) {
                    try { profileRaw = JSON.stringify(row.preview_profile); } catch (err) { profileRaw = ''; }
                  }
                  if (!payload && row.payload) {
                    try { payload = JSON.stringify(row.payload); } catch (err) { payload = ''; }
                  }
                }
              }
              openModal(
                src,
                payload,
                title,
                is3d,
                profileRaw
              );
              return;
            }
            const tab = e.target && e.target.closest ? e.target.closest('[data-ds-cart-tab]') : null;
            if (tab) {
              e.preventDefault();
              const idx = parseInt(tab.getAttribute('data-ds-cart-tab') || '0', 10);
              if (!isNaN(idx) && idx >= 0) renderMockup(idx);
              return;
            }
            if (e.target === modal || (e.target && e.target.closest && e.target.closest('[data-ds-cart-preview-close]'))) {
              closeModal();
            }
          }, true);
          document.addEventListener('keydown', function(e){ if (e.key === 'Escape') closeModal(); }, true);

          // WooCommerce fragment/cart refresh events (theme/cart drawer friendly).
          if (window.jQuery && window.jQuery.fn && document.body) {
            try {
              window.jQuery(document.body).on('wc_fragments_loaded wc_fragments_refreshed added_to_cart removed_from_cart updated_wc_div updated_cart_totals', function(){
                window.setTimeout(function(){ refreshPreviewRegistryFromDom(document); }, 60);
              });
            } catch (e) {}
          }

          // Generic fallback for custom drawers that inject cart HTML dynamically.
          try {
            const mo = new MutationObserver(function(mutations){
              let shouldRefresh = false;
              for (let i = 0; i < mutations.length; i++) {
                const m = mutations[i];
                if (!m || !m.addedNodes || !m.addedNodes.length) continue;
                for (let j = 0; j < m.addedNodes.length; j++) {
                  const n = m.addedNodes[j];
                  if (!n || n.nodeType !== 1) continue;
                  if ((n.matches && n.matches('.ds-cart-preview-link, .woocommerce-mini-cart, .widget_shopping_cart, .wc-block-mini-cart'))
                    || (n.querySelector && n.querySelector('.ds-cart-preview-link'))) {
                    shouldRefresh = true;
                    break;
                  }
                }
                if (shouldRefresh) break;
              }
              if (shouldRefresh) window.setTimeout(function(){ refreshPreviewRegistryFromDom(document); }, 30);
            });
            if (document.body) mo.observe(document.body, { childList: true, subtree: true });
          } catch (e) {}

          // Initial pass.
          refreshPreviewRegistryFromDom(document);
        })();
        </script>
        <?php
    }

    public static function add_order_item_meta($item, $cart_item_key, $values, $order) {
        $map = [
            self::CART_KEY => __('Design UUID', 'ds'),
            self::CART_KEY_PNG => __('Design PNG', 'ds'),
            self::CART_KEY_POST => __('Design Post ID', 'ds'),
        ];
        foreach ($map as $k => $label) {
            if (!empty($values[$k])) $item->add_meta_data($label, $values[$k], true);
        }

        if (!empty($values[self::CART_KEY_ZONES]) && is_array($values[self::CART_KEY_ZONES])) {
            $item->add_meta_data(__('Print Zones', 'ds'), implode(',', array_map('sanitize_key', $values[self::CART_KEY_ZONES])), true);
            $item->add_meta_data(self::ORDER_META_ZONES, implode(',', array_map('sanitize_key', $values[self::CART_KEY_ZONES])), true);
        }
        if (isset($values[self::CART_KEY_SURCHARGE]) && (float) $values[self::CART_KEY_SURCHARGE] > 0) {
            $item->add_meta_data(__('Print Surcharge', 'ds'), (float) $values[self::CART_KEY_SURCHARGE], true);
            $item->add_meta_data(self::ORDER_META_SURCHARGE, (float) $values[self::CART_KEY_SURCHARGE], true);
        }

        if (!empty($values[self::CART_KEY])) $item->add_meta_data(self::ORDER_META_UUID, sanitize_text_field($values[self::CART_KEY]), true);
        if (!empty($values[self::CART_KEY_PNG])) $item->add_meta_data(self::ORDER_META_PNG, esc_url_raw($values[self::CART_KEY_PNG]), true);
        if (!empty($values[self::CART_KEY_POST])) $item->add_meta_data(self::ORDER_META_POST, (int) $values[self::CART_KEY_POST], true);
        if (!empty($values[self::CART_KEY_POST])) {
            $zone_pngs = self::get_design_zones_png_urls((int) $values[self::CART_KEY_POST]);
            if (!empty($zone_pngs)) {
                $item->add_meta_data(self::ORDER_META_ZONES_PNG, wp_json_encode($zone_pngs), true);
            }
        }

        if (!empty($values[self::CART_KEY_POST])) {
            $design_post_id = intval($values[self::CART_KEY_POST]);
            if ($design_post_id > 0) {
                update_post_meta($design_post_id, 'order_id', $order->get_id());
            }
        }
    }

    public static function bind_new_order_item_to_design($item_id, $item_obj, $order_id) {
        if (!$item_id || !$item_obj || !is_a($item_obj, '\\WC_Order_Item_Product')) return;

        $design_post_id = (int) wc_get_order_item_meta($item_id, self::ORDER_META_POST, true);
        if ($design_post_id <= 0) {
            $design_post_id = (int) wc_get_order_item_meta($item_id, __('Design Post ID', 'ds'), true);
        }
        if ($design_post_id > 0) {
            update_post_meta($design_post_id, 'order_id', (int) $order_id);
            update_post_meta($design_post_id, 'order_item_id', (int) $item_id);
        }
    }

    public static function admin_order_itemmeta_links($item_id, $item, $product) {
        $meta = wc_get_order_item_meta($item_id, self::ORDER_META_PNG, true);
        $design_post_id = (int) wc_get_order_item_meta($item_id, self::ORDER_META_POST, true);
        $zones_png_meta = wc_get_order_item_meta($item_id, self::ORDER_META_ZONES_PNG, true);

        if (!$meta) $meta = wc_get_order_item_meta($item_id, __('Design PNG', 'ds'), true);
        if (!$design_post_id) $design_post_id = (int) wc_get_order_item_meta($item_id, __('Design Post ID', 'ds'), true);
        $zones_png = [];
        if (is_string($zones_png_meta) && $zones_png_meta !== '') {
            $decoded = json_decode($zones_png_meta, true);
            if (is_array($decoded)) $zones_png = $decoded;
        }
        if (empty($zones_png) && $design_post_id > 0) {
            $zones_png = self::get_design_zones_png_urls($design_post_id);
        }

        if ($meta || !empty($zones_png)) {
            echo '<div class="ds-order-design">';
            if ($meta) echo '<div><a href="' . esc_url($meta) . '" target="_blank">' . esc_html__('View PNG', 'ds') . '</a></div>';
            if (!empty($zones_png)) {
                $labels = $design_post_id > 0 ? self::get_design_zone_labels($design_post_id) : self::zone_labels();
                foreach ($zones_png as $zid => $zurl) {
                    $key = sanitize_key((string) $zid);
                    $name = isset($labels[$key]) ? $labels[$key] : self::humanize_zone_key($key);
                    if ($name === '') $name = $key;
                    echo '<div><a href="' . esc_url($zurl) . '" target="_blank">' . esc_html(sprintf(__('View Zone: %s', 'ds'), $name)) . '</a></div>';
                }
            }
            if ($design_post_id > 0) {
                $designer_url = self::get_designer_edit_url($design_post_id);
                if ($designer_url) {
                    echo '<div><a href="' . esc_url($designer_url) . '" target="_blank">' . esc_html__('Edit in Designer', 'ds') . '</a></div>';
                }
                $edit_url = get_edit_post_link($design_post_id, '');
                if (!$edit_url) {
                    $edit_url = admin_url('post.php?post=' . (int) $design_post_id . '&action=edit');
                }
                if ($edit_url) {
                    echo '<div><a href="' . esc_url($edit_url) . '" target="_blank">' . esc_html__('Edit Post Meta', 'ds') . '</a></div>';
                }
            }
            echo '</div>';
        }
    }
}
