<?php

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

class AIEO_DMM_Swatches extends AIEO_DMM_Module_Base {

    const SETTINGS_OPTION         = 'aieo_dmm_swatches_settings';
    const LEGACY_SETTINGS_OPTION  = 'om_product_swatches_settings';
    const RUNTIME_FLAG_OPTION     = 'aieo_dmm_swatches_runtime_enabled';
    const SETTINGS_HASH_OPTION    = 'aieo_dmm_swatches_settings_hash';
    const REBUILD_JOB_ACTION      = 'aieo_dmm_swatches_rebuild_batch';
    
    
    
    
    
    
    
    
    
    const SWATCH_PAYLOAD_SUFFIX   = '';

    public function get_id()    { return 'swatches'; }
    public function get_label() { return __('Product Swatches', 'ai-eshop-optimizer'); }
    public function get_priority() { return 55; }
    public function get_group()    { return 'visuals'; }

    public function register_hooks() {
        
        add_action('wp_ajax_aieo_dmm_swatches_save',      array($this, 'ajax_save_settings'));
        add_action('wp_ajax_aieo_dmm_swatches_stats',     array($this, 'ajax_stats'));
        add_action('wp_ajax_aieo_dmm_swatches_clear',     array($this, 'ajax_clear'));
        add_action('wp_ajax_aieo_dmm_swatches_rebuild',   array($this, 'ajax_rebuild_all'));
        add_action('wp_ajax_aieo_dmm_swatches_migrate',   array($this, 'ajax_migrate_legacy'));
        add_action('wp_ajax_aieo_dmm_swatches_toggle_runtime', array($this, 'ajax_toggle_runtime'));
        add_action('wp_ajax_aieo_dmm_swatches_get_categories', array($this, 'ajax_get_categories'));
        add_action('wp_ajax_aieo_dmm_swatches_colour_flags',   array($this, 'ajax_bulk_colour_flags'));

        
        
        
        
        
        
        add_filter('product_attributes_type_selector', array($this, 'available_attribute_types'));
        if (is_admin()) {
            add_action('admin_init', array($this, 'register_term_admin_hooks'));
            add_action('created_term', array($this, 'save_term_swatch_fields'), 10, 3);
            add_action('edit_term',    array($this, 'save_term_swatch_fields'), 10, 3);
            add_action('admin_enqueue_scripts', array($this, 'enqueue_term_admin_assets'));

            
            
            
            
            
            
            
            
            
            
            add_action('woocommerce_product_option_terms', array($this, 'render_custom_attribute_terms_select'), 10, 3);

            
            
            
            
            
            
            add_action('woocommerce_after_add_attribute_fields',  array($this, 'render_attribute_colour_flag_add'));
            add_action('woocommerce_after_edit_attribute_fields', array($this, 'render_attribute_colour_flag_edit'));
            add_action('woocommerce_attribute_added',   array($this, 'save_attribute_colour_flag_added'),   10, 2);
            add_action('woocommerce_attribute_updated', array($this, 'save_attribute_colour_flag_updated'), 10, 3);
        }

        
        add_action(self::REBUILD_JOB_ACTION, array($this, 'rebuild_batch'), 10, 1);

        if (!AIEO_DMM_Settings::is_module_enabled('swatches')) {
            return;
        }

        
        
        
        
        
        
        
        add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets'), 20);

        
        
        add_action('save_post_product',                      array($this, 'invalidate_for_product'), 20, 1);
        add_action('woocommerce_update_product',             array($this, 'invalidate_for_product'), 20, 1);
        add_action('woocommerce_update_product_variation',   array($this, 'invalidate_for_variation'), 20, 1);
        add_action('woocommerce_delete_product_variation',   array($this, 'invalidate_for_variation'), 20, 1);
        add_action('woocommerce_trash_product_variation',    array($this, 'invalidate_for_variation'), 20, 1);

        
        
        add_action('edited_term',  array($this, 'invalidate_on_term_edit'), 10, 3);
        add_action('delete_term',  array($this, 'invalidate_on_term_edit'), 10, 3);

        
        
        add_action('update_option_' . self::SETTINGS_OPTION, array($this, 'on_settings_update'), 10, 2);

        
        
        
        
        
        
        
        add_action('update_option_' . self::LEGACY_SETTINGS_OPTION, array($this, 'on_legacy_settings_update'), 10, 2);
        add_action('add_option_'    . self::LEGACY_SETTINGS_OPTION, array($this, 'on_legacy_settings_add'),    10, 2);

        
        
        
        
        
        
        if (is_admin() && !class_exists('OM_Product_Swatches_Product_Settings')) {
            add_filter('woocommerce_product_data_tabs',    array($this, 'add_product_data_tab'));
            add_action('woocommerce_product_data_panels',  array($this, 'render_product_data_panel'));
            add_action('woocommerce_process_product_meta', array($this, 'save_product_meta'));
            add_action('admin_enqueue_scripts',            array($this, 'enqueue_product_scripts'));
        }
    }

    
    
    

    
    public function default_settings() {
        return array(
            'enabled'                                  => 'yes',
            'tooltip'                                  => true,
            'mouseover'                                => false,
            'on_shop_catalog'                          => true,
            'display_variation_name_on_product_title'  => true,

            
            'color_width'                    => 24,
            'color_height'                   => 24,
            'color_gap'                      => 8,
            'color_border_radius'            => 50,
            'color_border_size'              => 1,
            'color_border_color'             => '#dedede',
            'color_border_size_hover'        => 2,
            'color_border_color_hover'       => '#212121',
            'color_border_size_selected'     => 2,
            'color_border_color_selected'    => '#212121',

            
            'button_text_color'              => '#212121',
            'button_bg_color'                => '#ffffff',
            'button_border_color'            => '#212121',
            'button_border_radius'           => 5,
            'button_border_size'             => 1,
            'button_padding_top'             => 8,
            'button_padding_right'           => 12,
            'button_padding_bottom'          => 8,
            'button_padding_left'            => 12,
            'button_gap'                     => 8,

            
            'image_width'                    => 40,
            'image_height'                   => 40,
            'image_gap'                      => 8,
            'image_border_radius'            => 0,
            'image_border_size'              => 1,
            'image_border_color'             => '#dedede',
            'image_loading_strategy'         => 'lazy',

            
            'image_width_shop_archive'       => 26,
            'image_height_shop_archive'      => 26,
            'image_gap_shop_archive'         => 6,

            
            
            'image_width_mobile'                  => 32,
            'image_height_mobile'                 => 32,
            'image_width_shop_archive_mobile'     => 22,
            'image_height_shop_archive_mobile'    => 22,

            
            
            
            'button_padding_top_mobile'           => 4,
            'button_padding_right_mobile'         => 8,
            'button_padding_bottom_mobile'        => 4,
            'button_padding_left_mobile'          => 8,
            'button_font_size_mobile'             => 11,

            
            
            'button_stack_vertical_archive'       => false,
            
            
            'button_circle_archive'               => false,
            'button_circle_diameter_archive'      => 36,
            'button_font_size_shop_archive'       => 11,

            
            
            
            
            'image_swatch_limit_desktop'     => 3,
            'image_swatch_limit_mobile'      => 3,
            'color_swatch_limit_desktop'     => 5,
            'color_swatch_limit_mobile'      => 3,
            'button_swatch_limit_desktop'    => 5,
            'button_swatch_limit_mobile'     => 3,

            
            'multi_select_enabled'           => false,
            'multi_select_display_style'     => 'grid',
            'multi_select_button_text'       => 'Add Selected to Cart',
            'multi_select_button_position'   => 'above',
            'multi_select_show_quantity'     => false,
            'multi_select_categories'        => array(),
            'cart_sync_enabled'              => true,
            
            
            
            
            
            
            
            'cart_summary_visible'           => true,
            'cart_variation_image'           => 'swatch',

            
            
            
            
            
            
            
            
            
            
            
            'track_per_variation_atc'        => false,

            
            'see_all_display_mode'           => 'swatches_only',
            'see_all_text'                   => 'See all {count} variations',
            'see_all_modal_title'            => 'All Variations',
            'see_all_view_product_text'      => 'View product page',
        );
    }

    
    public function get_settings() {
        $stored = get_option(self::SETTINGS_OPTION, null);
        if (!is_array($stored)) {
            
            $stored = get_option(self::LEGACY_SETTINGS_OPTION, array());
        }
        return wp_parse_args(is_array($stored) ? $stored : array(), $this->default_settings());
    }

    public function get($key, $default = null) {
        $s = $this->get_settings();
        return array_key_exists($key, $s) ? $s[$key] : $default;
    }

    
    public function save_settings(array $patch) {
        $current = $this->get_settings();
        $merged  = array_replace($current, $patch);

        update_option(self::SETTINGS_OPTION, $merged, false);

        
        
        if (class_exists('OM_Product_Swatches')) {
            update_option(self::LEGACY_SETTINGS_OPTION, $merged, false);
        }

        
        update_option(self::SETTINGS_HASH_OPTION, md5((string) wp_json_encode($merged)), false);

        return $merged;
    }

    
    public function on_settings_update($old_value, $new_value) {
        if ($new_value === $old_value) { return; }
        update_option(self::SETTINGS_HASH_OPTION, md5((string) wp_json_encode($new_value)), false);
    }

    
    public function on_legacy_settings_update($old_value, $new_value) {
        if (!is_array($new_value)) { return; }
        $aieo_current = get_option(self::SETTINGS_OPTION, array());
        if (is_array($aieo_current) && $aieo_current === $new_value) {
            
            return;
        }
        update_option(self::SETTINGS_OPTION, $new_value, false);
        update_option(self::SETTINGS_HASH_OPTION, md5((string) wp_json_encode($new_value)), false);
    }

    
    public function on_legacy_settings_add($option_name, $value) {
        if (!is_array($value)) { return; }
        $aieo_current = get_option(self::SETTINGS_OPTION, null);
        if (is_array($aieo_current) && $aieo_current === $value) { return; }
        update_option(self::SETTINGS_OPTION, $value, false);
        update_option(self::SETTINGS_HASH_OPTION, md5((string) wp_json_encode($value)), false);
    }

