<?php

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

class AIEO_DMM_Swatches_Runtime {

    
    private static $instance = null;

    
    private $settings = null;

    
    private $settings_hash = '';

    
    private $pending_rebuilds = array();

    
    private $one_to_one_memo = array();

    
    private static $rendered_for_product = array();

    
    private $taxonomy_cache = array();

    
    private $row_cache = array();

    
    private $product_settings_cache = array();

    public static function instance() {
        if ( self::$instance === null ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        
        
        
        if ( ! $this->is_runtime_enabled() ) {
            return;
        }

        
        
        
        add_action( 'after_setup_theme', array( $this, 'register_swatch_image_sizes' ), 999 );

        
        
        
        
        
        
        add_action( 'wp_head', array( $this, 'output_custom_css' ), 100 );
        add_action( 'wp_head', array( $this, 'output_collapse_secondary_css' ), 101 );

        
        
        
        add_action( 'wp_head', array( $this, 'output_early_multi_select_css' ), 1 );

        
        
        
        add_action( 'wp', array( $this, 'setup_archive_swatches' ) );

        
        
        
        
        add_action( 'wp', array( $this, 'maybe_remove_default_add_to_cart' ), 20 );

        
        
        
        
        
        
        

        
        
        
        
        add_action( 'wp_footer', array( $this, 'output_cart_variations_data_tag' ) );
        
        
        add_filter( 'woocommerce_add_to_cart_fragments', array( $this, 'add_cart_variations_fragment' ) );

        
        
        
        
        
        add_filter( 'body_class', array( $this, 'filter_body_class' ) );

        
        
        
        
        
        
        
        
        add_filter( 'woocommerce_ajax_variation_threshold', array( $this, 'filter_ajax_variation_threshold' ), 10, 2 );

        
        
        
        add_filter( 'woocommerce_dropdown_variation_attribute_options_html',
            array( $this, 'filter_variation_attribute_options_html' ), 10, 2 );

        
        add_filter( 'woocommerce_cart_item_thumbnail', array( $this, 'filter_cart_item_thumbnail' ), 10, 3 );

        
        
        
        
        
        add_action( 'shutdown', array( $this, 'flush_pending_rebuilds' ) );
    }

    private function is_runtime_enabled() {
        return (string) get_option( 'aieo_dmm_swatches_runtime_enabled', 'no' ) === 'yes';
    }

    private function get_settings() {
        if ( $this->settings === null ) {
            $stored = get_option( 'aieo_dmm_swatches_settings', array() );
            $this->settings = is_array( $stored ) ? $stored : array();
            $this->settings_hash = (string) get_option( 'aieo_dmm_swatches_settings_hash', '' );
            
            
            
            if ( $this->settings_hash !== '' && class_exists( 'AIEO_DMM_Swatches' ) ) {
                $this->settings_hash .= AIEO_DMM_Swatches::SWATCH_PAYLOAD_SUFFIX;
            }
        }
        return $this->settings;
    }

    
    public function register_swatch_image_sizes() {
        $s = $this->get_settings();
        $pw = (int) ( $s['image_width']  ?? 40 );
        $ph = (int) ( $s['image_height'] ?? 40 );
        $aw = (int) ( $s['image_width_shop_archive']  ?? 26 );
        $ah = (int) ( $s['image_height_shop_archive'] ?? 26 );

        if ( $pw > 0 && $ph > 0 ) {
            add_image_size( 'om_swatch_product', $pw, $ph, true );
        }
        if ( $aw > 0 && $ah > 0 ) {
            add_image_size( 'om_swatch_archive', $aw, $ah, true );
        }
    }

    
    
    public function output_collapse_secondary_css() {
        echo '<style>'
            . '.om-product-swatches[data-aieo-collapse-secondary="1"] .variations tr:nth-of-type(n+2),'
            . '.om-product-swatches[data-aieo-collapse-secondary="1"] .om-variations-wrapper:nth-of-type(n+2){display:none!important;}'
            . '</style>';
    }

    public function output_custom_css() {
        
        
        
        
        if ( class_exists( 'OM_Product_Swatches_Handler' ) ) { return; }

        $s = $this->get_settings();

        $v = function ( $key, $default ) use ( $s ) {
            if ( ! isset( $s[ $key ] ) || $s[ $key ] === '' ) { return $default; }
            return $s[ $key ];
        };

        $css  = '';

        
        $css .= ".om-variation-type-color a {"
              . "width:{$v('color_width',24)}px;height:{$v('color_height',24)}px;"
              . "border-radius:{$v('color_border_radius',50)}px;"
              . "border:{$v('color_border_size',1)}px solid {$v('color_border_color','#dedede')};"
              . "}";
        $css .= ".om-variation-type-color a:hover{"
              . "border:{$v('color_border_size_hover',2)}px solid {$v('color_border_color_hover','#212121')};"
              . "}";
        $css .= ".om-variation-type-color a.active,.om-variation-type-color a[aria-checked='true']{"
              . "border:{$v('color_border_size_selected',2)}px solid {$v('color_border_color_selected','#212121')};"
              . "}";
        $css .= ".om-variation-type-color{gap:{$v('color_gap',8)}px;}";

        
        $css .= ".om-variation-type-button a{"
              . "color:{$v('button_text_color','#212121')};"
              . "background-color:{$v('button_bg_color','#ffffff')};"
              . "border:{$v('button_border_size',1)}px solid {$v('button_border_color','#212121')};"
              . "border-radius:{$v('button_border_radius',5)}px;"
              . "padding:{$v('button_padding_top',8)}px {$v('button_padding_right',12)}px {$v('button_padding_bottom',8)}px {$v('button_padding_left',12)}px;"
              . "}";
        $css .= ".om-variation-type-button a:hover{"
              . "color:{$v('button_text_color_hover','#ffffff')};"
              . "background-color:{$v('button_bg_color_hover','#212121')};"
              . "border-color:{$v('button_border_color_hover','#212121')};"
              . "}";
        $css .= ".om-variation-type-button a.active,.om-variation-type-button a[aria-checked='true']{"
              . "color:{$v('button_text_color_selected','#ffffff')};"
              . "background-color:{$v('button_bg_color_selected','#212121')};"
              . "border-color:{$v('button_border_color_selected','#212121')};"
              . "}";
        $css .= ".om-variation-type-button{gap:{$v('button_gap',8)}px;}";

        
        $css .= ".om-variation-type-image a{"
              . "width:{$v('image_width',40)}px;height:{$v('image_height',40)}px;"
              . "border-radius:{$v('image_border_radius',0)}px;"
              . "border:{$v('image_border_size',1)}px solid {$v('image_border_color','#dedede')};"
              . "overflow:hidden;}";
        $css .= ".om-variation-type-image a img{border-radius:{$v('image_border_radius',0)}px;}";
        $css .= ".om-variation-type-image a:hover{"
              . "border:{$v('image_border_size_hover',2)}px solid {$v('image_border_color_hover','#212121')};"
              . "}";
        $css .= ".om-variation-type-image a.active,.om-variation-type-image a[aria-checked='true']{"
              . "border:{$v('image_border_size_selected',2)}px solid {$v('image_border_color_selected','#212121')};"
              . "}";
        $css .= ".om-variation-type-image{gap:{$v('image_gap',8)}px;}";

        
        $css .= $this->build_archive_css( $v );

        
        $css .= $this->build_recommendation_css( $v );

        
        
        
        
        $css .= 'body.om-multi-select-enabled .variations_form table.variations,'
              . 'body.om-multi-select-enabled .variations_form table.variations tbody,'
              . 'body.om-multi-select-enabled .variations_form table.variations tr'
              . '{display:block;width:100%;}'
              . 'body.om-multi-select-enabled .variations_form table.variations th.label'
              . '{display:none;}'
              . 'body.om-multi-select-enabled .variations_form table.variations td.value'
              . '{display:block;padding:0;width:100%;}';

        
        
        
        $mobile_css = '';
        $mw_pdp  = (int) $v( 'image_width_mobile', 32 );
        $mh_pdp  = (int) $v( 'image_height_mobile', 32 );
        $mw_arc  = (int) $v( 'image_width_shop_archive_mobile', 22 );
        $mh_arc  = (int) $v( 'image_height_shop_archive_mobile', 22 );
        if ( $mw_pdp > 0 || $mh_pdp > 0 ) {
            $mobile_css .= '.om-variation-type-image a{';
            if ( $mw_pdp > 0 ) { $mobile_css .= "width:{$mw_pdp}px;"; }
            if ( $mh_pdp > 0 ) { $mobile_css .= "height:{$mh_pdp}px;"; }
            $mobile_css .= '}';
        }
        if ( $mw_arc > 0 || $mh_arc > 0 ) {
            $arc_selectors = '.om-product-swatches--archive .om-variation-type-image a,'
                           . '.related.products .om-variation-type-image a,'
                           . '.cross-sells .om-variation-type-image a,'
                           . '.up-sells .om-variation-type-image a,'
                           . 'section.related .om-variation-type-image a';
            $mobile_css .= $arc_selectors . '{';
            if ( $mw_arc > 0 ) { $mobile_css .= "width:{$mw_arc}px;"; }
            if ( $mh_arc > 0 ) { $mobile_css .= "height:{$mh_arc}px;"; }
            $mobile_css .= '}';
        }
        
        $bpt = (int) $v( 'button_padding_top_mobile', 4 );
        $bpr = (int) $v( 'button_padding_right_mobile', 8 );
        $bpb = (int) $v( 'button_padding_bottom_mobile', 4 );
        $bpl = (int) $v( 'button_padding_left_mobile', 8 );
        $bfs = (int) $v( 'button_font_size_mobile', 11 );
        if ( $bpt > 0 || $bpr > 0 || $bpb > 0 || $bpl > 0 || $bfs > 0 ) {
            $mobile_css .= '.om-variation-type-button a{';
            if ( $bpt > 0 || $bpr > 0 || $bpb > 0 || $bpl > 0 ) {
                $mobile_css .= "padding:{$bpt}px {$bpr}px {$bpb}px {$bpl}px;";
            }
            if ( $bfs > 0 ) { $mobile_css .= "font-size:{$bfs}px;"; }
            $mobile_css .= '}';
        }
        if ( $mobile_css !== '' ) {
            $css .= '@media (max-width:600px){' . $mobile_css . '}';
        }

        
        
        
        
        
        
        
        
        if ( ! empty( $v( 'button_stack_vertical_archive', 0 ) ) ) {
            $row_sel = '.wc-block-product-template .om-product-swatches--archive .variations tr:has(.om-variation-type-button),'
                     . '.wp-block-woocommerce-product-template .om-product-swatches--archive .variations tr:has(.om-variation-type-button)';
            $list_sel = '.wc-block-product-template .om-product-swatches--archive .om-variation-type-button,'
                      . '.wp-block-woocommerce-product-template .om-product-swatches--archive .om-variation-type-button';
            $css .= $row_sel . '{'
                  . 'position:absolute !important;'
                  . 'top:25% !important;'
                  . 'transform:translateY(-50%) !important;'
                  . 'left:var(--wp--preset--spacing--3,12px) !important;'
                  . 'right:auto !important;'
                  . 'z-index:5 !important;'
                  . 'width:auto !important;'
                  . 'margin:0 !important;'
                  . '}';
            $css .= $list_sel . '{'
                  . 'display:flex !important;'
                  . 'flex-direction:column !important;'
                  . 'align-items:center !important;'
                  . 'justify-content:flex-start !important;'
                  . '}';

            
            
            
            
            if ( ! empty( $v( 'button_circle_archive', 0 ) ) ) {
                $diam = max( 20, (int) $v( 'button_circle_diameter_archive', 36 ) );
                $fs   = max( 6,  (int) $v( 'button_font_size_shop_archive', 11 ) );
                $a_sel = '.wc-block-product-template .om-product-swatches--archive .om-variation-type-button a,'
                       . '.wp-block-woocommerce-product-template .om-product-swatches--archive .om-variation-type-button a';
                $css .= $a_sel . '{'
                      . 'width:' . $diam . 'px !important;'
                      . 'height:' . $diam . 'px !important;'
                      . 'border-radius:50% !important;'
                      . 'padding:0 !important;'
                      . 'font-size:' . $fs . 'px !important;'
                      . 'display:flex !important;'
                      . 'align-items:center !important;'
                      . 'justify-content:center !important;'
                      . 'line-height:1 !important;'
                      . 'overflow:hidden !important;'
                      . 'white-space:nowrap !important;'
                      . 'text-align:center !important;'
                      . '}';
            }
        }

        if ( $css !== '' ) {
            echo '<style id="aieo-swatches-css">' . $css . '</style>'; 
        }
    }

    
    private function build_archive_css( $v ) {
        $css  = '';

        
        $css .= ".om-product-swatches--archive .om-variation-type-color a{"
              . "width:{$v('color_width_shop_archive',18)}px;height:{$v('color_height_shop_archive',18)}px;"
              . "border-radius:{$v('color_border_radius_shop_archive',50)}px;"
              . "border:{$v('color_border_size_shop_archive',1)}px solid {$v('color_border_color_shop_archive','#dedede')};"
              . "}";
        $css .= ".om-product-swatches--archive .om-variation-type-color a:hover{"
              . "border:{$v('color_border_size_hover_shop_archive',2)}px solid {$v('color_border_color_hover_shop_archive','#212121')};"
              . "}";
        $css .= ".om-product-swatches--archive .om-variation-type-color a.active{"
              . "border:{$v('color_border_size_selected_shop_archive',2)}px solid {$v('color_border_color_selected_shop_archive','#212121')};"
              . "}";
        $css .= ".om-product-swatches--archive .om-variation-type-color{gap:{$v('color_gap_shop_archive',6)}px;}";

        
        $css .= ".om-product-swatches--archive .om-variation-type-button a{"
              . "color:{$v('button_text_color_shop_archive','#212121')};"
              . "background-color:{$v('button_bg_color_shop_archive','#ffffff')};"
              . "border:{$v('button_border_size_shop_archive',1)}px solid {$v('button_border_color_shop_archive','#212121')};"
              . "border-radius:{$v('button_border_radius_shop_archive',5)}px;"
              . "padding:{$v('button_padding_top_shop_archive',6)}px {$v('button_padding_right_shop_archive',10)}px {$v('button_padding_bottom_shop_archive',6)}px {$v('button_padding_left_shop_archive',10)}px;"
              . "}";
        $css .= ".om-product-swatches--archive .om-variation-type-button a:hover{"
              . "color:{$v('button_text_color_hover_shop_archive','#ffffff')};"
              . "background-color:{$v('button_bg_color_hover_shop_archive','#212121')};"
              . "border-color:{$v('button_border_color_hover_shop_archive','#212121')};"
              . "}";
        $css .= ".om-product-swatches--archive .om-variation-type-button a.active{"
              . "color:{$v('button_text_color_selected_shop_archive','#ffffff')};"
              . "background-color:{$v('button_bg_color_selected_shop_archive','#212121')};"
              . "border-color:{$v('button_border_color_selected_shop_archive','#212121')};"
              . "}";
        $css .= ".om-product-swatches--archive .om-variation-type-button{gap:{$v('button_gap_shop_archive',6)}px;}";

        
        $css .= ".om-product-swatches--archive .om-variation-type-image a{"
              . "width:{$v('image_width_shop_archive',26)}px;height:{$v('image_height_shop_archive',26)}px;"
              . "border-radius:{$v('image_border_radius_shop_archive',0)}px;"
              . "border:{$v('image_border_size_shop_archive',1)}px solid {$v('image_border_color_shop_archive','#dedede')};"
              . "overflow:hidden;}";
        $css .= ".om-product-swatches--archive .om-variation-type-image a img{border-radius:{$v('image_border_radius_shop_archive',0)}px;}";
        $css .= ".om-product-swatches--archive .om-variation-type-image a:hover{"
              . "border:{$v('image_border_size_hover_shop_archive',2)}px solid {$v('image_border_color_hover_shop_archive','#212121')};"
              . "}";
        $css .= ".om-product-swatches--archive .om-variation-type-image a.active{"
              . "border:{$v('image_border_size_selected_shop_archive',2)}px solid {$v('image_border_color_selected_shop_archive','#212121')};"
              . "}";
        $css .= ".om-product-swatches--archive .om-variation-type-image{gap:{$v('image_gap_shop_archive',6)}px;}";

        return $css;
    }

    
    private function build_recommendation_css( $v ) {
        $roots = array(
            '.related.products .om-product-swatches',
            '.cross-sells .om-product-swatches',
            '.up-sells .om-product-swatches',
            'section.related .om-product-swatches',
            '.woocommerce-related-products .om-product-swatches',
            '.products.related .om-product-swatches',
            '.om-ppe-recently-viewed-section .om-product-swatches',
            '.merchant-recently-viewed-products .om-product-swatches',
            '.om-ppe-wishlist-page .om-product-swatches',
            '.om-ppe-wishlist-cart-section .om-product-swatches',
        );

        $join = function ( $suffix ) use ( $roots ) {
            return implode( ',', array_map( function ( $r ) use ( $suffix ) { return $r . $suffix; }, $roots ) );
        };

        $css  = '';

        
        $css .= $join( ' .om-variation-type-color a' ) . "{"
              . "width:{$v('color_width_shop_archive',18)}px;height:{$v('color_height_shop_archive',18)}px;"
              . "border-radius:{$v('color_border_radius_shop_archive',50)}px;"
              . "border:{$v('color_border_size_shop_archive',1)}px solid {$v('color_border_color_shop_archive','#dedede')};}";
        $css .= $join( ' .om-variation-type-color a:hover' ) . "{"
              . "border:{$v('color_border_size_hover_shop_archive',2)}px solid {$v('color_border_color_hover_shop_archive','#212121')};}";
        $css .= $join( ' .om-variation-type-color a.active' ) . "{"
              . "border:{$v('color_border_size_selected_shop_archive',2)}px solid {$v('color_border_color_selected_shop_archive','#212121')};}";
        $css .= $join( ' .om-variation-type-color' ) . "{gap:{$v('color_gap_shop_archive',6)}px;}";

        
        $css .= $join( ' .om-variation-type-button a' ) . "{"
              . "color:{$v('button_text_color_shop_archive','#212121')};"
              . "background-color:{$v('button_bg_color_shop_archive','#ffffff')};"
              . "border:{$v('button_border_size_shop_archive',1)}px solid {$v('button_border_color_shop_archive','#212121')};"
              . "border-radius:{$v('button_border_radius_shop_archive',5)}px;"
              . "padding:{$v('button_padding_top_shop_archive',6)}px {$v('button_padding_right_shop_archive',10)}px {$v('button_padding_bottom_shop_archive',6)}px {$v('button_padding_left_shop_archive',10)}px;}";
        $css .= $join( ' .om-variation-type-button a:hover' ) . "{"
              . "color:{$v('button_text_color_hover_shop_archive','#ffffff')};"
              . "background-color:{$v('button_bg_color_hover_shop_archive','#212121')};"
              . "border-color:{$v('button_border_color_hover_shop_archive','#212121')};}";
        $css .= $join( ' .om-variation-type-button a.active' ) . "{"
              . "color:{$v('button_text_color_selected_shop_archive','#ffffff')};"
              . "background-color:{$v('button_bg_color_selected_shop_archive','#212121')};"
              . "border-color:{$v('button_border_color_selected_shop_archive','#212121')};}";
        $css .= $join( ' .om-variation-type-button' ) . "{gap:{$v('button_gap_shop_archive',6)}px;}";

        
        $css .= $join( ' .om-variation-type-image a' ) . "{"
              . "width:{$v('image_width_shop_archive',26)}px;height:{$v('image_height_shop_archive',26)}px;"
              . "border-radius:{$v('image_border_radius_shop_archive',0)}px;"
              . "border:{$v('image_border_size_shop_archive',1)}px solid {$v('image_border_color_shop_archive','#dedede')};"
              . "overflow:hidden;}";
        $css .= $join( ' .om-variation-type-image a img' ) . "{border-radius:{$v('image_border_radius_shop_archive',0)}px;}";
        $css .= $join( ' .om-variation-type-image a:hover' ) . "{"
              . "border:{$v('image_border_size_hover_shop_archive',2)}px solid {$v('image_border_color_hover_shop_archive','#212121')};}";
        $css .= $join( ' .om-variation-type-image a.active' ) . "{"
              . "border:{$v('image_border_size_selected_shop_archive',2)}px solid {$v('image_border_color_selected_shop_archive','#212121')};}";
        $css .= $join( ' .om-variation-type-image' ) . "{gap:{$v('image_gap_shop_archive',6)}px;}";

        return $css;
    }

    
    public function output_early_multi_select_css() {
        if ( ! is_product() ) { return; }

        
        
        if ( class_exists( 'OM_Product_Swatches_Handler' ) ) { return; }

        $pid = (int) get_the_ID();
        if ( $pid <= 0 ) { return; }
        $ps = $this->resolve_product_settings( $pid );
        if ( empty( $ps['multi_select_enabled'] ) ) { return; }

        echo '<style id="aieo-multi-select-early-css">'
           . '.om-multi-select-enabled .variations_form .single_add_to_cart_button,'
           . '.om-multi-select-enabled .variations_form .woocommerce-variation-add-to-cart,'
           . '.om-multi-select-enabled .variations_form .woocommerce-variation-add-to-cart .quantity'
           . '{display:none !important;}'
           . '</style>';
    }

    
    public function filter_ajax_variation_threshold( $threshold, $product ) {
        return (int) apply_filters( 'aieo_swatches_ajax_variation_threshold', 200, $product );
    }

    
    public function filter_body_class( $classes ) {
        if ( ! is_product() ) { return $classes; }
        $pid = (int) get_the_ID();
        if ( $pid <= 0 ) { return $classes; }
        $ps = $this->resolve_product_settings( $pid );
        if ( ! empty( $ps['multi_select_enabled'] ) ) {
            $classes[] = 'om-multi-select-enabled';
        }
        return $classes;
    }

    
    public function maybe_remove_default_add_to_cart() {
        if ( is_admin() || ! is_product() ) { return; }

        $pid = (int) get_the_ID();
        if ( $pid <= 0 ) { return; }

        $ps = $this->resolve_product_settings( $pid );
        if ( empty( $ps['multi_select_enabled'] ) ) { return; }

        
        
        remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation_add_to_cart_button', 20 );
    }

    
    private function get_all_cart_variations_data() {
        $out = array();
        if ( ! function_exists( 'WC' ) || ! WC()->cart ) { return $out; }

        foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
            $product_id   = isset( $cart_item['product_id'] )   ? (int) $cart_item['product_id']   : 0;
            $variation_id = isset( $cart_item['variation_id'] ) ? (int) $cart_item['variation_id'] : 0;
            $quantity     = isset( $cart_item['quantity'] )     ? (int) $cart_item['quantity']     : 1;
            if ( $product_id <= 0 || $variation_id <= 0 ) { continue; }

            if ( ! isset( $out[ $product_id ] ) ) { $out[ $product_id ] = array(); }

            if ( isset( $out[ $product_id ][ $variation_id ] ) ) {
                
                $out[ $product_id ][ $variation_id ]['qty'] += $quantity;
                continue;
            }

            
            
            
            $variation_name = '';
            if ( isset( $cart_item['variation'] ) && is_array( $cart_item['variation'] ) ) {
                $parts = array();
                foreach ( $cart_item['variation'] as $attr_key => $attr_value ) {
                    if ( $attr_value === '' ) { continue; }
                    $taxonomy = str_replace( 'attribute_', '', (string) $attr_key );
                    $term     = get_term_by( 'slug', (string) $attr_value, $taxonomy );
                    $parts[]  = ( $term && ! is_wp_error( $term ) )
                        ? (string) $term->name
                        : ucfirst( str_replace( '-', ' ', (string) $attr_value ) );
                }
                $variation_name = implode( ', ', $parts );
            }

            $out[ $product_id ][ $variation_id ] = array(
                'qty'           => $quantity,
                'name'          => $variation_name,
                'cart_item_key' => (string) $cart_item_key,
            );
        }

        return $out;
    }

    
    public function output_cart_variations_data_tag() {
        if ( ! is_product() && ! is_shop() && ! is_product_taxonomy() ) { return; }

        $s = $this->get_settings();
        if ( isset( $s['cart_sync_enabled'] ) && empty( $s['cart_sync_enabled'] ) ) { return; }

        $data = $this->get_all_cart_variations_data();
        echo '<script type="application/json" class="om-cart-variations-data">'
           . wp_json_encode( $data )
           . '</script>';
    }

    
    public function add_cart_variations_fragment( $fragments ) {
        $s = $this->get_settings();
        if ( isset( $s['cart_sync_enabled'] ) && empty( $s['cart_sync_enabled'] ) ) {
            return $fragments;
        }
        if ( ! is_array( $fragments ) ) { $fragments = array(); }
        $data = $this->get_all_cart_variations_data();
        $fragments['.om-cart-variations-data'] =
            '<script type="application/json" class="om-cart-variations-data">'
            . wp_json_encode( $data )
            . '</script>';
        return $fragments;
    }

    
    public function output_see_all_variations_modal() {
        
        
        if ( class_exists( 'OM_Product_Swatches_Handler' ) ) { return; }

        $s = $this->get_settings();
        if ( empty( $s['on_shop_catalog'] ) ) { return; }

        if ( ! is_shop() && ! is_product_taxonomy() && ! is_search() ) { return; }

        $title = isset( $s['see_all_modal_title'] )
            ? (string) $s['see_all_modal_title']
            : __( 'All Variations', 'ai-eshop-optimizer' );
        $view  = isset( $s['see_all_view_product_text'] )
            ? (string) $s['see_all_view_product_text']
            : __( 'View product page', 'ai-eshop-optimizer' );
        ?>
<div id="om-see-all-variations-modal" class="om-modal" style="display:none;">
    <div class="om-modal-overlay"></div>
    <div class="om-modal-container">
        <div class="om-modal-header">
            <h3 class="om-modal-title"><?php echo esc_html( $title ); ?></h3>
            <button type="button" class="om-modal-close" aria-label="<?php esc_attr_e( 'Close', 'ai-eshop-optimizer' ); ?>">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
        <div class="om-modal-body">
            <div class="om-modal-product-info">
                <div class="om-modal-image-wrapper">
                    <img class="om-modal-product-image" src="" alt="" />
                </div>
                <div class="om-modal-product-details">
                    <h4 class="om-modal-product-title"></h4>
                    <a href="#" class="om-modal-view-product"><?php echo esc_html( $view ); ?></a>
                </div>
            </div>
            <div class="om-modal-variations-grid">
                <div class="om-modal-loading">
                    <span class="om-spinner"></span>
                    <?php esc_html_e( 'Loading variations...', 'ai-eshop-optimizer' ); ?>
                </div>
            </div>
        </div>
    </div>
</div>
        <?php
    }

    public function setup_archive_swatches() {
        if ( is_admin() ) { return; }
        
        
        
        
        
        
        $is_archive = is_shop()
            || ( function_exists( 'is_product_taxonomy' ) && is_product_taxonomy() )
            || is_search();
        if ( ! $is_archive ) { return; }

        $s = $this->get_settings();
        if ( empty( $s['on_shop_catalog'] ) ) { return; }

        
        
        remove_action( 'woocommerce_after_shop_loop_item', 'woocommerce_template_loop_add_to_cart', 10 );
        add_action( 'woocommerce_after_shop_loop_item', array( $this, 'render_archive_swatches' ), 10 );

        
        
        
        
        
        
        
        add_action( 'woocommerce_after_shop_loop_item', static function () {
            remove_action( 'woocommerce_after_shop_loop_item', 'woocommerce_template_loop_add_to_cart', 10 );
        }, 9 );
    }

    
    public function render_archive_swatches() {
        global $product;
        if ( ! $product instanceof WC_Product ) { return; }

        $product_id = (int) $product->get_id();

        
        
        
        
        
        
        
        
        
        if ( isset( self::$rendered_for_product[ $product_id ] ) ) {
            return;
        }

        if ( ! $product->is_type( 'variable' ) ) {
            
            
            
            
            self::$rendered_for_product[ $product_id ] = 'classic_loop';
            woocommerce_template_loop_add_to_cart();
            return;
        }

        self::$rendered_for_product[ $product_id ] = 'classic_loop';

        $cache      = $this->read_cache_row( $product_id, 'archive' );

        if ( ! $cache ) {
            
            
            $cache = $this->build_inline_fallback( $product, 'archive' );
            if ( $cache ) {
                $this->pending_rebuilds[ $product_id ] = true;
            }
        }

        if ( ! $cache || empty( $cache['variations'] ) ) {
            woocommerce_template_loop_add_to_cart();
            return;
        }

        
        
        
        
        $this->render_archive_product_wrapper( $product, $cache );
    }

    
    public function render_card_swatches_html( WC_Product $product, $context = 'archive' ) {
        if ( ! $this->is_runtime_enabled() ) { return ''; }

        $context_key = ( $context === 'product' ) ? 'product' : 'archive';
        $product_id  = (int) $product->get_id();

        
        
        
        
        
        
        if ( isset( self::$rendered_for_product[ $product_id ] ) ) {
            return '';
        }

        
        
        
        
        
        
        
        if ( ! $product->is_type( 'variable' ) ) {
            self::$rendered_for_product[ $product_id ] = 'block';

            $previous = isset( $GLOBALS['product'] ) ? $GLOBALS['product'] : null;
            $GLOBALS['product'] = $product;

            ob_start();
            echo '<div class="om-product-swatches om-product-swatches--archive om-product-swatches--simple">';
            $this->render_archive_cta_link( $product );
            echo '</div>';
            $html = (string) ob_get_clean();

            if ( $previous === null ) {
                unset( $GLOBALS['product'] );
            } else {
                $GLOBALS['product'] = $previous;
            }

            return $html;
        }

        self::$rendered_for_product[ $product_id ] = 'block';

        $cache = $this->read_cache_row( $product_id, $context_key );
        if ( ! $cache ) {
            $cache = $this->build_inline_fallback( $product, $context_key );
            if ( $cache ) {
                $this->pending_rebuilds[ $product_id ] = true;
            }
        }
        if ( ! $cache || empty( $cache['variations'] ) ) {
            return '';
        }

        
        
        
        $previous = isset( $GLOBALS['product'] ) ? $GLOBALS['product'] : null;
        $GLOBALS['product'] = $product;

        ob_start();
        $this->render_archive_product_wrapper( $product, $cache );
        $html = (string) ob_get_clean();

        if ( $previous === null ) {
            unset( $GLOBALS['product'] );
        } else {
            $GLOBALS['product'] = $previous;
        }

        return $html;
    }

    
    