    public function get_settings_hash() {
        $h = get_option(self::SETTINGS_HASH_OPTION, '');
        if ($h === '') {
            $h = md5((string) wp_json_encode($this->get_settings()));
            update_option(self::SETTINGS_HASH_OPTION, $h, false);
        }
        return $h . self::SWATCH_PAYLOAD_SUFFIX;
    }

    
    public static function compose_swatch_label($desc, array $attributes) {
        $desc   = trim((string) $desc);
        $labels = array();
        foreach ($attributes as $akey => $aval) {
            $aval = (string) $aval;
            if ($aval === '' || strpos((string) $akey, 'attribute_') !== 0) { continue; }
            $tax = substr((string) $akey, 10); 
            if ($tax !== '' && taxonomy_exists($tax)) {
                $term     = get_term_by('slug', $aval, $tax);
                $labels[] = ($term && !is_wp_error($term)) ? $term->name : $aval;
            } else {
                $labels[] = $aval; 
            }
        }
        $attr_label = implode(' / ', $labels);
        if ($desc !== '' && $attr_label !== '') { return $desc . ' - ' . $attr_label; }
        return $desc !== '' ? $desc : $attr_label;
    }

    
    private static function variation_description($variation_id) {
        $variation_id = (int) $variation_id;
        if ($variation_id <= 0) { return ''; }
        $v = wc_get_product($variation_id);
        if (!$v || !is_a($v, 'WC_Product')) { return ''; }
        return trim(wp_strip_all_tags((string) $v->get_description()));
    }

    
    private static function cart_desc_label() {
        return (string) apply_filters('aieo_dmm_swatch_cart_desc_label', __('Variant', 'ai-eshop-optimizer'));
    }

    
    public static function cart_item_variation_description($item_data, $cart_item) {
        $vid = isset($cart_item['variation_id']) ? (int) $cart_item['variation_id'] : 0;
        if ($vid <= 0) { return $item_data; } 
        
        
        
        $product = (isset($cart_item['data']) && is_a($cart_item['data'], 'WC_Product')) ? $cart_item['data'] : null;
        $desc = $product ? trim(wp_strip_all_tags((string) $product->get_description())) : self::variation_description($vid);
        if ($desc !== '') {
            $item_data[] = array(
                'key'     => self::cart_desc_label(),
                'value'   => $desc,
                'display' => esc_html($desc),
            );
        }
        return $item_data;
    }

    
    public static function order_item_variation_description($item, $cart_item_key, $values, $order) {
        $vid = isset($values['variation_id']) ? (int) $values['variation_id'] : 0;
        if ($vid <= 0) { return; }
        $product = (isset($values['data']) && is_a($values['data'], 'WC_Product')) ? $values['data'] : null;
        $desc = $product ? trim(wp_strip_all_tags((string) $product->get_description())) : self::variation_description($vid);
        if ($desc !== '') {
            $item->add_meta_data(self::cart_desc_label(), $desc, true);
        }
    }

    
    
    

    
    public function build_cache_for_product($product_id) {
        $product_id = (int) $product_id;
        if ($product_id <= 0) { return false; }

        $product = wc_get_product($product_id);
        if (!$product || !$product->is_type('variable')) {
            
            $this->delete_cache_rows($product_id);
            return false;
        }

        
        $archive = $this->build_variations_payload($product, 'archive');
        $single  = $this->build_variations_payload($product, 'single');

        $this->write_cache_row($product_id, 'archive', $archive);
        $this->write_cache_row($product_id, 'single',  $single);

        
        
        $this->write_variation_map_rows($product_id, $single['variation_data']);

        
        
        if (class_exists('OM_Product_Swatches_Cache')) {
            $this->shadow_write_legacy($product_id, 'archive', $archive);
            $this->shadow_write_legacy($product_id, 'single',  $single);
        }

        return true;
    }

    
    private function build_variations_payload($product, $context) {
        $product_id        = (int) $product->get_id();
        $settings          = $this->get_settings();
        $hide_oos          = get_option('woocommerce_hide_out_of_stock_items', 'no') === 'yes';

        if ($context === 'archive') {
            $img_w     = (int) ($settings['image_width_shop_archive']  ?? 26);
            $img_h     = (int) ($settings['image_height_shop_archive'] ?? 26);
            $dlim      = (int) ($settings['image_swatch_limit_desktop'] ?? 5);
            $mlim      = (int) ($settings['image_swatch_limit_mobile']  ?? 3);
            $max_limit = max($dlim, $mlim);
        } else {
            $img_w     = (int) ($settings['image_width']  ?? 40);
            $img_h     = (int) ($settings['image_height'] ?? 40);
            $max_limit = 0;
        }

        $all_ids = $product->get_children();
        if (!is_array($all_ids)) { $all_ids = array(); }

        $to_process     = array();
        $total_available = 0;

        if ($context === 'archive' && $hide_oos) {
            foreach ($all_ids as $vid) {
                $stock = get_post_meta((int) $vid, '_stock_status', true);
                if ($stock === 'instock' || $stock === 'onbackorder') {
                    $to_process[] = (int) $vid;
                }
            }
            $total_available = count($to_process);
        } else {
            $to_process     = array_map('intval', $all_ids);
            $total_available = count($to_process);
        }

        $variation_data = array();
        $count          = 0;
        $parent_image_id = (int) $product->get_image_id();

        foreach ($to_process as $vid) {
            if ($context === 'archive' && $max_limit > 0 && $count >= $max_limit) {
                break;
            }

            $meta = get_post_meta((int) $vid);
            if (!is_array($meta)) { continue; }

            $attributes = array();
            foreach ($meta as $mkey => $mval) {
                if (strpos($mkey, 'attribute_') === 0) {
                    $attributes[$mkey] = isset($mval[0]) ? wc_clean($mval[0]) : '';
                }
            }

            $stock_status = isset($meta['_stock_status'][0]) ? (string) $meta['_stock_status'][0] : '';
            $available    = ($stock_status === 'instock' || $stock_status === 'onbackorder');

            $image_id = isset($meta['_thumbnail_id'][0]) ? (int) $meta['_thumbnail_id'][0] : 0;
            if ($image_id <= 0) { $image_id = $parent_image_id; }

            $img_url = '';
            if ($image_id > 0) {
                $intermediate = image_get_intermediate_size($image_id, array($img_w, $img_h));
                if (is_array($intermediate) && !empty($intermediate['url'])) {
                    $img_url = (string) $intermediate['url'];
                } else {
                    $full = wp_get_attachment_image_src($image_id, 'full');
                    $img_url = is_array($full) ? (string) $full[0] : '';
                }
            }

            $price_html = '';
            $variation_obj = wc_get_product((int) $vid);
            if ($variation_obj) {
                $price_html = (string) $variation_obj->get_price_html();
            }

            $sku = isset($meta['_sku'][0]) ? (string) $meta['_sku'][0] : '';

            
            
            
            
            $vdesc        = isset($meta['_variation_description'][0]) ? trim(wp_strip_all_tags((string) $meta['_variation_description'][0])) : '';
            $swatch_label = self::compose_swatch_label($vdesc, $attributes);

            $variation_data[] = array(
                'variation_id'          => (int) $vid,
                'available'             => $available,
                'display'               => '',
                'base'                  => '',
                'img_url'               => $img_url,
                'img_w'                 => $img_w,
                'img_h'                 => $img_h,
                'attributes'            => $attributes,
                'price_html'            => $price_html,
                'sku'                   => $sku,
                'stock_status'          => $stock_status,
                'variation_description' => $vdesc,
                'swatch_label'          => $swatch_label,
            );
            $count++;
        }

        return array(
            'variation_data'   => $variation_data,
            'total_available'  => $total_available,
            'stored_count'     => count($variation_data),
        );
    }

    
    private function write_cache_row($product_id, $context, array $payload) {
        global $wpdb;
        $table = $wpdb->prefix . 'aieo_dmm_swatches_cache';
        if (!aieo_dmm_table_exists($table)) { return; }

        $variation_data = $payload['variation_data'];
        $variation_ids  = array_map('intval', array_column($variation_data, 'variation_id'));

        $map = array();
        foreach ($variation_data as $v) {
            $hash = sha1((string) wp_json_encode($v['attributes']));
            $map[$hash] = (int) $v['variation_id'];
        }

        $row = array(
            'product_id'          => (int) $product_id,
            'context'             => (string) $context,
            'total_available'     => (int) $payload['total_available'],
            'stored_count'        => (int) $payload['stored_count'],
            'variations_json'     => (string) wp_json_encode($variation_data),
            'variation_ids_json'  => (string) wp_json_encode($variation_ids),
            'variations_map_json' => (string) wp_json_encode($map),
            'grid_html'           => null,
            'cache_hash'          => $this->get_settings_hash(),
            'built_at'            => current_time('mysql', true),
        );

        $wpdb->query($wpdb->prepare(
            "INSERT INTO {$table}
                (product_id, context, total_available, stored_count,
                 variations_json, variation_ids_json, variations_map_json,
                 grid_html, cache_hash, built_at)
             VALUES (%d, %s, %d, %d, %s, %s, %s, %s, %s, %s)
             ON DUPLICATE KEY UPDATE
                total_available     = VALUES(total_available),
                stored_count        = VALUES(stored_count),
                variations_json     = VALUES(variations_json),
                variation_ids_json  = VALUES(variation_ids_json),
                variations_map_json = VALUES(variations_map_json),
                grid_html           = VALUES(grid_html),
                cache_hash          = VALUES(cache_hash),
                built_at            = VALUES(built_at)",
            $row['product_id'], $row['context'], $row['total_available'], $row['stored_count'],
            $row['variations_json'], $row['variation_ids_json'], $row['variations_map_json'],
            $row['grid_html'], $row['cache_hash'], $row['built_at']
        ));
    }

    
    private function write_variation_map_rows($product_id, array $variation_data) {
        global $wpdb;
        $table = $wpdb->prefix . 'aieo_dmm_swatches_variation_map';
        if (!aieo_dmm_table_exists($table)) { return; }

        
        
        
        $term_swatch_url_cache = array();

        $now = current_time('mysql', true);
        foreach ($variation_data as $v) {
            $hash = sha1((string) wp_json_encode($v['attributes'] ?? array()));

            
            
            
            
            
            $swatch_url = $this->resolve_variation_swatch_image_url(
                $v['attributes'] ?? array(),
                $term_swatch_url_cache
            );

            $wpdb->query($wpdb->prepare(
                "INSERT INTO {$table}
                    (variation_id, product_id, attribute_combo_hash, image_url, swatch_image_url, sku, stock_status, updated_at)
                 VALUES (%d, %d, %s, %s, %s, %s, %s, %s)
                 ON DUPLICATE KEY UPDATE
                    product_id           = VALUES(product_id),
                    attribute_combo_hash = VALUES(attribute_combo_hash),
                    image_url            = VALUES(image_url),
                    swatch_image_url     = VALUES(swatch_image_url),
                    sku                  = VALUES(sku),
                    stock_status         = VALUES(stock_status),
                    updated_at           = VALUES(updated_at)",
                (int) $v['variation_id'],
                (int) $product_id,
                $hash,
                (string) ($v['img_url'] ?? ''),
                (string) $swatch_url,
                (string) ($v['sku'] ?? ''),
                (string) ($v['stock_status'] ?? ''),
                $now
            ));
        }
    }

    
    private function resolve_variation_swatch_image_url(array $attributes, array &$cache) {
        foreach ($attributes as $attr_key => $attr_value) {
            if ($attr_value === '') { continue; }
            
            
            $taxonomy = strpos((string) $attr_key, 'attribute_') === 0
                ? substr((string) $attr_key, 10)
                : (string) $attr_key;
            if ($taxonomy === '' || ! taxonomy_exists($taxonomy)) { continue; }

            $term = get_term_by('slug', (string) $attr_value, $taxonomy);
            if (! $term || is_wp_error($term)) { continue; }

            $term_id = (int) $term->term_id;
            if (array_key_exists($term_id, $cache)) {
                if ($cache[$term_id] !== '') { return $cache[$term_id]; }
                continue;
            }

            $image_id = (int) get_term_meta($term_id, 'product_attribute_image', true);
            if ($image_id <= 0) {
                $cache[$term_id] = '';
                continue;
            }

            $src = wp_get_attachment_image_src($image_id, 'woocommerce_thumbnail');
            $url = is_array($src) && ! empty($src[0]) ? (string) $src[0] : '';
            $cache[$term_id] = $url;
            if ($url !== '') { return $url; }
        }
        return '';
    }

    
    public function delete_cache_rows($product_id) {
        global $wpdb;
        $cache = $wpdb->prefix . 'aieo_dmm_swatches_cache';
        $map   = $wpdb->prefix . 'aieo_dmm_swatches_variation_map';
        if (aieo_dmm_table_exists($cache)) {
            $wpdb->delete($cache, array('product_id' => (int) $product_id), array('%d'));
        }
        if (aieo_dmm_table_exists($map)) {
            $wpdb->delete($map, array('product_id' => (int) $product_id), array('%d'));
        }
        if (class_exists('OM_Product_Swatches_Cache')) {
            OM_Product_Swatches_Cache::delete_by_product_id((int) $product_id);
        }
    }

    
    private function shadow_write_legacy($product_id, $context, array $payload) {
        if (!class_exists('OM_Product_Swatches_Cache')) { return; }

        $variation_data = $payload['variation_data'];
        $total          = count($variation_data);

        if ($total <= 10) {
            $key = "om_swatch_{$context}_{$product_id}";
            OM_Product_Swatches_Cache::set($key, $variation_data, 0, $product_id, "swatch_{$context}");

            if ($context === 'archive' && (int) $payload['total_available'] > $total) {
                $mkey = "om_swatch_{$context}_{$product_id}_meta";
                OM_Product_Swatches_Cache::set($mkey, array(
                    'total_available' => (int) $payload['total_available'],
                    'stored_count'    => $total,
                ), 0, $product_id, "swatch_{$context}_meta");
            }
        } else {
            foreach ($variation_data as $v) {
                $key = "om_swatch_{$context}_{$product_id}_var_{$v['variation_id']}";
                OM_Product_Swatches_Cache::set($key, $v, 0, $product_id, "swatch_{$context}_var");
            }
            $index_key = "om_swatch_{$context}_{$product_id}_index";
            OM_Product_Swatches_Cache::set($index_key, array(
                'total'           => $total,
                'ids'             => array_column($variation_data, 'variation_id'),
                'total_available' => (int) $payload['total_available'],
            ), 0, $product_id, "swatch_{$context}_index");
        }
    }

    
    
    