    private function is_one_to_one_variation_mapping( WC_Product $product ) {
        $pid = (int) $product->get_id();
        if ( isset( $this->one_to_one_memo[ $pid ] ) ) {
            return $this->one_to_one_memo[ $pid ];
        }
        $variation_ids = $product->get_children();
        $vcount = count( $variation_ids );
        if ( $vcount < 2 ) { return $this->one_to_one_memo[ $pid ] = false; }

        $attrs = $product->get_attributes();
        $var_attr_keys = array();
        foreach ( $attrs as $key => $attr ) {
            if ( is_object( $attr ) && method_exists( $attr, 'get_variation' ) && $attr->get_variation() ) {
                $var_attr_keys[] = (string) $key;
            }
        }
        if ( count( $var_attr_keys ) < 2 ) { return $this->one_to_one_memo[ $pid ] = false; }

        
        $distinct = array_fill_keys( $var_attr_keys, array() );
        foreach ( $variation_ids as $vid ) {
            $v = wc_get_product( $vid );
            if ( ! $v ) { continue; }
            $va = $v->get_attributes();
            foreach ( $var_attr_keys as $k ) {
                $val = isset( $va[ $k ] ) ? (string) $va[ $k ] : '';
                if ( $val !== '' ) { $distinct[ $k ][ $val ] = true; }
            }
        }
        
        
        foreach ( $distinct as $values ) {
            if ( count( $values ) !== $vcount ) {
                return $this->one_to_one_memo[ $pid ] = false;
            }
        }
        return $this->one_to_one_memo[ $pid ] = true;
    }

    private function render_archive_product_wrapper( WC_Product $product, array $cache ) {
        
        
        
        
        unset( $cache );

        $product_id = (int) $product->get_id();

        $button_select_text = (string) $product->add_to_cart_text();
        $button_add_text    = (string) $product->single_add_to_cart_text();
        if ( $button_select_text === $button_add_text ) {
            $button_add_text = __( 'Add to cart', 'woocommerce' );
        }

        
        
        remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation_add_to_cart_button', 20 );
        add_filter( 'woocommerce_reset_variations_link', '__return_empty_string' );

        
        
        
        
        
        
        
        
        $collapse_attr = $this->is_one_to_one_variation_mapping( $product ) ? ' data-aieo-collapse-secondary="1"' : '';

        printf(
            '<div class="om-product-swatches om-product-swatches--archive" data-button-select-text="%1$s" data-button-add-text="%2$s"%3$s>',
            esc_attr( $button_select_text ),
            esc_attr( $button_add_text ),
            $collapse_attr 
        );

        
        
        
        
        
        
        
        ob_start();
        woocommerce_template_single_add_to_cart();
        echo trim( ob_get_clean() ); 

        remove_filter( 'woocommerce_reset_variations_link', '__return_empty_string' );

        $this->render_archive_cta_link( $product );

        echo '</div>';
    }

    
    private function render_archive_cta_link( WC_Product $product ) {
        $product_id        = (int) $product->get_id();
        $add_to_cart_text  = (string) $product->add_to_cart_text();
        $add_to_cart_url   = (string) $product->add_to_cart_url();

        
        
        
        
        
        
        
        
        
        
        
        $classes = array( 'button', 'om-add-to-cart-button', 'add_to_cart_button', 'aieo-archive-add-to-cart' );

        
        
        
        
        
        
        $is_in_cart       = $this->is_product_in_cart( $product_id );
        $is_variable      = $product->is_type( 'variable' );
        $has_defaults     = ! empty( $product->get_default_attributes() );
        $is_see_options   = $is_variable && ! $has_defaults && ! $is_in_cart;
        $extra_attrs      = '';
        if ( $is_in_cart ) {
            $classes[] = 'aieo-archive-add-to-cart--in-cart';
            
            
            $extra_attrs = ' aria-disabled="true" tabindex="-1"';
        } elseif ( $is_see_options ) {
            $classes[] = 'aieo-archive-add-to-cart--see-options';
        }

        if (
            $product->is_purchasable()
            && $product->is_in_stock()
            && $product->supports( 'ajax_add_to_cart' )
        ) {
            $classes[] = 'ajax_add_to_cart';
        }
        
        
        if ( $has_defaults ) {
            $add_to_cart_text = (string) $product->single_add_to_cart_text();
        }
        $add_to_cart_class = implode( ' ', $classes );

        printf(
            '<a href="%1$s" class="%2$s"%6$s data-product_id="%3$d" data-product_sku="%4$s">%5$s</a>',
            esc_url( $add_to_cart_url ),
            esc_attr( $add_to_cart_class ),
            $product_id,
            esc_attr( (string) $product->get_sku() ),
            esc_html( $add_to_cart_text ),
            $extra_attrs 
        );
    }

    
    private function is_product_in_cart( $product_id ) {
        if ( ! function_exists( 'WC' ) ) { return false; }
        $cart = WC()->cart;
        if ( ! $cart ) { return false; }
        $pid = (int) $product_id;
        if ( $pid <= 0 ) { return false; }
        foreach ( $cart->get_cart() as $item ) {
            $ipid = (int) ( $item['product_id']   ?? 0 );
            $ivid = (int) ( $item['variation_id'] ?? 0 );
            if ( ( $ipid === $pid || $ivid === $pid ) && (int) ( $item['quantity'] ?? 0 ) > 0 ) {
                return true;
            }
        }
        return false;
    }

    
    public function filter_variation_attribute_options_html( $html, $args ) {
        $args = wp_parse_args( $args, array(
            'options'          => false,
            'attribute'        => false,
            'product'          => false,
            'selected'         => false,
            'name'             => '',
            'id'               => '',
            'class'            => '',
            'show_option_none' => '',
        ) );

        $product   = $args['product'];
        $attribute = (string) $args['attribute'];
        if ( ! $product instanceof WC_Product || $attribute === '' ) {
            return $html;
        }

        
        if ( false === $args['selected'] ) {
            $selected_key = 'attribute_' . sanitize_title( $attribute );
            
            $args['selected'] = isset( $_REQUEST[ $selected_key ] )
                ? wc_clean( wp_unslash( $_REQUEST[ $selected_key ] ) )
                : $product->get_variation_default_attribute( $attribute );
        }

        $tax = $this->get_attribute_taxonomy( $attribute );
        if ( ! $tax ) {
            return $html;
        }
        $attribute_type = (string) $tax->attribute_type;

        
        if ( $attribute_type === 'select' ) {
            $classes = array( 'om-variation-item' );
            if ( ! empty( $args['class'] ) ) {
                $classes = array_merge( $classes, array_filter( explode( ' ', (string) $args['class'] ) ) );
            }
            if ( ! empty( $args['selected'] ) ) {
                $classes[] = 'active';
            }
            $replaced = str_replace( '<select ', '<select class="' . esc_attr( implode( ' ', $classes ) ) . '" ', $html );
            return '<div class="om-variation-type-select">' . $replaced . '</div>';
        }

        $options = is_array( $args['options'] ) ? $args['options'] : array();
        if ( empty( $options ) ) {
            $attrs = $product->get_variation_attributes();
            $options = isset( $attrs[ $attribute ] ) ? (array) $attrs[ $attribute ] : array();
        }

        
        
        
        
        
        
        $product_id       = (int) $product->get_id();
        $is_main_product  = is_singular( 'product' ) && get_queried_object_id() === $product_id;
        $is_modal_context = (bool) apply_filters( 'aieo_product_swatches_modal_context', apply_filters( 'om_product_swatches_modal_context', false ) );
        $is_archive       = ! $is_main_product && ! $is_modal_context;
        $context_key      = $is_archive ? 'archive' : 'single';
        $render_context   = $is_archive ? 'archive' : 'product';

        $cache = $this->read_cache_row( $product_id, $context_key );
        if ( ! $cache ) {
            $cache = $this->build_inline_fallback( $product, $context_key );
            if ( $cache ) {
                $this->pending_rebuilds[ $product_id ] = true;
            }
        }
        if ( ! $cache ) {
            
            return $html;
        }

        return $this->render_attribute_wrapper_html(
            $product,
            $cache,
            $attribute,
            $options,
            (string) $args['selected'],
            $render_context,
            $html,
            $attribute_type
        );
    }

    
    private function render_attribute_wrapper( WC_Product $product, array $cache, $attribute, array $options, $selected, $context ) {
        $tax = $this->get_attribute_taxonomy( $attribute );
        if ( ! $tax ) { return; }
        $attribute_type = (string) $tax->attribute_type;
        if ( $attribute_type === 'select' ) {
            return; 
        }
        echo $this->render_attribute_wrapper_html( 
            $product, $cache, $attribute, $options, $selected, $context, '', $attribute_type
        );
    }

    
    private function render_attribute_wrapper_html( WC_Product $product, array $cache, $attribute, array $options, $selected, $context, $original_select_html, $attribute_type ) {
        $settings   = $this->get_settings();
        $variations = is_array( $cache['variations'] ) ? $cache['variations'] : array();

        $hide_oos        = get_option( 'woocommerce_hide_out_of_stock_items', 'no' ) === 'yes';
        $is_archive      = $context === 'archive';
        $tooltip         = ! empty( $settings['tooltip'] );
        $mouseover       = ! empty( $settings['mouseover'] );

        $wrapper_classes = array( 'om-variations-wrapper' );
        if ( $mouseover ) { $wrapper_classes[] = 'om-variations-mouseover'; }

        $extra_attrs = '';
        if ( $is_archive ) {
            $extra_attrs .= ' data-context="archive"';
            $total_available = (int) ( $cache['total_available'] ?? 0 );
            $stored_count    = (int) ( $cache['stored_count'] ?? count( $variations ) );
            if ( $total_available > $stored_count ) {
                $extra_attrs .= ' data-server-swatch-limit="1" data-total-variations="' . esc_attr( (string) $total_available ) . '"';
            }
        }

        
        
        
        $ps = $this->resolve_product_settings( (int) $product->get_id() );
        if ( ! $is_archive && ! empty( $ps['multi_select_enabled'] ) ) {
            $wrapper_classes[] = 'om-multi-select-mode';
            if ( $ps['display_style'] === 'dropdown' ) {
                $wrapper_classes[] = 'om-multi-select-dropdown-mode';
            }
            $extra_attrs .= ' data-multi-select="1"';
            $extra_attrs .= ' data-display-style="' . esc_attr( (string) $ps['display_style'] ) . '"';
            if ( $ps['display_style'] === 'dropdown' ) {
                $extra_attrs .= ' data-show-quantity="' . ( ! empty( $ps['show_quantity'] ) ? '1' : '0' ) . '"';
            }
        }

        $cart_sync = ! empty( $ps['cart_sync_enabled'] );
        $extra_attrs .= ' data-cart-sync="' . ( $cart_sync ? '1' : '0' ) . '"';

        $output  = '<div class="' . esc_attr( implode( ' ', $wrapper_classes ) ) . '" data-type="' . esc_attr( $attribute_type ) . '" data-attribute="' . esc_attr( $attribute ) . '"' . $extra_attrs . '>';

        
        
        
        
        
        
        
        if ( $original_select_html !== '' ) {
            $output .= str_replace(
                '<select',
                '<select style="display:none !important;position:absolute !important;visibility:hidden !important;"',
                $original_select_html
            );
        }

        $output .= '<div class="om-variation-type-' . esc_attr( $attribute_type ) . '">';

        
        
        $terms = wc_get_product_terms( (int) $product->get_id(), $attribute, array( 'fields' => 'all' ) );
        if ( is_wp_error( $terms ) || ! is_array( $terms ) ) { $terms = array(); }

        
        
        
        
        
        
        
        if ( ! empty( $terms ) && ( $attribute_type === 'color' || $attribute_type === 'image' ) ) {
            $term_ids = array();
            foreach ( $terms as $t ) {
                if ( isset( $t->term_id ) ) { $term_ids[] = (int) $t->term_id; }
            }
            if ( ! empty( $term_ids ) && function_exists( 'update_termmeta_cache' ) ) {
                update_termmeta_cache( $term_ids );
            }
        }

        $img_w = $is_archive
            ? (int) ( $settings['image_width_shop_archive']  ?? 26 )
            : (int) ( $settings['image_width']  ?? 40 );
        $img_h = $is_archive
            ? (int) ( $settings['image_height_shop_archive'] ?? 26 )
            : (int) ( $settings['image_height'] ?? 40 );
        $loading = isset( $settings['image_loading_strategy'] ) ? (string) $settings['image_loading_strategy'] : 'lazy';
        if ( ! in_array( $loading, array( 'lazy', 'eager', 'auto' ), true ) ) {
            $loading = 'lazy';
        }

        foreach ( $terms as $term ) {
            if ( ! in_array( $term->slug, $options, true ) ) { continue; }

            $variation_ids  = $this->get_variation_ids_by_term( $variations, $attribute, $term->slug );
            if ( empty( $variation_ids ) ) { continue; }

            $is_oos = $this->is_term_out_of_stock( $variations, $variation_ids );
            if ( $is_archive && $is_oos ) { continue; }

            $base_label    = $term->name;
            $display_label = $term->name;
            $visible_label = $tooltip ? $base_label : $display_label;

            
            
            
            $vdesc         = $this->get_variation_description_by_ids( $variations, $variation_ids );
            $tooltip_label = ( $vdesc !== '' ) ? ( $vdesc . ' - ' . $term->name ) : $display_label;

            $item_class = ( $hide_oos === true && $is_oos ) ? 'om-variation-item permanent-disabled' : 'om-variation-item';

            switch ( $attribute_type ) {
                case 'color':
                    $style = $this->resolve_term_color_style( (int) $term->term_id );
                    $output .= $this->render_color_swatch_term( $term, (string) $selected, $item_class, $style, $base_label, $display_label, $visible_label, $tooltip, $is_oos, $tooltip_label );
                    break;
                case 'button':
                    $output .= $this->render_button_swatch_term( $term, (string) $selected, $item_class, $base_label, $display_label, $visible_label, $tooltip, $is_oos, $tooltip_label );
                    break;
                case 'image':
                    $image_data = $this->resolve_term_image_src(
                        (int) $term->term_id,
                        $variations,
                        $attribute,
                        $term->slug,
                        $img_w,
                        $img_h,
                        $is_archive
                    );
                    $output .= $this->render_image_swatch_term( $term, (string) $selected, $item_class, $image_data, $base_label, $display_label, $loading, $tooltip, $is_oos, $tooltip_label );
                    break;
            }
        }

        $output .= '</div>';

        
        if ( $is_archive ) {
            $total_available = (int) ( $cache['total_available'] ?? 0 );
            $stored_count    = (int) ( $cache['stored_count'] ?? count( $variations ) );
            if ( $total_available > $stored_count && $total_available > 0 ) {
                $mode = isset( $settings['see_all_display_mode'] ) ? (string) $settings['see_all_display_mode'] : 'swatches_only';
                if ( $mode === 'text_only' || $mode === 'both' ) {
                    $template = isset( $settings['see_all_text'] )
                        ? (string) $settings['see_all_text']
                        : __( 'See all {count} variations', 'ai-eshop-optimizer' );
                    $label    = str_replace( '{count}', (string) $total_available, $template );
                    $output  .= sprintf(
                        '<a href="#" class="om-see-all-variations-link" data-product-id="%1$d" data-total-variations="%2$d">%3$s</a>',
                        (int) $product->get_id(),
                        $total_available,
                        esc_html( $label )
                    );
                }
            }
        }

        $output .= '</div>';
        return $output;
    }

    
    private function resolve_term_color_style( $term_id ) {
        $all = get_term_meta( (int) $term_id );
        if ( ! is_array( $all ) ) { $all = array(); }

        $sidecar_type = isset( $all['_aieo_swatch_color_type'][0] )
            ? (string) $all['_aieo_swatch_color_type'][0]
            : '';
        $legacy_type  = isset( $all['product_attribute_color_type'][0] )
            ? (string) $all['product_attribute_color_type'][0]
            : '';
        $type = $sidecar_type !== '' ? $sidecar_type : $legacy_type;

        if ( $type === 'gradient' ) {
            $payload = isset( $all['_aieo_swatch_gradient'][0] )
                ? maybe_unserialize( $all['_aieo_swatch_gradient'][0] )
                : array();
            $c1  = is_array( $payload ) && isset( $payload['color_1'] ) ? (string) $payload['color_1']
                : ( isset( $all['product_attribute_color_gradient_color_1'][0] ) ? (string) $all['product_attribute_color_gradient_color_1'][0] : '' );
            $c2  = is_array( $payload ) && isset( $payload['color_2'] ) ? (string) $payload['color_2']
                : ( isset( $all['product_attribute_color_gradient_color_2'][0] ) ? (string) $all['product_attribute_color_gradient_color_2'][0] : '' );
            $dir = is_array( $payload ) && isset( $payload['direction'] ) ? (string) $payload['direction']
                : ( isset( $all['product_attribute_color_gradient_direction'][0] ) ? (string) $all['product_attribute_color_gradient_direction'][0] : 'to bottom' );
            return sprintf( 'background: linear-gradient(%s,%s 0%%,%s 100%%);', $dir, $c1, $c2 );
        }

        if ( $type === 'multiple' ) {
            $payload = isset( $all['_aieo_swatch_multiple'][0] )
                ? maybe_unserialize( $all['_aieo_swatch_multiple'][0] )
                : array();
            $colors = is_array( $payload ) && isset( $payload['colors'] ) && is_array( $payload['colors'] )
                ? array_values( array_filter( array_map( 'strval', $payload['colors'] ) ) )
                : array_values( array_filter( array(
                    isset( $all['product_attribute_color_multiple_color_1'][0] ) ? (string) $all['product_attribute_color_multiple_color_1'][0] : '',
                    isset( $all['product_attribute_color_multiple_color_2'][0] ) ? (string) $all['product_attribute_color_multiple_color_2'][0] : '',
                    isset( $all['product_attribute_color_multiple_color_3'][0] ) ? (string) $all['product_attribute_color_multiple_color_3'][0] : '',
                    isset( $all['product_attribute_color_multiple_color_4'][0] ) ? (string) $all['product_attribute_color_multiple_color_4'][0] : '',
                ) ) );
            $dir = is_array( $payload ) && isset( $payload['direction'] ) ? (string) $payload['direction']
                : ( isset( $all['product_attribute_color_multiple_direction'][0] ) ? (string) $all['product_attribute_color_multiple_direction'][0] : 'to bottom' );

            switch ( count( $colors ) ) {
                case 1:
                    return sprintf( 'background-color: %s;', $colors[0] );
                case 2:
                    return sprintf( 'background: linear-gradient(%s, %s 50%%, %s 50%%);', $dir, $colors[0], $colors[1] );
                case 3:
                    return sprintf(
                        'background: linear-gradient(%s, %s 33.333%%, %s 33.333%% 66.666%%, %s 66.666%% 100%%);',
                        $dir, $colors[0], $colors[1], $colors[2]
                    );
                case 4:
                    return sprintf(
                        'background: linear-gradient(135deg, %1$s, %1$s), linear-gradient(135deg, %2$s, %2$s), linear-gradient(225deg, %3$s, %3$s), linear-gradient(225deg, %4$s, %4$s); background-position: 0%% 0%%, 0%% 100%%, 100%% 0%%, 100%% 100%%; background-size: 50%% 50%%; background-repeat: no-repeat;',
                        $colors[0], $colors[1], $colors[2], $colors[3]
                    );
                default:
                    return 'background-color: transparent;';
            }
        }

        
        
        $color = isset( $all['color'][0] ) ? (string) $all['color'][0] : '';
        if ( $color === '' && isset( $all['product_attribute_color'][0] ) ) {
            $color = (string) $all['product_attribute_color'][0];
        }
        return sprintf( 'background-color: %s;', $color );
    }

    
    private function resolve_term_image_src( $term_id, array $variations, $attribute, $slug, $img_w, $img_h, $is_archive ) {
        
        
        
        
        
        
        
        
        
        
        
        
        
        $sidecar_id = (int) get_term_meta( (int) $term_id, '_aieo_swatch_image_id', true );
        $legacy_id  = (int) get_term_meta( (int) $term_id, 'product_attribute_image', true );
        $image_id   = $sidecar_id > 0 ? $sidecar_id : $legacy_id;
        
        $image_id = (int) apply_filters( 'aieo_swatch_image_id_for_term', $image_id, (int) $term_id, (string) $attribute );
        if ( $image_id > 0 ) {
            $size  = $is_archive ? 'om_swatch_archive' : 'om_swatch_product';
            $inter = image_get_intermediate_size( $image_id, array( $img_w, $img_h ) );
            if ( is_array( $inter ) && ! empty( $inter['url'] ) ) {
                return array(
                    'url'    => (string) $inter['url'],
                    'width'  => (int) ( $inter['width']  ?? $img_w ),
                    'height' => (int) ( $inter['height'] ?? $img_h ),
                );
            }
            $full = wp_get_attachment_image_src( $image_id, $size );
            if ( is_array( $full ) && ! empty( $full[0] ) ) {
                return array(
                    'url'    => (string) $full[0],
                    'width'  => (int) ( $full[1] ?? $img_w ),
                    'height' => (int) ( $full[2] ?? $img_h ),
                );
            }
        }

        
        
        foreach ( $variations as $v ) {
            $attrs = isset( $v['attributes'] ) && is_array( $v['attributes'] ) ? $v['attributes'] : array();
            $key   = 'attribute_' . $this->encode_attribute_slug( (string) $attribute );
            if ( ! isset( $attrs[ $key ] ) ) { continue; }
            if ( (string) $attrs[ $key ] !== (string) $slug ) { continue; }
            if ( ! empty( $v['img_url'] ) ) {
                return array(
                    'url'    => (string) $v['img_url'],
                    'width'  => (int) ( $v['img_w'] ?? $img_w ),
                    'height' => (int) ( $v['img_h'] ?? $img_h ),
                );
            }
            break;
        }

        
        return array(
            'url'    => function_exists( 'wc_placeholder_img_src' ) ? (string) wc_placeholder_img_src( 'thumbnail' ) : '',
            'width'  => (int) $img_w,
            'height' => (int) $img_h,
        );
    }

    
    private function render_color_swatch_term( $term, $selected, $item_class, $style, $base_label, $display_label, $visible_label, $tooltip, $is_oos, $tooltip_label = '' ) {
        
        
        
        
        
        
        $hover        = ( (string) $tooltip_label !== '' ) ? (string) $tooltip_label : $display_label;
        $title_label  = $is_oos ? ( $display_label . ' - ' . __( 'Out of stock', 'ai-eshop-optimizer' ) ) : $hover;
        $title_attr   = ' title="' . esc_attr( $title_label ) . '"';
        
        
        
        $title_attr  .= ' data-swatch-label="' . esc_attr( $hover ) . '"';
        $tooltip_attr = ( $tooltip && ! $is_oos ) ? ' data-tooltip="' . esc_attr( $hover ) . '"' : '';

        $html  = '<a href="#" role="button" class="' . esc_attr( $item_class ) . '" data-original-text="' . esc_attr( $base_label ) . '" value="' . esc_attr( $term->slug ) . '"' . $title_attr . $tooltip_attr . ' ' . selected( sanitize_title( $selected ), $term->slug, false ) . '>';
        $html .= '<span class="om-variation-text" style="' . esc_attr( $style ) . '" data-original-text="' . esc_attr( $base_label ) . '">';
        if ( $tooltip && ! $is_oos ) {
            $html .= '<span class="om-variation-tooltip" data-original-tooltip="' . esc_attr( $hover ) . '"></span>';
        } elseif ( ! $tooltip ) {
            $html .= esc_html( $visible_label );
        }
        $html .= '</span></a>';

        return $html;
    }

    
    private function render_button_swatch_term( $term, $selected, $item_class, $base_label, $display_label, $visible_label, $tooltip, $is_oos, $tooltip_label = '' ) {
        
        $hover        = ( (string) $tooltip_label !== '' ) ? (string) $tooltip_label : $display_label;
        $title_label  = $is_oos ? ( $display_label . ' - ' . __( 'Out of stock', 'ai-eshop-optimizer' ) ) : $hover;
        $title_attr   = ' title="' . esc_attr( $title_label ) . '"';
        
        
        
        $title_attr  .= ' data-swatch-label="' . esc_attr( $hover ) . '"';
        $tooltip_attr = ( $tooltip && ! $is_oos ) ? ' data-tooltip="' . esc_attr( $hover ) . '"' : '';

        $html = '<a href="#" role="button" class="' . esc_attr( $item_class ) . '" data-original-text="' . esc_attr( $base_label ) . '" value="' . esc_attr( $term->slug ) . '"' . $title_attr . $tooltip_attr . ' ' . selected( sanitize_title( $selected ), $term->slug, false ) . '>';
        if ( $tooltip && ! $is_oos ) {
            $html .= '<span class="om-variation-tooltip" data-original-tooltip="' . esc_attr( $hover ) . '">' . esc_html( $base_label ) . '</span>';
        } else {
            $html .= esc_html( $visible_label );
        }
        $html .= '</a>';

        return $html;
    }

    
    private function render_image_swatch_term( $term, $selected, $item_class, array $image_data, $base_label, $display_label, $loading, $tooltip, $is_oos, $tooltip_label = '' ) {
        
        $hover        = ( (string) $tooltip_label !== '' ) ? (string) $tooltip_label : $display_label;
        $title_label  = $is_oos ? ( $display_label . ' - ' . __( 'Out of stock', 'ai-eshop-optimizer' ) ) : $hover;
        $title_attr   = ' title="' . esc_attr( $title_label ) . '"';
        
        
        
        $title_attr  .= ' data-swatch-label="' . esc_attr( $hover ) . '"';
        $tooltip_attr = ( $tooltip && ! $is_oos ) ? ' data-tooltip="' . esc_attr( $hover ) . '"' : '';

        $html  = '<a href="#" role="button" class="' . esc_attr( $item_class ) . '" data-original-text="' . esc_attr( $base_label ) . '" value="' . esc_attr( $term->slug ) . '"' . $title_attr . $tooltip_attr . ' ' . selected( sanitize_title( $selected ), $term->slug, false ) . '>';
        $html .= '<span class="om-variation-text" data-original-text="' . esc_attr( $base_label ) . '">';
        $html .= sprintf(
            '<img width="%1$d" height="%2$d" src="%3$s" class="om-swatch-image" alt="%4$s" loading="%5$s" decoding="async" />',
            (int) $image_data['width'],
            (int) $image_data['height'],
            esc_url( (string) $image_data['url'] ),
            esc_attr( $display_label ),
            esc_attr( $loading )
        );
        if ( $tooltip && ! $is_oos ) {
            $html .= '<span class="om-variation-tooltip" data-original-tooltip="' . esc_attr( $hover ) . '"></span>';
        }
        $html .= '</span></a>';

        return $html;
    }

    
    private function get_variation_ids_by_term( array $variations, $attribute, $slug ) {
        $out = array();
        $key = 'attribute_' . $this->encode_attribute_slug( (string) $attribute );
        foreach ( $variations as $v ) {
            $attrs = isset( $v['attributes'] ) && is_array( $v['attributes'] ) ? $v['attributes'] : array();
            if ( ! isset( $attrs[ $key ] ) ) { continue; }
            
            if ( (string) $attrs[ $key ] === '' || (string) $attrs[ $key ] === (string) $slug ) {
                $vid = (int) ( $v['variation_id'] ?? 0 );
                if ( $vid > 0 ) { $out[] = $vid; }
            }
        }
        return $out;
    }

    
    private function get_variation_description_by_ids( array $variations, array $variation_ids ) {
        if ( empty( $variation_ids ) ) { return ''; }
        $lookup = array_flip( array_map( 'intval', $variation_ids ) );
        foreach ( $variations as $v ) {
            $vid = (int) ( $v['variation_id'] ?? 0 );
            if ( ! isset( $lookup[ $vid ] ) ) { continue; }
            $d = isset( $v['variation_description'] ) ? trim( (string) $v['variation_description'] ) : '';
            if ( $d !== '' ) { return $d; }
        }
        return '';
    }

    
    private function is_term_out_of_stock( array $variations, array $variation_ids ) {
        if ( empty( $variation_ids ) ) { return true; }
        $lookup = array_flip( array_map( 'intval', $variation_ids ) );
        foreach ( $variations as $v ) {
            $vid = (int) ( $v['variation_id'] ?? 0 );
            if ( ! isset( $lookup[ $vid ] ) ) { continue; }
            if ( ! empty( $v['available'] ) ) { return false; }
        }
        return true;
    }

    
    private function resolve_product_settings( $product_id ) {
        $product_id = (int) $product_id;
        if ( isset( $this->product_settings_cache[ $product_id ] ) ) {
            return $this->product_settings_cache[ $product_id ];
        }

        $g = $this->get_settings();
        $global = array(
            'display_style'        => isset( $g['multi_select_display_style'] ) ? (string) $g['multi_select_display_style'] : 'grid',
            'multi_select_enabled' => ! empty( $g['multi_select_enabled'] ),
            'show_quantity'        => ! empty( $g['multi_select_show_quantity'] ),
            'cart_sync_enabled'    => ! isset( $g['cart_sync_enabled'] ) || ! empty( $g['cart_sync_enabled'] ),
            'source'               => 'global',
        );

        
        if ( get_post_type( $product_id ) !== 'product' ) {
            if ( $global['display_style'] !== 'dropdown' ) { $global['show_quantity'] = false; }
            $this->product_settings_cache[ $product_id ] = $global;
            return $global;
        }
        $type = class_exists( 'WC_Product_Factory' ) ? WC_Product_Factory::get_product_type( $product_id ) : '';
        if ( $type !== 'variable' ) {
            if ( $global['display_style'] !== 'dropdown' ) { $global['show_quantity'] = false; }
            $this->product_settings_cache[ $product_id ] = $global;
            return $global;
        }

        $resolved             = $global;
        $display_style_source = 'global';
        $show_quantity_source = 'global';

        
        $p_display  = get_post_meta( $product_id, '_om_swatch_display_style', true );
        $p_multi    = get_post_meta( $product_id, '_om_swatch_multi_select',  true );
        $p_qty      = get_post_meta( $product_id, '_om_swatch_show_quantity', true );

        if ( $p_display === 'grid' || $p_display === 'dropdown' ) {
            $resolved['display_style'] = $p_display;
            $resolved['source']        = 'product';
            $display_style_source      = 'product';
        }
        if ( $p_multi === 'yes' ) {
            $resolved['multi_select_enabled'] = true;
            $resolved['source']               = 'product';
        } elseif ( $p_multi === 'no' ) {
            $resolved['multi_select_enabled'] = false;
            $resolved['source']               = 'product';
        }
        if ( $p_qty === 'yes' ) {
            $resolved['show_quantity'] = true;
            $show_quantity_source      = 'product';
        } elseif ( $p_qty === 'no' ) {
            $resolved['show_quantity'] = false;
            $show_quantity_source      = 'product';
        }

        
        if ( $display_style_source === 'global' ) {
            $dropdown_cats = isset( $g['dropdown_style_categories'] ) ? (array) $g['dropdown_style_categories'] : array();
            $grid_cats     = isset( $g['grid_style_categories']     ) ? (array) $g['grid_style_categories']     : array();
            if ( ! empty( $dropdown_cats ) || ! empty( $grid_cats ) ) {
                $product_cats = wc_get_product_cat_ids( $product_id );
                if ( ! empty( $product_cats ) ) {
                    if ( ! empty( $dropdown_cats ) && array_intersect( $product_cats, $dropdown_cats ) ) {
                        $resolved['display_style'] = 'dropdown';
                        $resolved['source']        = 'category';
                        $display_style_source      = 'category';
                    }
                    
                    if ( ! empty( $grid_cats ) && array_intersect( $product_cats, $grid_cats ) ) {
                        $resolved['display_style'] = 'grid';
                        $resolved['source']        = 'category';
                        $display_style_source      = 'category';
                    }
                }
            }
        }

        
        
        if ( $resolved['source'] !== 'product' && $resolved['multi_select_enabled'] ) {
            $ms_cats = isset( $g['multi_select_categories'] ) ? (array) $g['multi_select_categories'] : array();
            if ( ! empty( $ms_cats ) ) {
                $product_cats = wc_get_product_cat_ids( $product_id );
                if ( empty( $product_cats ) || ! array_intersect( $product_cats, $ms_cats ) ) {
                    $resolved['multi_select_enabled'] = false;
                }
            }
        }

        
        
        if ( $show_quantity_source === 'global' && $resolved['show_quantity'] && $resolved['display_style'] === 'dropdown' ) {
            $q_cats = isset( $g['quantity_selector_categories'] ) ? (array) $g['quantity_selector_categories'] : array();
            if ( ! empty( $q_cats ) ) {
                $product_cats = wc_get_product_cat_ids( $product_id );
                if ( empty( $product_cats ) || ! array_intersect( $product_cats, $q_cats ) ) {
                    $resolved['show_quantity'] = false;
                }
            }
        }

        
        if ( $resolved['display_style'] !== 'dropdown' ) {
            $resolved['show_quantity'] = false;
        }

        $this->product_settings_cache[ $product_id ] = $resolved;
        return $resolved;
    }

    
    private function get_attribute_taxonomy( $attribute_name ) {
        $name = (string) $attribute_name;
        $key  = sanitize_title( $name );
        if ( array_key_exists( $key, $this->taxonomy_cache ) ) {
            return $this->taxonomy_cache[ $key ] ?: null;
        }
        $all = function_exists( 'wc_get_attribute_taxonomies' ) ? wc_get_attribute_taxonomies() : array();
        $short = str_replace( 'pa_', '', $name );
        foreach ( $all as $tax ) {
            if ( isset( $tax->attribute_name ) && (string) $tax->attribute_name === $short ) {
                $this->taxonomy_cache[ $key ] = $tax;
                return $tax;
            }
        }
        $this->taxonomy_cache[ $key ] = false;
        return null;
    }

    
    private function encode_attribute_slug( $attribute ) {
        $s = sanitize_title( (string) $attribute );
        
        return $s;
    }

    
    private function read_cache_row( $product_id, $context ) {
        $memo_key = ( (int) $product_id ) . '|' . (string) $context;
        if ( array_key_exists( $memo_key, $this->row_cache ) ) {
            return $this->row_cache[ $memo_key ];
        }

        global $wpdb;
        $table = $wpdb->prefix . 'aieo_dmm_swatches_cache';

        $row = $wpdb->get_row( $wpdb->prepare(
            "SELECT variations_json, variation_ids_json, variations_map_json,
                    total_available, stored_count, cache_hash
             FROM {$table}
             WHERE product_id = %d AND context = %s
             LIMIT 1",
            (int) $product_id,
            (string) $context
        ), ARRAY_A );

        if ( ! $row ) {
            $this->row_cache[ $memo_key ] = null;
            return null;
        }

        $current_hash = $this->get_settings_hash_cached();
        if ( $current_hash !== '' && $row['cache_hash'] !== '' && $row['cache_hash'] !== $current_hash ) {
            $this->pending_rebuilds[ (int) $product_id ] = true;
        }

        $variations = json_decode( (string) $row['variations_json'], true );
        $ids        = json_decode( (string) $row['variation_ids_json'], true );
        $map        = json_decode( (string) $row['variations_map_json'], true );

        $decoded = array(
            'variations'      => is_array( $variations ) ? $variations : array(),
            'ids'             => is_array( $ids ) ? $ids : array(),
            'map'             => is_array( $map ) ? $map : array(),
            'total_available' => (int) $row['total_available'],
            'stored_count'    => (int) $row['stored_count'],
        );
        $this->row_cache[ $memo_key ] = $decoded;
        return $decoded;
    }