    public function invalidate_for_product($product_id) {
        $product_id = (int) $product_id;
        if ($product_id <= 0) { return; }
        
        
        
        $this->schedule_single_rebuild($product_id);
    }

    public function invalidate_for_variation($variation_id) {
        $variation_id = (int) $variation_id;
        if ($variation_id <= 0) { return; }
        $parent = wp_get_post_parent_id($variation_id);
        if ($parent) {
            $this->schedule_single_rebuild((int) $parent);
        }
    }

    public function invalidate_on_term_edit($term_id, $tt_id, $taxonomy) {
        if (!is_string($taxonomy) || strpos($taxonomy, 'pa_') !== 0) { return; }
        
        
        
        
        
        
        
        
        
        
        
        global $wpdb;
        $term = get_term((int) $term_id);
        if (!$term || is_wp_error($term) || empty($term->slug)) { return; }
        $vids = $wpdb->get_col($wpdb->prepare(
            "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT 2000",
            'attribute_' . $taxonomy,
            $term->slug
        ));
        $parents = array();
        foreach ((array) $vids as $vid) {
            $p = wp_get_post_parent_id((int) $vid);
            if ($p) { $parents[(int) $p] = true; }
        }
        foreach (array_keys($parents) as $pid) {
            $this->schedule_single_rebuild($pid);
        }
    }

    private function schedule_single_rebuild($product_id) {
        if (function_exists('as_enqueue_async_action')) {
            as_enqueue_async_action(self::REBUILD_JOB_ACTION, array(array((int) $product_id)), 'aieo-dmm-swatches');
            return;
        }
        
        wp_schedule_single_event(time() + 1, self::REBUILD_JOB_ACTION, array(array((int) $product_id)));
    }

    
    public function rebuild_batch($product_ids) {
        if (!is_array($product_ids)) { return; }
        foreach ($product_ids as $pid) {
            $this->build_cache_for_product((int) $pid);
        }
    }

    
    
    

    public function get_stats() {
        global $wpdb;

        $cache_table = $wpdb->prefix . 'aieo_dmm_swatches_cache';
        $map_table   = $wpdb->prefix . 'aieo_dmm_swatches_variation_map';

        $stats = array(
            'cache_rows'        => 0,
            'map_rows'          => 0,
            'variable_products' => 0,
            'coverage_pct'      => 0,
            'stale_rows'        => 0,
            'last_built_at'     => null,
            'object_cache'      => wp_using_ext_object_cache(),
            'runtime_enabled'   => (string) get_option(self::RUNTIME_FLAG_OPTION, 'no') === 'yes',
            'settings_hash'     => $this->get_settings_hash(),
            'legacy_plugin_active' => class_exists('OM_Product_Swatches'),
        );

        if (aieo_dmm_table_exists($cache_table)) {
            
            $stats['cache_rows']    = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$cache_table}");
            
            $stats['last_built_at'] = $wpdb->get_var("SELECT MAX(built_at) FROM {$cache_table}");
            $hash = $this->get_settings_hash();
            $stats['stale_rows'] = (int) $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$cache_table} WHERE cache_hash <> %s",
                $hash
            ));
        }

        if (aieo_dmm_table_exists($map_table)) {
            
            $stats['map_rows'] = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$map_table}");
        }

        
        $stats['variable_products'] = (int) $wpdb->get_var(
            "SELECT COUNT(DISTINCT p.ID)
             FROM {$wpdb->posts} p
             INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID
             INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
             INNER JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
             WHERE p.post_type = 'product' AND p.post_status = 'publish'
               AND tt.taxonomy = 'product_type' AND t.slug = 'variable'"
        );

        $total_context_rows_expected = $stats['variable_products'] * 2; 
        if ($total_context_rows_expected > 0) {
            $stats['coverage_pct'] = round(100 * $stats['cache_rows'] / $total_context_rows_expected, 1);
        }

        return $stats;
    }

    public function ajax_stats() {
        AIEO_DMM::verify_ajax_request();
        wp_send_json_success($this->get_stats());
    }

    public function ajax_save_settings() {
        AIEO_DMM::verify_ajax_request();
        $raw = isset($_POST['settings']) && is_array($_POST['settings'])
            ? map_deep(wp_unslash($_POST['settings']), 'sanitize_text_field')
            : array();
        $patch = $this->sanitize_settings($raw);
        $merged = $this->save_settings($patch);
        wp_send_json_success(array(
            'settings' => $merged,
            'message'  => __('Settings saved.', 'ai-eshop-optimizer'),
        ));
    }

    public function ajax_clear() {
        AIEO_DMM::verify_ajax_request();
        global $wpdb;
        $cache = $wpdb->prefix . 'aieo_dmm_swatches_cache';
        $map   = $wpdb->prefix . 'aieo_dmm_swatches_variation_map';
        if (aieo_dmm_table_exists($cache)) {
            
            $wpdb->query("TRUNCATE TABLE {$cache}");
        }
        if (aieo_dmm_table_exists($map)) {
            
            $wpdb->query("TRUNCATE TABLE {$map}");
        }
        if (class_exists('OM_Product_Swatches_Cache')) {
            OM_Product_Swatches_Cache::delete_all();
        }
        wp_send_json_success(array(
            'message' => __('Swatch cache cleared. Rows will rebuild lazily on first frontend hit.', 'ai-eshop-optimizer'),
            'stats'   => $this->get_stats(),
        ));
    }

    
    public function ajax_rebuild_all() {
        AIEO_DMM::verify_ajax_request();

        global $wpdb;
        $ids = $wpdb->get_col(
            "SELECT DISTINCT p.ID
             FROM {$wpdb->posts} p
             INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID
             INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
             INNER JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
             WHERE p.post_type = 'product' AND p.post_status = 'publish'
               AND tt.taxonomy = 'product_type' AND t.slug = 'variable'"
        );
        $ids = array_map('intval', (array) $ids);

        $chunks    = array_chunk($ids, 100);
        $enqueued  = 0;
        foreach ($chunks as $chunk) {
            if (function_exists('as_enqueue_async_action')) {
                as_enqueue_async_action(self::REBUILD_JOB_ACTION, array($chunk), 'aieo-dmm-swatches');
            } else {
                wp_schedule_single_event(time() + ($enqueued * 5), self::REBUILD_JOB_ACTION, array($chunk));
            }
            $enqueued++;
        }

        wp_send_json_success(array(
            'total_products'  => count($ids),
            'chunks_enqueued' => $enqueued,
            'message'         => sprintf(
                
                __('Queued %1$d products for rebuild in %2$d batches of 100.', 'ai-eshop-optimizer'),
                count($ids),
                $enqueued
            ),
        ));
    }

    public function ajax_migrate_legacy() {
        AIEO_DMM::verify_ajax_request();
        $result = $this->migrate_legacy();
        wp_send_json_success(array_merge(
            $result,
            array('message' => sprintf(
                
                __('Migrated %d products from the legacy swatches cache.', 'ai-eshop-optimizer'),
                (int) $result['migrated']
            ))
        ));
    }

    
    public function ajax_get_categories() {
        AIEO_DMM::verify_ajax_request();
        $terms = get_terms(array(
            'taxonomy'   => 'product_cat',
            'hide_empty' => false,
            'orderby'    => 'name',
            'order'      => 'ASC',
        ));
        $out = array();
        if (!is_wp_error($terms)) {
            foreach ($terms as $t) {
                $out[] = array(
                    'id'    => (int) $t->term_id,
                    'name'  => (string) $t->name,
                    'count' => (int) $t->count,
                );
            }
        }
        wp_send_json_success($out);
    }

    public function ajax_toggle_runtime() {
        AIEO_DMM::verify_ajax_request();
        $enable = !empty($_POST['enabled']);
        update_option(self::RUNTIME_FLAG_OPTION, $enable ? 'yes' : 'no', false);
        wp_send_json_success(array(
            'runtime_enabled' => $enable,
            'message' => $enable
                ? __('AIEO runtime renderer enabled. The legacy om-product-swatches frontend will step aside.', 'ai-eshop-optimizer')
                : __('AIEO runtime disabled. The legacy om-product-swatches frontend is authoritative.', 'ai-eshop-optimizer'),
        ));
    }

    
    
    

    
    public function migrate_legacy() {
        $result = array('migrated' => 0, 'skipped' => 0, 'errors' => 0, 'options_migrated' => false);

        
        if (get_option(self::SETTINGS_OPTION, null) === null) {
            $legacy_settings = get_option(self::LEGACY_SETTINGS_OPTION, null);
            if (is_array($legacy_settings)) {
                update_option(self::SETTINGS_OPTION, $legacy_settings, false);
                update_option(self::SETTINGS_HASH_OPTION, md5((string) wp_json_encode($legacy_settings)), false);
                $result['options_migrated'] = true;
            }
        }

        
        
        
        
        
        global $wpdb;
        $ids = $wpdb->get_col(
            "SELECT DISTINCT p.ID
             FROM {$wpdb->posts} p
             INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID
             INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
             INNER JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
             WHERE p.post_type = 'product' AND p.post_status = 'publish'
               AND tt.taxonomy = 'product_type' AND t.slug = 'variable'"
        );
        $ids    = array_map('intval', (array) $ids);
        $chunks = array_chunk($ids, 100);
        foreach ($chunks as $chunk) {
            if (function_exists('as_enqueue_async_action')) {
                as_enqueue_async_action(self::REBUILD_JOB_ACTION, array($chunk), 'aieo-dmm-swatches');
            } else {
                wp_schedule_single_event(time() + 1, self::REBUILD_JOB_ACTION, array($chunk));
            }
            $result['migrated'] += count($chunk);
        }

        return $result;
    }

    
    
    

    private function sanitize_settings(array $input) {
        $bool_keys = array(
            'enabled', 'tooltip', 'mouseover', 'on_shop_catalog',
            'display_variation_name_on_product_title',
            'multi_select_enabled', 'multi_select_show_quantity',
            'cart_sync_enabled', 'cart_summary_visible',
            'track_per_variation_atc',
            'button_stack_vertical_archive',
            'button_circle_archive',
        );
        
        
        
        
        
        
        $int_keys = array(
            
            'color_width', 'color_height', 'color_gap', 'color_border_radius',
            'color_border_size', 'color_border_size_hover', 'color_border_size_selected',
            'color_width_shop_archive', 'color_height_shop_archive',
            'color_gap_shop_archive', 'color_border_radius_shop_archive',
            'color_border_size_shop_archive',
            'color_border_size_hover_shop_archive', 'color_border_size_selected_shop_archive',

            
            'button_border_radius', 'button_border_size',
            'button_padding_top', 'button_padding_right', 'button_padding_bottom', 'button_padding_left',
            'button_gap',
            'button_border_radius_shop_archive', 'button_border_size_shop_archive',
            'button_padding_top_shop_archive', 'button_padding_right_shop_archive',
            'button_padding_bottom_shop_archive', 'button_padding_left_shop_archive',
            'button_gap_shop_archive',

            
            'image_width', 'image_height', 'image_gap',
            'image_border_radius', 'image_border_size',
            'image_border_size_hover', 'image_border_size_selected',
            'image_width_shop_archive', 'image_height_shop_archive', 'image_gap_shop_archive',
            'image_border_radius_shop_archive', 'image_border_size_shop_archive',
            'image_border_size_hover_shop_archive', 'image_border_size_selected_shop_archive',

            
            'image_width_mobile', 'image_height_mobile',
            'image_width_shop_archive_mobile', 'image_height_shop_archive_mobile',

            
            'button_padding_top_mobile', 'button_padding_right_mobile',
            'button_padding_bottom_mobile', 'button_padding_left_mobile',
            'button_font_size_mobile',
            'button_circle_diameter_archive',
            'button_font_size_shop_archive',

            
            'image_swatch_limit_desktop', 'image_swatch_limit_mobile',
            'color_swatch_limit_desktop', 'color_swatch_limit_mobile',
            'button_swatch_limit_desktop', 'button_swatch_limit_mobile',
        );
        
        
        
        $color_keys = array(
            'color_border_color', 'color_border_color_hover', 'color_border_color_selected',
            'color_border_color_shop_archive',
            'color_border_color_hover_shop_archive', 'color_border_color_selected_shop_archive',
            'button_text_color', 'button_text_color_hover', 'button_text_color_selected',
            'button_bg_color',   'button_bg_color_hover',   'button_bg_color_selected',
            'button_border_color','button_border_color_hover','button_border_color_selected',
            'button_text_color_shop_archive', 'button_text_color_hover_shop_archive', 'button_text_color_selected_shop_archive',
            'button_bg_color_shop_archive',   'button_bg_color_hover_shop_archive',   'button_bg_color_selected_shop_archive',
            'button_border_color_shop_archive','button_border_color_hover_shop_archive','button_border_color_selected_shop_archive',
            'image_border_color', 'image_border_color_hover', 'image_border_color_selected',
            'image_border_color_shop_archive',
            'image_border_color_hover_shop_archive', 'image_border_color_selected_shop_archive',
        );
        $enum_keys = array(
            'image_loading_strategy'       => array('lazy', 'eager', 'auto', 'swap'),
            'multi_select_display_style'   => array('grid', 'dropdown'),
            'multi_select_button_position' => array('above', 'below'),
            'cart_variation_image'         => array('product', 'variation', 'gallery', 'swatch'),
            'see_all_display_mode'         => array('swatches_only', 'text_only', 'both'),
        );
        $text_keys = array(
            'multi_select_button_text',
            'see_all_text', 'see_all_modal_title', 'see_all_view_product_text',
        );

        $out = array();

        foreach ($bool_keys as $k) {
            if ($k === 'enabled') {
                $out[$k] = (isset($input[$k]) && ($input[$k] === 'yes' || !empty($input[$k]))) ? 'yes' : 'no';
            } else {
                $out[$k] = !empty($input[$k]);
            }
        }
        foreach ($int_keys as $k) {
            if (isset($input[$k])) {
                $out[$k] = absint($input[$k]);
            }
        }
        foreach ($color_keys as $k) {
            if (!isset($input[$k])) { continue; }
            $raw = (string) $input[$k];
            $hex = function_exists('sanitize_hex_color') ? sanitize_hex_color($raw) : null;
            if ($hex !== null && $hex !== '') {
                $out[$k] = $hex;
            } elseif (preg_match('/^[a-zA-Z_][a-zA-Z0-9_\-\(\), \.%]*$/', $raw)) {
                
                $out[$k] = sanitize_text_field($raw);
            }
        }
        foreach ($enum_keys as $k => $allowed) {
            if (isset($input[$k]) && in_array($input[$k], $allowed, true)) {
                $out[$k] = $input[$k];
            }
        }
        foreach ($text_keys as $k) {
            if (isset($input[$k])) {
                $out[$k] = sanitize_text_field($input[$k]);
            }
        }

        foreach (array('multi_select_categories', 'dropdown_style_categories', 'grid_style_categories', 'quantity_selector_categories') as $cat_key) {
            if (isset($input[$cat_key]) && is_array($input[$cat_key])) {
                $out[$cat_key] = array_values(array_unique(array_map('absint', array_filter($input[$cat_key]))));
            } else {
                $out[$cat_key] = array();
            }
        }

        return $out;
    }

    
    
    

    public function render_subtab() {
        $this->render_template('swatches.php', array(
            'module'   => $this,
            'settings' => $this->get_settings(),
            'stats'    => $this->get_stats(),
        ));
    }

    
    public function enqueue_frontend_assets() {
        if (is_admin()) { return; }
        if ((string) get_option(self::RUNTIME_FLAG_OPTION, 'no') !== 'yes') { return; }

        
        
        
        
        
        
        
        if (defined('AIEO_RUNTIME_VERSION')) { return; }

        
        
        
        if (method_exists('AIEO_DMM', 'is_iapi_enabled') && AIEO_DMM::is_iapi_enabled('swatches')) { return; }

        
        
        
        
        if (!function_exists('is_woocommerce')) { return; }
        $aieo_swatches_ctx = is_woocommerce() || is_cart();
        if (!apply_filters('aieo_swatches_assets_in_context', $aieo_swatches_ctx)) { return; }

        
        
        
        wp_dequeue_style('om-product-swatches');
        wp_dequeue_script('om-product-swatches');
        wp_deregister_style('om-product-swatches');
        wp_deregister_script('om-product-swatches');

        $css_path = AIEO_DIR . 'assets/css/aieo-swatches.css';
        $js_path  = AIEO_DIR . 'assets/js/aieo-swatches.js';
        $css_ver  = file_exists($css_path) ? (string) filemtime($css_path) : AIEO_VERSION;
        $js_ver   = file_exists($js_path)  ? (string) filemtime($js_path)  : AIEO_VERSION;

        wp_enqueue_style(
            'aieo-swatches',
            AIEO_URI . 'assets/css/aieo-swatches.css',
            array(),
            $css_ver
        );
        wp_enqueue_script(
            'aieo-swatches',
            AIEO_URI . 'assets/js/aieo-swatches.js',
            array('jquery'),
            $js_ver,
            true
        );

        
        
        
        
        
        
        wp_add_inline_script(
            'aieo-swatches',
            'window.botigaInCartFlag = window.botigaInCartFlag || { variations_in_cart: [] };',
            'before'
        );

        $settings = get_option(self::SETTINGS_OPTION, array());
        if (!is_array($settings)) { $settings = array(); }

        $loc = array(
            'ajaxUrl'      => admin_url('admin-ajax.php'),
            'nonce'        => wp_create_nonce('aieo_swatches_frontend'),
            'legacyNonce'  => wp_create_nonce('om_product_swatches_frontend'),
            'settings'     => $settings,
            'actionPrefix' => 'aieo_swatch_',
        );
        wp_localize_script('aieo-swatches', 'omProductSwatches', $loc);
        wp_localize_script('aieo-swatches', 'aieoSwatches',      $loc);

        
        
        
        
        
        $is_mobile    = function_exists('wp_is_mobile') ? wp_is_mobile() : false;
        $image_limit  = $is_mobile
            ? (int) ($settings['image_swatch_limit_mobile']  ?? 3)
            : (int) ($settings['image_swatch_limit_desktop'] ?? 5);
        $color_limit  = $is_mobile
            ? (int) ($settings['color_swatch_limit_mobile']  ?? 3)
            : (int) ($settings['color_swatch_limit_desktop'] ?? 5);
        $button_limit = $is_mobile
            ? (int) ($settings['button_swatch_limit_mobile']  ?? 3)
            : (int) ($settings['button_swatch_limit_desktop'] ?? 5);

        $multi_select_categories = isset($settings['multi_select_categories'])
            ? array_map('absint', (array) $settings['multi_select_categories'])
            : array();
        $cart_sync_enabled = !empty($settings['cart_sync_enabled']);

        $script_data = array(
            'customizerOptions' => array(
                'tooltip'                            => !empty($settings['tooltip']),
                'displayVariationNameOnProductTitle' => !empty($settings['display_variation_name_on_product_title']),
                'imageSwatchLimitShopArchive'        => $image_limit,
                'colorSwatchLimitShopArchive'        => $color_limit,
                'buttonSwatchLimitShopArchive'       => $button_limit,
                'imageLoadingStrategy'               => (string) ($settings['image_loading_strategy'] ?? 'lazy'),
                'moreVariationsLabel'                => esc_html__('View %d more variations', 'ai-eshop-optimizer'),
                'multiSelectEnabled'                 => !empty($settings['multi_select_enabled']),
                'multiSelectDisplayStyle'            => (string) ($settings['multi_select_display_style'] ?? 'grid'),
                'multiSelectButtonText'              => (string) ($settings['multi_select_button_text'] ?? __('Add Selected to Cart', 'ai-eshop-optimizer')),
                'multiSelectButtonPosition'          => (string) ($settings['multi_select_button_position'] ?? 'above'),
                'multiSelectShowQuantity'            => !empty($settings['multi_select_show_quantity']),
                'multiSelectCategories'              => $multi_select_categories,
                'cartSyncEnabled'                    => $cart_sync_enabled,
                'cartSummaryVisible'                 => !empty($settings['cart_summary_visible']),
                'seeAllDisplayMode'                  => (string) ($settings['see_all_display_mode'] ?? 'swatches_only'),
                'seeAllText'                         => (string) ($settings['see_all_text'] ?? __('See all {count} variations', 'ai-eshop-optimizer')),
                'seeAllModalTitle'                   => (string) ($settings['see_all_modal_title'] ?? __('All Variations', 'ai-eshop-optimizer')),
                'seeAllViewProductText'              => (string) ($settings['see_all_view_product_text'] ?? __('View product page', 'ai-eshop-optimizer')),
            ),
            'ajax_url'  => admin_url('admin-ajax.php'),
            'security'  => wp_create_nonce('aieo_swatches_frontend'),
            'i18n' => array(
                'selected'          => __('selected', 'ai-eshop-optimizer'),
                'variation'         => __('variation', 'ai-eshop-optimizer'),
                'variations'        => __('variations', 'ai-eshop-optimizer'),
                'item'              => __('item', 'ai-eshop-optimizer'),
                'items'             => __('items', 'ai-eshop-optimizer'),
                'subtotal'          => __('Subtotal', 'ai-eshop-optimizer'),
                'selectVariations'  => __('Select variations', 'ai-eshop-optimizer'),
                'loadingVariations' => __('Loading variations...', 'ai-eshop-optimizer'),
                'noVariations'      => __('No variations available', 'ai-eshop-optimizer'),
                'failedToLoad'      => __('Failed to load variations', 'ai-eshop-optimizer'),
                'couldNotAdd'       => __('Could not add items to cart.', 'ai-eshop-optimizer'),
                'networkError'      => __('Network error. Please try again.', 'ai-eshop-optimizer'),
                
                
                'promptMessage'     => apply_filters('aieo_swatches_prompt_message', (string) (!empty($settings['multi_select_prompt_message']) ? $settings['multi_select_prompt_message'] : __('Select the variations you want to add to your cart', 'ai-eshop-optimizer'))),
                'successMessage'    => apply_filters('aieo_swatches_success_message', (string) (!empty($settings['multi_select_success_message']) ? $settings['multi_select_success_message'] : __('Great! {count} items added to your cart', 'ai-eshop-optimizer'))),
                'clearSelections'   => __('Clear selections', 'ai-eshop-optimizer'),
                'addingToCart'      => __('Adding...', 'ai-eshop-optimizer'),
                'addedToCart'       => __('Added!', 'ai-eshop-optimizer'),
                'addToCart'         => __('Add to cart', 'ai-eshop-optimizer'),
                'updateCart'        => __('Update cart', 'ai-eshop-optimizer'),
                'outOfStock'        => __('Out of stock', 'ai-eshop-optimizer'),
                'close'             => __('Close', 'ai-eshop-optimizer'),
                'inCart'            => __('in cart', 'ai-eshop-optimizer'),
                'in'                => __('in', 'ai-eshop-optimizer'),
                'viewDetails'       => __('View details', 'ai-eshop-optimizer'),
                'hideDetails'       => __('Hide details', 'ai-eshop-optimizer'),
                'variationOf'       => __('variation of this product', 'ai-eshop-optimizer'),
                'variationsOf'      => __('variations of this product', 'ai-eshop-optimizer'),
                'viewProductPage'   => __('View product page', 'ai-eshop-optimizer'),
            ),
            'cartVariations' => array(),
        );

        
        
        
        
        if ($cart_sync_enabled && function_exists('WC') && WC()->cart) {
            $cart_variations = array();
            foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) {
                $pid = isset($cart_item['product_id'])   ? (int) $cart_item['product_id']   : 0;
                $vid = isset($cart_item['variation_id']) ? (int) $cart_item['variation_id'] : 0;
                $qty = isset($cart_item['quantity'])     ? (int) $cart_item['quantity']     : 1;
                if ($pid <= 0 || $vid <= 0) { continue; }
                if (!isset($cart_variations[$pid])) { $cart_variations[$pid] = array(); }
                if (isset($cart_variations[$pid][$vid])) {
                    $cart_variations[$pid][$vid]['qty'] += $qty;
                } else {
                    $cart_variations[$pid][$vid] = array(
                        'qty'           => $qty,
                        'name'          => '',
                        'cart_item_key' => (string) $cart_item_key,
                    );
                }
            }
            $script_data['cartVariations'] = $cart_variations;
        }

        wp_localize_script('aieo-swatches', 'omSwatchSettings', $script_data);
    }

    
    public function enqueue_admin_assets() {
        wp_enqueue_style('wp-color-picker');
        wp_enqueue_script('wp-color-picker');

        $css = '
            /* Tighten the appearance tables on the swatches subtab.
               Default form-table layout stretches td columns to fill the
               admin panel, which leaves dozens of pixels of whitespace
               between the spinner and the px-unit label. Caps the td
               widths and aligns content compactly. */
            .aieo-swatch-styling{margin-top:6px;border:1px solid #e2e8f0;border-radius:6px;background:#fff;}
            .aieo-swatch-styling thead th{
                font-weight:600;color:#1f2937;padding:10px 12px;
                background:#f8fafc;border-bottom:1px solid #e2e8f0;
            }
            .aieo-swatch-styling tbody td,
            .aieo-swatch-styling tbody th{
                padding:8px 12px;vertical-align:middle;border-bottom:1px solid #f1f5f9;
                white-space:nowrap;
            }
            .aieo-swatch-styling tbody tr:last-child td,
            .aieo-swatch-styling tbody tr:last-child th{border-bottom:none;}
            .aieo-swatch-styling tbody th{font-weight:500;color:#374151;width:240px;}
            .aieo-swatch-styling tbody td{width:auto;}
            /* Force number inputs to a compact intrinsic width — wp-admin
               theme rules (e.g. form-table input override) had been
               stretching them to 100% of the td. !important is necessary
               to beat those generic-selector overrides without depending
               on selector specificity. */
            .aieo-swatch-styling input[type="number"]{
                width:64px !important;
                min-width:64px !important;
                max-width:64px !important;
                display:inline-block;
                vertical-align:middle;
                margin-right:4px;
            }
            /* "px" / "/" / span hints sit inline with the input, not on a
               new line. Combined with white-space:nowrap on the td. */
            .aieo-swatch-styling td .description,
            .aieo-swatch-styling td span{display:inline-block;vertical-align:middle;margin-left:2px;}
            /* The color picker wraps the input in its own .wp-picker-container.
               Pull the trigger button + clear button into a single inline row
               with the px-spinner. */
            .aieo-swatch-styling .wp-picker-container{display:inline-flex;align-items:center;gap:4px;margin-left:6px;vertical-align:middle;}
            .aieo-swatch-styling .wp-picker-container .wp-picker-input-wrap input.wp-color-picker{width:84px;}
            .aieo-swatch-styling .wp-picker-container .wp-color-result{height:26px;min-height:26px;line-height:24px;padding:0 0 0 30px;}
            .aieo-swatch-styling .wp-picker-container .wp-color-result-text{padding:0 6px;line-height:24px;}
            /* The picker popover floats above other rows when open. */
            .aieo-swatch-styling .wp-picker-active .wp-picker-holder{position:absolute;z-index:30;}
        ';
        wp_register_style('aieo-dmm-swatches-admin', false); 
        wp_enqueue_style('aieo-dmm-swatches-admin');
        wp_add_inline_style('aieo-dmm-swatches-admin', $css);

        $js = "
        jQuery(function($){
            $('.aieo-color-input').each(function(){
                var \$el = $(this);
                if (\$el.hasClass('wp-color-picker-init')) { return; }
                \$el.addClass('wp-color-picker-init').wpColorPicker({
                    // Compact picker — only show the spectrum/saturation
                    // grid, drop the default palette swatches row.
                    hide: true,
                    palettes: false
                });
            });
        });
        ";
        wp_register_script('aieo-dmm-swatches-admin', '', array('jquery', 'wp-color-picker'), AIEO_VERSION, true);
        wp_enqueue_script('aieo-dmm-swatches-admin');
        wp_add_inline_script('aieo-dmm-swatches-admin', $js);
    }

    
    
    

    
    public function add_product_data_tab($tabs) {
        $tabs['aieo_swatches'] = array(
            'label'    => __('Swatches', 'ai-eshop-optimizer'),
            'target'   => 'aieo_swatches_product_data',
            'class'    => array('show_if_variable'),
            'priority' => 65,
        );
        return $tabs;
    }

    
    public function render_product_data_panel() {
        global $post;
        if (!$post || get_post_type($post) !== 'product') { return; }

        $product_id    = (int) $post->ID;
        $display_style = (string) get_post_meta($product_id, '_om_swatch_display_style', true);
        $multi_select  = (string) get_post_meta($product_id, '_om_swatch_multi_select',  true);
        $show_quantity = (string) get_post_meta($product_id, '_om_swatch_show_quantity', true);
        ?>
        <div id="aieo_swatches_product_data" class="panel woocommerce_options_panel hidden">
            <div class="options_group">
                <p class="form-field">
                    <label for="_om_swatch_display_style"><?php esc_html_e('Display Style', 'ai-eshop-optimizer'); ?></label>
                    <select id="_om_swatch_display_style" name="_om_swatch_display_style" class="select short">
                        <option value=""         <?php selected($display_style, '');         ?>><?php esc_html_e('Use Default (Category/Global)', 'ai-eshop-optimizer'); ?></option>
                        <option value="grid"     <?php selected($display_style, 'grid');     ?>><?php esc_html_e('Grid (Standard Swatches)', 'ai-eshop-optimizer'); ?></option>
                        <option value="dropdown" <?php selected($display_style, 'dropdown'); ?>><?php esc_html_e('Dropdown List', 'ai-eshop-optimizer'); ?></option>
                    </select>
                    <?php
                    
                    echo wc_help_tip(__('Override the display style for this product. Overrides category and global settings.', 'ai-eshop-optimizer'));
                    ?>
                </p>

                <p class="form-field">
                    <label for="_om_swatch_multi_select"><?php esc_html_e('Multi-Select Mode', 'ai-eshop-optimizer'); ?></label>
                    <select id="_om_swatch_multi_select" name="_om_swatch_multi_select" class="select short">
                        <option value=""    <?php selected($multi_select, '');    ?>><?php esc_html_e('Use Default (Category/Global)', 'ai-eshop-optimizer'); ?></option>
                        <option value="yes" <?php selected($multi_select, 'yes'); ?>><?php esc_html_e('Enabled', 'ai-eshop-optimizer'); ?></option>
                        <option value="no"  <?php selected($multi_select, 'no');  ?>><?php esc_html_e('Disabled', 'ai-eshop-optimizer'); ?></option>
                    </select>
                    <?php
                    
                    echo wc_help_tip(__('Allow customers to select multiple variations at once (batch add to cart).', 'ai-eshop-optimizer'));
                    ?>
                </p>

                <p class="form-field">
                    <label for="_om_swatch_show_quantity"><?php esc_html_e('Quantity Selectors', 'ai-eshop-optimizer'); ?></label>
                    <select id="_om_swatch_show_quantity" name="_om_swatch_show_quantity" class="select short">
                        <option value=""    <?php selected($show_quantity, '');    ?>><?php esc_html_e('Use Default (Category/Global)', 'ai-eshop-optimizer'); ?></option>
                        <option value="yes" <?php selected($show_quantity, 'yes'); ?>><?php esc_html_e('Show', 'ai-eshop-optimizer'); ?></option>
                        <option value="no"  <?php selected($show_quantity, 'no');  ?>><?php esc_html_e('Hide', 'ai-eshop-optimizer'); ?></option>
                    </select>
                    <?php
                    
                    echo wc_help_tip(__('Show quantity inputs per variation (only applies in dropdown mode).', 'ai-eshop-optimizer'));
                    ?>
                </p>
            </div>

            <div class="options_group">
                <p class="description" style="padding: 0 12px 12px;">
                    <strong><?php esc_html_e('Settings Hierarchy:', 'ai-eshop-optimizer'); ?></strong><br>
                    <?php esc_html_e('Product Override → Category Override → Global Default', 'ai-eshop-optimizer'); ?>
                </p>
            </div>
        </div>
        <?php
    }

    
    public function save_product_meta($product_id) {
        $product_id = (int) $product_id;
        if ($product_id <= 0 || !current_user_can('edit_product', $product_id)) { return; }

        $fields = array(
            '_om_swatch_display_style' => array('', 'grid', 'dropdown'),
            '_om_swatch_multi_select'  => array('', 'yes',  'no'),
            '_om_swatch_show_quantity' => array('', 'yes',  'no'),
        );

        foreach ($fields as $meta_key => $allowed) {
            
            $raw = isset($_POST[$meta_key]) ? sanitize_text_field(wp_unslash($_POST[$meta_key])) : '';
            if (!in_array($raw, $allowed, true)) { continue; }
            if ($raw === '') {
                delete_post_meta($product_id, $meta_key);
            } else {
                update_post_meta($product_id, $meta_key, $raw);
            }
        }

        
        
        
    }

    
    public function enqueue_product_scripts($hook) {
        if (!in_array($hook, array('post.php', 'post-new.php'), true)) { return; }
        global $post;
        if (!$post || $post->post_type !== 'product') { return; }

        wp_add_inline_style('woocommerce_admin_styles',
            '#woocommerce-product-data ul.wc-tabs li.aieo_swatches_options a::before{content:"\f111";font-family:dashicons;}'
        );
    }

    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

    
    
    public function render_custom_attribute_terms_select($attribute_taxonomy, $index, $attribute) {
        if (!$attribute_taxonomy || !is_object($attribute_taxonomy)) { return; }
        $type = isset($attribute_taxonomy->attribute_type) ? (string) $attribute_taxonomy->attribute_type : '';
        if (!in_array($type, array('color', 'image', 'button'), true)) { return; }
        if (!is_object($attribute) || !method_exists($attribute, 'is_taxonomy') || !$attribute->is_taxonomy()) { return; }

        $term_limit = absint(apply_filters('woocommerce_admin_terms_metabox_datalimit', 50));
        $orderby    = !empty($attribute_taxonomy->attribute_orderby) ? $attribute_taxonomy->attribute_orderby : 'name';
        $taxonomy   = $attribute->get_taxonomy();
        $selected   = $attribute->get_terms();
        ?>
        <select multiple="multiple"
                data-minimum_input_length="0"
                data-limit="<?php echo esc_attr($term_limit); ?>"
                data-return_id="id"
                data-placeholder="<?php esc_attr_e('Select values', 'ai-eshop-optimizer'); ?>"
                data-orderby="<?php echo esc_attr($orderby); ?>"
                class="multiselect attribute_values wc-taxonomy-term-search"
                name="attribute_values[<?php echo esc_attr($index); ?>][]"
                data-taxonomy="<?php echo esc_attr($taxonomy); ?>">
            <?php
            if ($selected) {
                foreach ($selected as $term) {
                    
                    echo '<option value="' . esc_attr($term->term_id) . '" selected="selected">'
                        . esc_html(apply_filters('woocommerce_product_attribute_term_name', $term->name, $term))
                        . '</option>';
                }
            }
            ?>
        </select>
        <button class="button plus select_all_attributes"><?php esc_html_e('Select all', 'ai-eshop-optimizer'); ?></button>
        <button class="button minus select_no_attributes"><?php esc_html_e('Select none', 'ai-eshop-optimizer'); ?></button>
        <?php
    }

    

    
    private function colour_attributes_table() {
        global $wpdb;
        return $wpdb->prefix . 'aieo_dmm_color_attributes';
    }

    
    public function attribute_is_colour($taxonomy) {
        global $wpdb;
        $taxonomy = (string) $taxonomy;
        if ($taxonomy === '') { return false; }
        $table = $this->colour_attributes_table();
        
        return (bool) $wpdb->get_var($wpdb->prepare("SELECT 1 FROM {$table} WHERE taxonomy = %s", $taxonomy));
    }

    
    public function render_attribute_colour_flag_add() {
        ?>
        <div class="form-field">
            <label for="aieo_attribute_is_colour">
                <input name="aieo_attribute_is_colour" id="aieo_attribute_is_colour" type="checkbox" value="1" />
                <?php esc_html_e('AIEO colour context', 'ai-eshop-optimizer'); ?>
            </label>
            <p class="description"><?php esc_html_e("Tick if this attribute's terms are product colours/shades (e.g. \xE2\x80\x9CFrench Caramel\xE2\x80\x9D). AIEO reads the term name into the product colour used for SEO schema and marketplace feeds \xE2\x80\x94 even when the attribute type is Image.", 'ai-eshop-optimizer'); ?></p>
        </div>
        <?php
    }

    
    public function render_attribute_colour_flag_edit() {
        $taxonomy = '';
        
        $edit_id = isset($_GET['edit']) ? absint(wp_unslash($_GET['edit'])) : 0; 
        if ($edit_id && function_exists('wc_attribute_taxonomy_name_by_id')) {
            $taxonomy = (string) wc_attribute_taxonomy_name_by_id($edit_id);
        }
        $checked = $taxonomy !== '' && $this->attribute_is_colour($taxonomy);
        ?>
        <tr class="form-field">
            <th scope="row" valign="top">
                <label for="aieo_attribute_is_colour"><?php esc_html_e('AIEO colour context', 'ai-eshop-optimizer'); ?></label>
            </th>
            <td>
                <input name="aieo_attribute_is_colour" id="aieo_attribute_is_colour" type="checkbox" value="1" <?php checked($checked); ?> />
                <p class="description"><?php esc_html_e("Tick if this attribute's terms are product colours/shades (e.g. \xE2\x80\x9CFrench Caramel\xE2\x80\x9D). AIEO reads the term name into the product colour used for SEO schema and marketplace feeds \xE2\x80\x94 even when the attribute type is Image.", 'ai-eshop-optimizer'); ?></p>
            </td>
        </tr>
        <?php
    }

    
    public function save_attribute_colour_flag_added($id, $data) {
        $name = isset($data['attribute_name']) ? (string) $data['attribute_name'] : '';
        if ($name === '') { return; }
        $label = isset($data['attribute_label']) && $data['attribute_label'] !== '' ? (string) $data['attribute_label'] : $name;
        $this->store_attribute_colour_flag('pa_' . $name, (int) $id, $label);
    }

    
    public function save_attribute_colour_flag_updated($id, $data, $old_taxonomy) {
        $name         = isset($data['attribute_name']) ? (string) $data['attribute_name'] : '';
        $new_taxonomy = $name !== '' ? 'pa_' . $name : '';
        if ($old_taxonomy && $old_taxonomy !== $new_taxonomy) {
            $this->delete_attribute_colour_flag((string) $old_taxonomy);
        }
        if ($new_taxonomy === '') { return; }
        $label = isset($data['attribute_label']) && $data['attribute_label'] !== '' ? (string) $data['attribute_label'] : $name;
        $this->store_attribute_colour_flag($new_taxonomy, (int) $id, $label);
    }

    
    private function store_attribute_colour_flag($taxonomy, $attribute_id, $label) {
        global $wpdb;
        
        $is_colour = isset($_POST['aieo_attribute_is_colour']) && (string) wp_unslash($_POST['aieo_attribute_is_colour']) === '1'; 
        if (!$is_colour) {
            $this->delete_attribute_colour_flag($taxonomy);
            return;
        }
        $table = $this->colour_attributes_table();
        
        
        $wpdb->query($wpdb->prepare(
            "INSERT INTO {$table} (taxonomy, attribute_id, attribute_label)
             VALUES (%s, %d, %s)
             ON DUPLICATE KEY UPDATE attribute_id = VALUES(attribute_id), attribute_label = VALUES(attribute_label)",
            (string) $taxonomy,
            (int) $attribute_id,
            (string) $label
        ));
    }

    
    private function delete_attribute_colour_flag($taxonomy) {
        global $wpdb;
        $wpdb->delete($this->colour_attributes_table(), array('taxonomy' => (string) $taxonomy), array('%s'));
    }

    
    public function get_colour_flag_count() {
        global $wpdb;
        $table = $this->colour_attributes_table();
        
        return (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
    }

    
    public function ajax_bulk_colour_flags() {
        AIEO_DMM::verify_ajax_request();

        global $wpdb;
        $op    = isset($_POST['op']) ? sanitize_key(wp_unslash($_POST['op'])) : '';
        $table = $this->colour_attributes_table();
        $wc    = $wpdb->prefix . 'woocommerce_attribute_taxonomies';

        if ($op === 'flag_image' || $op === 'flag_image_button') {
            
            $types = ($op === 'flag_image_button') ? "'image','button'" : "'image'";
            
            $wpdb->query(
                "INSERT IGNORE INTO {$table} (taxonomy, attribute_id, attribute_label)
                 SELECT CONCAT('pa_', attribute_name), attribute_id, attribute_label
                 FROM {$wc}
                 WHERE attribute_type IN ({$types})"
            );
            $msg = __('Swatch attributes flagged as colour.', 'ai-eshop-optimizer');
        } elseif ($op === 'clear') {
            
            $wpdb->query("DELETE FROM {$table}");
            $msg = __('All colour flags cleared.', 'ai-eshop-optimizer');
        } else {
            wp_send_json_error(array('message' => __('Unknown operation.', 'ai-eshop-optimizer')));
        }

        wp_send_json_success(array(
            'message'       => $msg,
            'flagged_count' => $this->get_colour_flag_count(),
        ));
    }

    public function available_attribute_types($types) {
        if (!is_array($types)) { $types = array(); }
        if (!isset($types['select'])) { $types['select'] = __('Select', 'ai-eshop-optimizer'); }

        
        $types['image']  = __('Image',  'ai-eshop-optimizer');
        $types['button'] = __('Button', 'ai-eshop-optimizer');

        
        
        
        
        
        
        if (!isset($types['wc-visual'])) {
            $types['color'] = __('Color', 'ai-eshop-optimizer');
        }
        return $types;
    }

    
    public function register_term_admin_hooks() {
        if (!function_exists('wc_get_attribute_taxonomies')) { return; }
        $taxes = wc_get_attribute_taxonomies();
        if (empty($taxes)) { return; }
        foreach ($taxes as $att_tax) {
            $tax_slug = 'pa_' . $att_tax->attribute_name;
            add_action($tax_slug . '_add_form_fields',  array($this, 'render_term_swatch_fields_add'));
            add_action($tax_slug . '_edit_form_fields', array($this, 'render_term_swatch_fields_edit'), 10, 2);
        }
    }

    
    public function save_term_swatch_fields($term_id, $tt_id, $taxonomy) {
        if (strpos((string) $taxonomy, 'pa_') !== 0) { return; }
        $att = $this->get_attribute_taxonomy_by_slug($taxonomy);
        if (!$att) { return; }

        $type = (string) $att->attribute_type;
        $term_id = (int) $term_id;

        
        
        if ($type === 'color' || $type === 'wc-visual') {
            $color_type   = isset($_POST['tag-color-type']) ? sanitize_text_field(wp_unslash($_POST['tag-color-type'])) : 'background';
            $bg_color     = isset($_POST['tag-color']) ? sanitize_text_field(wp_unslash($_POST['tag-color'])) : '';
            $grad_c1      = isset($_POST['tag-color-gradient-color-1']) ? sanitize_text_field(wp_unslash($_POST['tag-color-gradient-color-1'])) : '';
            $grad_c2      = isset($_POST['tag-color-gradient-color-2']) ? sanitize_text_field(wp_unslash($_POST['tag-color-gradient-color-2'])) : '';
            $grad_dir     = isset($_POST['tag-color-gradient-direction']) ? sanitize_text_field(wp_unslash($_POST['tag-color-gradient-direction'])) : '';
            $multi_dir    = isset($_POST['tag-color-multiple-direction']) ? sanitize_text_field(wp_unslash($_POST['tag-color-multiple-direction'])) : '';
            $multi_colors = array_filter(array(
                isset($_POST['tag-color-multiple-color-1']) ? sanitize_text_field(wp_unslash($_POST['tag-color-multiple-color-1'])) : '',
                isset($_POST['tag-color-multiple-color-2']) ? sanitize_text_field(wp_unslash($_POST['tag-color-multiple-color-2'])) : '',
                isset($_POST['tag-color-multiple-color-3']) ? sanitize_text_field(wp_unslash($_POST['tag-color-multiple-color-3'])) : '',
                isset($_POST['tag-color-multiple-color-4']) ? sanitize_text_field(wp_unslash($_POST['tag-color-multiple-color-4'])) : '',
            ));

            
            
            
            $canonical_hex = '';
            if ($color_type === 'gradient' && $grad_c1 !== '') {
                $canonical_hex = $grad_c1;
            } elseif ($color_type === 'multiple' && !empty($multi_colors)) {
                $canonical_hex = reset($multi_colors);
            } elseif ($bg_color !== '') {
                $canonical_hex = $bg_color;
            }

            $sanitised = $canonical_hex !== '' ? sanitize_hex_color($canonical_hex) : '';
            if ($sanitised) {
                update_term_meta($term_id, 'color', $sanitised);
            } else {
                delete_term_meta($term_id, 'color');
            }

            
            if ($color_type !== '' && $color_type !== 'background') {
                update_term_meta($term_id, '_aieo_swatch_color_type', $color_type);
            } else {
                delete_term_meta($term_id, '_aieo_swatch_color_type');
            }

            if ($color_type === 'gradient' && ($grad_c1 !== '' || $grad_c2 !== '')) {
                update_term_meta($term_id, '_aieo_swatch_gradient', array(
                    'color_1'   => $grad_c1,
                    'color_2'   => $grad_c2,
                    'direction' => $grad_dir,
                ));
            } else {
                delete_term_meta($term_id, '_aieo_swatch_gradient');
            }

            if ($color_type === 'multiple' && !empty($multi_colors)) {
                update_term_meta($term_id, '_aieo_swatch_multiple', array(
                    'colors'    => array_values($multi_colors),
                    'direction' => $multi_dir,
                ));
            } else {
                delete_term_meta($term_id, '_aieo_swatch_multiple');
            }
        } elseif ($type === 'image') {
            $image_id = isset($_POST['tag-image']) ? absint($_POST['tag-image']) : 0;
            if ($image_id > 0) {
                update_term_meta($term_id, '_aieo_swatch_image_id', $image_id);
            } else {
                delete_term_meta($term_id, '_aieo_swatch_image_id');
            }
        }
    }

    
    public function render_term_swatch_fields_add($taxonomy) {
        $att = $this->get_attribute_taxonomy_by_slug($taxonomy);
        if ($att) {
            $this->render_term_swatch_fields($att->attribute_type, $att->attribute_label, null, false);
        }
    }

    
    public function render_term_swatch_fields_edit($term, $taxonomy) {
        $att = $this->get_attribute_taxonomy_by_slug($taxonomy);
        if ($att) {
            $this->render_term_swatch_fields($att->attribute_type, $att->attribute_label, $term, true);
        }
    }

    
    private function render_term_swatch_fields($type, $label, $term, $is_edit) {
        if ($type !== 'color' && $type !== 'image') { return; }

        $wrap_tag  = $is_edit ? 'tr' : 'div';
        $label_tag = $is_edit ? 'th' : 'label';
        $field_tag = $is_edit ? 'td' : 'div';

        echo '<' . $wrap_tag . ' class="form-field aieo-swatch-form-field aieo-swatch-form-field--' . esc_attr($type) . '">'; 
        echo '<' . $label_tag . '>' . esc_html($label) . '</' . $label_tag . '>'; 
        echo '<' . $field_tag . '>'; 
        if ($type === 'color') {
            $this->render_term_swatch_color_fields($term);
        } else {
            $this->render_term_swatch_image_field($term);
        }
        echo '</' . $field_tag . '>'; 
        echo '</' . $wrap_tag . '>'; 
    }

    private function render_term_swatch_color_fields($term) {
        $color_types = array(
            'background' => __('Background', 'ai-eshop-optimizer'),
            'gradient'   => __('Gradient',   'ai-eshop-optimizer'),
            'multiple'   => __('Multiple Colours', 'ai-eshop-optimizer'),
        );
        $directions = array(
            'to bottom' => '↓',
            'to right'  => '→',
            '135deg'    => '↘',
            '-135deg'   => '↙',
        );

        
        $all_meta = $term ? get_term_meta($term->term_id) : array();
        $g = static function ($k) use ($all_meta) {
            return isset($all_meta[$k][0]) ? (string) $all_meta[$k][0] : '';
        };
        $color_type   = $g('product_attribute_color_type');
        $color        = $g('product_attribute_color');
        $g_c1         = $g('product_attribute_color_gradient_color_1');
        $g_c2         = $g('product_attribute_color_gradient_color_2');
        $g_dir        = $g('product_attribute_color_gradient_direction');
        $m_c1         = $g('product_attribute_color_multiple_color_1');
        $m_c2         = $g('product_attribute_color_multiple_color_2');
        $m_c3         = $g('product_attribute_color_multiple_color_3');
        $m_c4         = $g('product_attribute_color_multiple_color_4');
        $m_dir        = $g('product_attribute_color_multiple_direction');

        
        
        $shown_type   = $color_type !== '' ? $color_type : 'background';
        $hidden       = static function ($want) use ($shown_type) {
            return $shown_type === $want ? '' : ' hidden';
        };
        ?>
        <div class="aieo-swatch-color-wrapper">
            <p>
                <label><strong><?php esc_html_e('Type', 'ai-eshop-optimizer'); ?></strong></label><br>
                <select name="tag-color-type" class="aieo-swatch-color-type">
                    <?php foreach ($color_types as $k => $v) : ?>
                        <option value="<?php echo esc_attr($k); ?>" <?php selected($shown_type, $k); ?>><?php echo esc_html($v); ?></option>
                    <?php endforeach; ?>
                </select>
            </p>

            <div class="aieo-swatch-color-set aieo-swatch-color-set--background<?php echo esc_attr($hidden('background')); ?>">
                <p>
                    <label><?php esc_html_e('Colour', 'ai-eshop-optimizer'); ?></label><br>
                    <input type="text" name="tag-color" value="<?php echo esc_attr($color); ?>" class="aieo-swatch-color-picker" data-default-color="" />
                </p>
            </div>

            <div class="aieo-swatch-color-set aieo-swatch-color-set--gradient<?php echo esc_attr($hidden('gradient')); ?>">
                <p>
                    <label><?php esc_html_e('Color 1', 'ai-eshop-optimizer'); ?></label><br>
                    <input type="text" name="tag-color-gradient-color-1" value="<?php echo esc_attr($g_c1); ?>" class="aieo-swatch-color-picker" />
                </p>
                <p>
                    <label><?php esc_html_e('Color 2', 'ai-eshop-optimizer'); ?></label><br>
                    <input type="text" name="tag-color-gradient-color-2" value="<?php echo esc_attr($g_c2); ?>" class="aieo-swatch-color-picker" />
                </p>
                <p>
                    <label><?php esc_html_e('Direction', 'ai-eshop-optimizer'); ?></label><br>
                    <select name="tag-color-gradient-direction">
                        <?php foreach ($directions as $k => $v) : ?>
                            <option value="<?php echo esc_attr($k); ?>" <?php selected($g_dir, $k); ?>><?php echo esc_html($v); ?></option>
                        <?php endforeach; ?>
                    </select>
                </p>
            </div>

            <div class="aieo-swatch-color-set aieo-swatch-color-set--multiple<?php echo esc_attr($hidden('multiple')); ?>">
                <?php foreach (array(1,2,3,4) as $i) :
                    $val = ${'m_c' . $i};
                ?>
                <p>
                    <label><?php echo esc_html(sprintf( __('Color %d', 'ai-eshop-optimizer'), $i)); ?></label><br>
                    <input type="text" name="tag-color-multiple-color-<?php echo (int) $i; ?>" value="<?php echo esc_attr($val); ?>" class="aieo-swatch-color-picker" />
                </p>
                <?php endforeach; ?>
                <p>
                    <label><?php esc_html_e('Direction', 'ai-eshop-optimizer'); ?></label><br>
                    <select name="tag-color-multiple-direction">
                        <?php foreach ($directions as $k => $v) : ?>
                            <option value="<?php echo esc_attr($k); ?>" <?php selected($m_dir, $k); ?>><?php echo esc_html($v); ?></option>
                        <?php endforeach; ?>
                    </select>
                </p>
            </div>
        </div>
        <?php
    }

    private function render_term_swatch_image_field($term) {
        $image_id    = $term ? (int) get_term_meta($term->term_id, 'product_attribute_image', true) : 0;
        $placeholder = function_exists('wc_placeholder_img_src') ? wc_placeholder_img_src('thumbnail') : '';
        $thumb       = $placeholder;
        if ($image_id > 0) {
            $img = wp_get_attachment_image_src($image_id, 'thumbnail');
            if (is_array($img) && !empty($img[0])) { $thumb = $img[0]; }
        }
        $remove_hidden = $image_id > 0 ? '' : ' hidden';
        ?>
        <div class="aieo-swatch-image-field">
            <input type="hidden" name="tag-image" class="aieo-swatch-image-input" value="<?php echo esc_attr($image_id); ?>" />
            <figure class="aieo-swatch-image-preview">
                <img src="<?php echo esc_url($thumb); ?>" data-placeholder="<?php echo esc_url($placeholder); ?>" alt="" />
            </figure>
            <p>
                <button type="button" class="button aieo-swatch-image-upload"><?php esc_html_e('Upload', 'ai-eshop-optimizer'); ?></button>
                <button type="button" class="button aieo-swatch-image-remove<?php echo esc_attr($remove_hidden); ?>"><?php esc_html_e('Remove', 'ai-eshop-optimizer'); ?></button>
            </p>
        </div>
        <?php
    }

    
    private function get_attribute_taxonomy_by_slug($taxonomy) {
        if (!function_exists('wc_get_attribute_taxonomies')) { return null; }
        $taxes = wc_get_attribute_taxonomies();
        foreach ($taxes as $t) {
            if ('pa_' . $t->attribute_name === $taxonomy) { return $t; }
        }
        return null;
    }

    
    public function enqueue_term_admin_assets($hook) {
        if ($hook !== 'edit-tags.php' && $hook !== 'term.php') { return; }
        $tax = isset($_GET['taxonomy']) ? sanitize_key((string) $_GET['taxonomy']) : '';
        if (strpos($tax, 'pa_') !== 0) { return; }

        wp_enqueue_media();
        wp_enqueue_style('wp-color-picker');
        wp_enqueue_script('wp-color-picker');

        $css = '
            .aieo-swatch-color-wrapper{max-width:480px}
            .aieo-swatch-color-wrapper>p{margin:0 0 10px}
            .aieo-swatch-color-set{padding:10px 12px;background:#f8fafc;border:1px solid #e5e7eb;border-radius:4px;margin:0 0 10px}
            .aieo-swatch-color-set.hidden,.aieo-swatch-image-field .button.hidden{display:none}
            .aieo-swatch-image-field{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
            .aieo-swatch-image-preview{margin:0;border:1px solid #d1d5db;border-radius:4px;width:80px;height:80px;overflow:hidden;background:#fff;display:flex;align-items:center;justify-content:center}
            .aieo-swatch-image-preview img{max-width:100%;max-height:100%;display:block}
        ';
        wp_register_style('aieo-swatch-term-admin', false); 
        wp_enqueue_style('aieo-swatch-term-admin');
        wp_add_inline_style('aieo-swatch-term-admin', $css);

        $js = "
        jQuery(function($){
            // 1. WP color-picker on every hex input.
            $('.aieo-swatch-color-picker').each(function(){
                if (!$(this).hasClass('wp-color-picker-init')) { $(this).addClass('wp-color-picker-init').wpColorPicker(); }
            });
            // 2. Toggle visible color-set on type change.
            function syncColorSets(){
                var t = $('.aieo-swatch-color-type').val() || 'background';
                $('.aieo-swatch-color-set').addClass('hidden');
                $('.aieo-swatch-color-set--' + t).removeClass('hidden');
            }
            $(document).on('change', '.aieo-swatch-color-type', syncColorSets);
            syncColorSets();
            // 3. Image picker — open WP media frame, set hidden id + thumb.
            var frame;
            $(document).on('click', '.aieo-swatch-image-upload', function(e){
                e.preventDefault();
                var \$wrap = $(this).closest('.aieo-swatch-image-field');
                if (frame) { frame.off('select'); }
                frame = wp.media({title:'" . esc_js(__('Choose image', 'ai-eshop-optimizer')) . "',multiple:false,library:{type:'image'}});
                frame.on('select', function(){
                    var att = frame.state().get('selection').first().toJSON();
                    \$wrap.find('.aieo-swatch-image-input').val(att.id);
                    var url = (att.sizes && att.sizes.thumbnail) ? att.sizes.thumbnail.url : att.url;
                    \$wrap.find('.aieo-swatch-image-preview img').attr('src', url);
                    \$wrap.find('.aieo-swatch-image-remove').removeClass('hidden');
                });
                frame.open();
            });
            $(document).on('click', '.aieo-swatch-image-remove', function(e){
                e.preventDefault();
                var \$wrap = $(this).closest('.aieo-swatch-image-field');
                \$wrap.find('.aieo-swatch-image-input').val('');
                var \$img = \$wrap.find('.aieo-swatch-image-preview img');
                \$img.attr('src', \$img.data('placeholder') || '');
                $(this).addClass('hidden');
            });
        });
        ";
        wp_register_script('aieo-swatch-term-admin', '', array('jquery', 'wp-color-picker'), AIEO_VERSION , true);
        wp_enqueue_script('aieo-swatch-term-admin');
        wp_add_inline_script('aieo-swatch-term-admin', $js);
    }
}

if (!has_filter('woocommerce_get_item_data', array('AIEO_DMM_Swatches', 'cart_item_variation_description'))) {
    add_filter('woocommerce_get_item_data',                   array('AIEO_DMM_Swatches', 'cart_item_variation_description'), 10, 2);
    add_action('woocommerce_checkout_create_order_line_item', array('AIEO_DMM_Swatches', 'order_item_variation_description'), 10, 4);
}