    private function get_settings_hash_cached() {
        if ( $this->settings === null ) { $this->get_settings(); }
        return $this->settings_hash;
    }

    
    private function build_inline_fallback( WC_Product $product, $context ) {
        $s        = $this->get_settings();
        $hide_oos = get_option( 'woocommerce_hide_out_of_stock_items', 'no' ) === 'yes';
        $img_w    = $context === 'archive'
            ? (int) ( $s['image_width_shop_archive']  ?? 26 )
            : (int) ( $s['image_width']  ?? 40 );
        $img_h    = $context === 'archive'
            ? (int) ( $s['image_height_shop_archive'] ?? 26 )
            : (int) ( $s['image_height'] ?? 40 );
        $max_limit = $context === 'archive'
            ? max( (int) ( $s['image_swatch_limit_desktop'] ?? 5 ), (int) ( $s['image_swatch_limit_mobile'] ?? 3 ) )
            : 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 );
        }

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

        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] : '';
            $image_id     = isset( $meta['_thumbnail_id'][0] )  ? (int) $meta['_thumbnail_id'][0] : $parent_image_id;
            $img_url      = '';
            if ( $image_id > 0 ) {
                $inter = image_get_intermediate_size( $image_id, array( $img_w, $img_h ) );
                if ( is_array( $inter ) && ! empty( $inter['url'] ) ) {
                    $img_url = (string) $inter['url'];
                } else {
                    $full = wp_get_attachment_image_src( $image_id, 'full' );
                    $img_url = is_array( $full ) ? (string) $full[0] : '';
                }
            }

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

            $variations[] = array(
                'variation_id'          => (int) $vid,
                'available'             => ( $stock_status === 'instock' || $stock_status === 'onbackorder' ),
                'img_url'               => $img_url,
                'img_w'                 => $img_w,
                'img_h'                 => $img_h,
                'attributes'            => $attributes,
                'price_html'            => '',
                'sku'                   => isset( $meta['_sku'][0] ) ? (string) $meta['_sku'][0] : '',
                'stock_status'          => $stock_status,
                'variation_description' => $vdesc,
                'swatch_label'          => $swatch_label,
            );
            $map[ sha1( (string) wp_json_encode( $attributes ) ) ] = (int) $vid;
            $count++;
        }

        return array(
            'variations'      => $variations,
            'ids'             => array_column( $variations, 'variation_id' ),
            'map'             => $map,
            'total_available' => $total_available,
            'stored_count'    => count( $variations ),
        );
    }

    
    public function filter_cart_item_thumbnail( $thumbnail, $cart_item, $cart_item_key ) {
        $s    = $this->get_settings();
        $mode = (string) ( $s['cart_variation_image'] ?? 'swatch' );
        if ( $mode === 'product' ) { return $thumbnail; }

        $variation_id = isset( $cart_item['variation_id'] ) ? (int) $cart_item['variation_id'] : 0;
        if ( $variation_id <= 0 ) { return $thumbnail; }

        
        
        
        
        global $wpdb;
        $table = $wpdb->prefix . 'aieo_dmm_swatches_variation_map';
        $row   = $wpdb->get_row( $wpdb->prepare(
            "SELECT swatch_image_url, image_url FROM {$table} WHERE variation_id = %d LIMIT 1",
            $variation_id
        ), ARRAY_A );

        
        $url = '';
        if ( $mode === 'swatch' ) {
            $url = is_array( $row ) ? (string) ( $row['swatch_image_url'] ?? '' ) : '';
            
            if ( $url === '' && is_array( $row ) ) {
                $url = (string) ( $row['image_url'] ?? '' );
            }
        } elseif ( $mode === 'variation' ) {
            $url = is_array( $row ) ? (string) ( $row['image_url'] ?? '' ) : '';
        }

        if ( $url === '' ) { return $thumbnail; }

        return sprintf(
            '<img src="%1$s" class="attachment-woocommerce_thumbnail" alt="" />',
            esc_url( $url )
        );
    }

    
    public function flush_pending_rebuilds() {
        if ( empty( $this->pending_rebuilds ) ) { return; }
        $ids = array_keys( $this->pending_rebuilds );
        $this->pending_rebuilds = array();

        if ( function_exists( 'as_enqueue_async_action' ) ) {
            as_enqueue_async_action( 'aieo_dmm_swatches_rebuild_batch', array( $ids ), 'aieo-dmm-swatches' );
        } else {
            wp_schedule_single_event( time() + 1, 'aieo_dmm_swatches_rebuild_batch', array( $ids ) );
        }
    }
}
