/**
 * OM Product Swatch
 *
 * jQuery dependent: true
 */

'use strict';

function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
var omProductSwatches = omProductSwatches || {};

/**
 * Product Swatch
 */
omProductSwatches.productSwatch = {
  init: function init(e) {
    var wrapperEl = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
    var wrappers = this.getWrapperCollection(wrapperEl);
    var hasOmSelectSwatch = document.querySelectorAll('select.om-variation-item').length > 0;
    var newWrappers = [];

    for (var i = 0; i < wrappers.length; i++) {
      var currentWrapper = wrappers[i];

      if (!currentWrapper || currentWrapper.classList && currentWrapper.classList.contains('om-widget-variations')) {
        continue;
      }

      if (!currentWrapper.getAttribute('data-om-swatches-ready')) {
        currentWrapper.setAttribute('data-om-swatches-ready', '1');
        newWrappers.push(currentWrapper);
      }
    }

    if (!newWrappers.length && !hasOmSelectSwatch) {
      return false;
    }

    for (var _i = 0; _i < newWrappers.length; _i++) {
      this.variations(newWrappers[_i]);
    }

    if (newWrappers.length) {
      this.resetVariationsEvent(newWrappers);
      this.variationMouseOver(newWrappers);

      if (omProductSwatches.swatchLimitHandler && typeof omProductSwatches.swatchLimitHandler.init === 'function') {
        omProductSwatches.swatchLimitHandler.init(newWrappers);
      }

      // Notify vanilla interactivity layer about newly mounted swatches
      document.dispatchEvent(new CustomEvent('om.swatches.refresh', {
        detail: {
          root: newWrappers
        }
      }));
    } else {
      this.resetVariationsEvent();
      this.variationMouseOver();

      if (omProductSwatches.swatchLimitHandler && typeof omProductSwatches.swatchLimitHandler.init === 'function') {
        omProductSwatches.swatchLimitHandler.init();
      }
    }

    this.displayVariationNameOnProductTitle();
    this.handleActiveClassOnSelectSwatchType();

    return true;
  },
  getWrapperCollection: function getWrapperCollection(wrapperEl) {
    var wrappers = [];

    if (wrapperEl) {
      if (wrapperEl instanceof Element) {
        wrappers = [wrapperEl];
      } else if (wrapperEl instanceof NodeList || Array.isArray(wrapperEl)) {
        wrappers = Array.from(wrapperEl);
      } else if (wrapperEl && wrapperEl.length) {
        wrappers = Array.from(wrapperEl);
      }
    }

    if (!wrappers.length) {
      wrappers = Array.from(document.querySelectorAll('.om-variations-wrapper:not(.om-widget-variations)'));
    }

    return wrappers;
  },
  variations: function variations(wrapper) {
    if (!wrapper) {
      return;
    }

    // Skip archive pages - they use archiveProductSwatch instead
    // Only skip if it's specifically an archive context (has --archive class)
    var isArchivePage = wrapper.closest('.om-product-swatches--archive');
    if (isArchivePage) {
      return false;
    }

    var self = this,
      select = wrapper.querySelector('select'),
      items = wrapper.querySelectorAll('.om-variation-item');

    // Check if already initialized to prevent duplicate bindings
    var initialized = items[0] && items[0].closest('.om-variations-wrapper').classList.contains('js-initialized');
    if (initialized) {
      return false;
    }

    if (!select) {
      return;
    }

    // Set initial active state and bind click handlers
    for (var i = 0; i < items.length; i++) {
      if (select.value) {
        var variation_item_selected = wrapper.querySelector('.om-variation-item[value="' + select.value + '"]');
        if (variation_item_selected) {
          variation_item_selected.classList.add('active');
          variation_item_selected.dispatchEvent(new Event('om.variations.selected'));
        }
      }

      // Use IIFE to capture select in proper closure per item
      (function(currentSelect, currentItem) {
        currentItem.addEventListener('click', function (e) {
          e.preventDefault(); // ALWAYS prevent href="#" page jump

          // IMPORTANT: Prevent clicks on disabled/out-of-stock variations
          if (this.classList.contains('disabled') || this.classList.contains('permanent-disabled')) {
            return false;
          }

          var value = this.getAttribute('value');

          self.removeActiveClass(this);

          // Toggle: deselect if clicking same value
          if (value === currentSelect.value) {
            value = '';
          } else {
            this.classList.add('active');
          }

          jQuery(currentSelect).val(value).trigger('change');

          // Manually trigger WooCommerce's variation form check
          var $form = jQuery(currentSelect).closest('.variations_form');
          if ($form.length && typeof $form.trigger === 'function') {
            $form.trigger('check_variations');
          }

          self.matchVariations(this);
        });
      })(select, items[i]);

      // Keep this for other events
      items[i].addEventListener('om.variations.selected', function (e) {
        var _self = this;
        setTimeout(function () {
          self.matchVariations(_self);
        }, 300);
      });

      // Mark wrapper as initialized
      items[i].closest('.om-variations-wrapper').classList.add('js-initialized');
    }
  },
  matchVariations: function matchVariations(variation) {
    var wrapper = variation.closest('.variations').querySelectorAll('.om-variations-wrapper');
    for (var i = 0; i < wrapper.length; i++) {
      var arr = [],
        items = wrapper[i].querySelectorAll('.om-variation-item'),
        selectOptions = wrapper[i].querySelector('select').options;
      for (var u = 0; u < selectOptions.length; u++) {
        arr.push(selectOptions[u].value);
      }
      arr = arr.filter(function (e) {
        return e;
      });
      for (var a = 0; a < items.length; a++) {
        if (arr.includes(items[a].getAttribute('value'))) {
          items[a].classList.remove('disabled');
        } else {
          items[a].classList.add('disabled');
        }
      }
    }
  },
  removeActiveClass: function removeActiveClass(item) {
    var self = this,
      items = typeof item !== 'undefined' ? item.closest('div').querySelectorAll('.om-variation-item') : document.querySelectorAll('.om-variations-wrapper .om-variation-item');
    for (var u = 0; u < items.length; u++) {
      items[u].classList.remove('active');
      items[u].classList.remove('disabled');
    }
  },
  resetVariationsEvent: function resetVariationsEvent(context) {
    var self = this;
    var buttons = [];

    if (context && context.length) {
      for (var i = 0; i < context.length; i++) {
        var wrapper = context[i];
        if (!wrapper) {
          continue;
        }

        var form = wrapper.closest('form');
        if (form) {
          buttons = buttons.concat(Array.from(form.querySelectorAll('.reset_variations')));
        }
      }
    }

    if (!buttons.length) {
      buttons = Array.from(document.querySelectorAll('.reset_variations'));
    }

    for (var _i2 = 0; _i2 < buttons.length; _i2++) {
      var button = buttons[_i2];
      if (button.getAttribute('data-om-swatches-reset-bound')) {
        continue;
      }

      button.setAttribute('data-om-swatches-reset-bound', '1');
      button.addEventListener('click', function () {
        self.removeActiveClass(this);
      });
    }
  },
  getVariationAttributes: function getVariationAttributes(variations, value) {
    for (var index in variations) {
      if (variations[index]) {
        var variation = variations[index];
        var attributes = variation.attributes;
        for (var attribute in attributes) {
          if (attributes.hasOwnProperty(attribute)) {
            if (attributes[attribute] === value) {
              return variation;
            }
          }
        }
      }
    }
  },
  variationMouseOver: function variationMouseOver(context) {
    if (typeof jQuery !== 'function') {
      return;
    }

    var self = this;
    var $context = context && context.length ? jQuery(context) : jQuery(document);
    var $variations = $context.filter('.om-variations-mouseover');

    if (!$variations.length) {
      $variations = $context.find('.om-variations-mouseover');
    }

    $variations.each(function () {
      var $variation = jQuery(this);

      if ($variation.data('omSwatchMouseoverBound')) {
        return;
      }

      $variation.data('omSwatchMouseoverBound', true);
      var $block = $variation.closest('.wc-block-grid__product');
      var $product = $block.length ? $block : $variation.closest('.product');
      var $galleryImage = $product.find('.woocommerce-product-gallery__image');
      var $eael_galleryImage = $product.find('.elementor-widget-eael-woo-product-images');
      var $productImage = $product.find('.woocommerce-loop-product__link');
      var $blockImage = $product.find('.wc-block-grid__product');
      var $mainImage = $product.find('img').first();
      var $selector = $variation.find('select');
      if ($galleryImage.length) {
        $mainImage = $galleryImage.first().find('img').first();
      } else if ($productImage.length) {
        $mainImage = $productImage.first().find('img').first();
      } else if ($blockImage.length) {
        $mainImage = $blockImage.first().find('img').first();
      } else if ($eael_galleryImage.length) {
        $mainImage = $eael_galleryImage.first().find('img').first();
      }
      $mainImage.attr({
        'data-o_src': $mainImage.attr('src'),
        'data-o_srcset': $mainImage.attr('srcset'),
        'data-o_sizes': $mainImage.attr('sizes')
      });
      var variations = $product.find('form').data('product_variations');
      var $items = $variation.find('.om-variation-item');
      $items.each(function () {
        var $item = jQuery(this);
        var atts = {};
        $item.on('mouseover mouseout', function (event) {
          if (event.type === 'mouseover') {
            // Only use variations data if available
            if (variations && variations !== false) {
              var variation = self.getVariationAttributes(variations, $item.attr('value'));
              if (variation && variation.image) {
                atts.src = variation.image.thumb_src ? variation.image.thumb_src : variation.image.src;
                atts.srcset = variation.image.srcset;
                atts.sizes = variation.image.sizes;
                if ($galleryImage.length) {
                  atts.src = variation.image.src;
                }
                setTimeout(function () {
                  if ($eael_galleryImage.length) {
                    var newDiv = document.createElement('div');
                    newDiv.classList.add('om-variation-swatch-image');
                    newDiv.innerHTML = "<img src=\"".concat(variation.image.src, "\" srcset=\"").concat(variation.image.srcset, "\" sizes=\"").concat(variation.image.sizes, "\" />");
                    $eael_galleryImage.append(newDiv);
                    setTimeout(function () {
                      $eael_galleryImage.find('.om-variation-swatch-image').addClass('active');
                    }, 100);
                  }
                }, 100);
              }
            }
          } else {
            if ($selector.val() && variations && variations !== false) {
              var variation = self.getVariationAttributes(variations, $selector.val());
              if (variation && variation.image) {
                atts.src = variation.image.thumb_src;
                atts.srcset = variation.image.srcset;
                atts.sizes = variation.image.sizes;
                if ($galleryImage.length) {
                  atts.src = variation.image.src;
                }
              }
            } else {
              atts.src = $mainImage.attr('data-o_src');
              atts.srcset = $mainImage.attr('data-o_srcset');
              atts.sizes = $mainImage.attr('data-o_sizes');
            }
            setTimeout(function () {
              if ($eael_galleryImage.length) {
                $eael_galleryImage.find('.om-variation-swatch-image').remove();
              }
            }, 100);
          }
          if (atts.src && $mainImage.attr('src') !== atts.src) {
            $mainImage.attr(atts);
          }
        });
      });
    });
  },
  displayVariationNameOnProductTitle: function displayVariationNameOnProductTitle() {
    var self = this;
    if (typeof omSwatchSettings === 'undefined') {
      return false;
    }
    if (omSwatchSettings.customizerOptions.displayVariationNameOnProductTitle == 0) {
      return false;
    }
    // Only run on single product pages, NOT archives
    if (!jQuery('body').hasClass('single-product')) {
      return false;
    }
    var isQuickView = jQuery('.om-quick-view-popup').length ? jQuery('.om-quick-view-popup').hasClass('opened') : false;
    // Title selector covers classic themes (.product_title inside .entry-summary)
    // AND block themes (core Post Title => .wp-block-post-title; roosterx-ff =>
    // .ff-product-title) which have NO .entry-summary / .product_title.
    var titleSel = '.entry-summary .product_title, h1.product_title, .ff-product-title, .wp-block-post-title';
    var form = isQuickView ? jQuery('.single-product.om-quick-view-popup .variations_form') : jQuery('.single-product .variations_form');
    var productTitleEl = isQuickView ? jQuery('.single-product.om-quick-view-popup').find(titleSel).first() : jQuery(titleSel).first();
    if (!productTitleEl.length) {
      return false;
    }
    // Base title text without any previously-appended caption span.
    var productTitle = productTitleEl.clone().children('.om-ptitle-variation-name').remove().end().text();
    // Resolve the title element to write into for a given swatch/form context,
    // honouring sticky add-to-cart + quick view, else the main title element.
    function titleTagFor($el) {
      if ($el.closest('.om-single-sticky-add-to-cart-wrapper').length) {
        return $el.closest('.om-single-sticky-add-to-cart-wrapper').find('.sticky-addtocart-title');
      }
      if (isQuickView) {
        return $el.closest('.om-quick-view-summary').find(titleSel).first();
      }
      var $t = $el.closest('.entry-summary, .product, .type-product, .variations_form').find(titleSel).first();
      return $t.length ? $t : productTitleEl;
    }
    jQuery(document).off('click.omVariationTitle').on('click.omVariationTitle', '.om-variation-item', function (e) {
      var variationName = self.getVariationName(jQuery(this));
      titleTagFor(jQuery(this)).html(productTitle + ' <span class="om-ptitle-variation-name">' + variationName + '</span>');
    });
    form.off('reset_data.omVariationTitle').on('reset_data.omVariationTitle', function (e) {
      titleTagFor(jQuery(this)).html(productTitle);
    });
    form.off('check_variations.wc-variation-form.omVariationTitle').on('check_variations.wc-variation-form.omVariationTitle', function (e) {
      var $form = jQuery(this);
      var variation = $form.find('table.variations tbody tr .om-variation-item.active');
      var variationName = self.getVariationName(jQuery(variation));
      titleTagFor(jQuery(variation)).html(productTitle + ' <span class="om-ptitle-variation-name">' + variationName + '</span>');
    });
  },
  isButtonSwatchType: function isButtonSwatchType($el) {
    return $el.closest('.om-variation-type-button').length;
  },
  isSelectSwatchType: function isSelectSwatchType($el) {
    return $el.prop('tagName') === 'SELECT';
  },
  handleActiveClassOnSelectSwatchType: function handleActiveClassOnSelectSwatchType() {
    jQuery('select.om-variation-item').off('change').on('change', function () {
      if (jQuery(this).val()) {
        jQuery(this).addClass('active');
      } else {
        jQuery(this).removeClass('active');
      }
    });
  },
  getVariationName: function getVariationName(variationItem) {
    var self = this,
      isStickyAddToCart = variationItem.closest('.om-single-sticky-add-to-cart-wrapper').length,
      isQuickView = jQuery('.om-quick-view-popup').length ? jQuery('.om-quick-view-popup').hasClass('opened') : false,
      product = isStickyAddToCart ? variationItem.closest('.om-single-sticky-add-to-cart-wrapper') : isQuickView ? variationItem.closest('.om-quick-view-summary') : variationItem.closest('.entry-summary, .variations_form, .product'),
      hasMultipleVariations = product.find('table.variations tbody tr').length > 1;
    var variationName = '';
    if (hasMultipleVariations) {
      var _iterator = _createForOfIteratorHelper(product.find('table.variations tbody tr .om-variation-item')),
        _step;
      try {
        for (_iterator.s(); !(_step = _iterator.n()).done;) {
          var variation = _step.value;
          if (jQuery(variation).hasClass('active')) {
            var isButtonSwatchType = self.isButtonSwatchType(jQuery(variation)),
              isSelectSwatchType = self.isSelectSwatchType(jQuery(variation));
            if (isSelectSwatchType) {
              variationName += jQuery(variation).find('option:selected').text() + ' ';
            }
            if (isButtonSwatchType) {
              variationName += jQuery(variation).text() + ' ';
            }
            if (omSwatchSettings.customizerOptions.tooltip && !isButtonSwatchType) {
              var tooltipText = jQuery(variation).find('.om-variation-tooltip').attr('data-original-tooltip') || jQuery(variation).find('.om-variation-tooltip').text();
              variationName += tooltipText + ' ';
            }
            if (!omSwatchSettings.customizerOptions.tooltip && !isButtonSwatchType) {
              var omSL = jQuery(variation).attr('data-swatch-label');
              variationName += (omSL ? omSL : jQuery(variation).find('.om-variation-text').text()) + ' ';
            }
          }
        }
      } catch (err) {
        _iterator.e(err);
      } finally {
        _iterator.f();
      }
    } else {
      if (jQuery(variationItem).hasClass('active')) {
        var _isButtonSwatchType = self.isButtonSwatchType(jQuery(variationItem)),
          _isSelectSwatchType = self.isSelectSwatchType(jQuery(variationItem));
        if (_isSelectSwatchType) {
          return variationName = jQuery(variationItem).find('option:selected').text();
        }
        if (_isButtonSwatchType) {
          variationName = jQuery(variationItem).text();
        }
        if (omSwatchSettings.customizerOptions.tooltip && !_isButtonSwatchType) {
          variationName = jQuery(variationItem).find('.om-variation-tooltip').attr('data-original-tooltip') || jQuery(variationItem).find('.om-variation-tooltip').text();
        }
        if (!omSwatchSettings.customizerOptions.tooltip && !_isButtonSwatchType) {
          var _omSL = jQuery(variationItem).attr('data-swatch-label');
          variationName = _omSL ? _omSL : jQuery(variationItem).find('.om-variation-text').text();
        }
      }
    }
    return variationName;
  }
};

/**
 * Archive Product Swatches
 */
omProductSwatches.archiveProductSwatch = {
  init: function init(context) {
    var self = this;
    if (typeof jQuery !== 'function') {
      return;
    }

    // Only target archive pages (with --archive class), not single product pages
    var $forms = context ? context instanceof jQuery ? context : jQuery(context) : jQuery('.om-product-swatches--archive .variations_form');

    if (!$forms.length || typeof wc_add_to_cart_variation_params === 'undefined') {
      return;
    }

    $forms = $forms.filter(function () {
      var $form = jQuery(this);

      if ($form.attr('data-om-swatches-archive-ready')) {
        return false;
      }

      $form.attr('data-om-swatches-archive-ready', '1');
      return true;
    });

    if (!$forms.length) {
      return;
    }

    var ajaxAddToCart = typeof wc_add_to_cart_params !== 'undefined';
    this.handleActiveClassOnSelectSwatchType();

    // Initialize each form individually
    $forms.each(function () {
      var $form = jQuery(this);

      // Just call wc_variation_form() - NO manual trigger needed
      if (typeof jQuery.fn.wc_variation_form === 'function') {
        $form.wc_variation_form();
      }

      var isGutenberg = $form.closest('.wc-block-grid__products').length;
      var $product = isGutenberg ? $form.closest('.wc-block-grid__product') : $form.closest('.product');
      var $price = $product.find('.price').first();
      var $image = $product.find('img').first();
      var $button = $product.find('.add_to_cart_button').first();
      if (!$button.length) {
        $button = $product.find('.button.product_type_variable');
      }
      var $swatches = $product.find('.om-product-swatches');
      var $clonedPrice = $price.clone();
      var $clonedImage = $image.clone();
      var $clonedButton = $button.clone();
      var buttonLayout = $swatches.data('button-layout');
      var foundVariation = false;
      if (buttonLayout === 'layout3' || buttonLayout === 'layout4') {
        jQuery(document).on('added_to_cart', function () {
          jQuery('.added_to_cart').remove();
        });
      }
      $form.on('found_variation', function (event, variation) {
        var imageSrc = $clonedImage.attr('src'),
          imageSrcSet = $clonedImage.attr('srcset'),
          imageSizes = $clonedImage.attr('sizes');

        // Check if variation has image data
        if (variation.image && variation.image.thumb_src && variation.image.thumb_src !== $clonedImage.attr('src')) {
          imageSrc = variation.image.thumb_src;
          imageSrcSet = variation.image.srcset || '';
          imageSizes = variation.image.sizes || '';
        }

        $image.attr({
          'src': imageSrc,
          'srcset': imageSrcSet,
          'sizes': imageSizes
        });

        // Check if price_html exists before updating
        if (variation.price_html) {
          $price.html(jQuery(variation.price_html).html());
        }
        if ($button.hasClass('added')) {
          $button.removeClass('added');
          $button.next('.added_to_cart').remove();
        }
        if (buttonLayout !== 'layout4') {
          if (variation.is_in_stock || variation.backorders_allowed) {
            if (ajaxAddToCart) {
              if ($button.closest('.button-layout3').length) {
                $button.addClass('add_to_cart_button');
              }
              $button.attr({
                'data-quantity': 1,
                'data-product_id': variation.variation_id,
                'data-product_sku': variation.variation_sku
              });
            } else {
              $button.attr('href', '?add-to-cart=' + variation.variation_id);
            }
            $button.html($swatches.data('button-add-text')).addClass('ajax_add_to_cart');
          } else {
            if (!ajaxAddToCart) {
              $button.attr('href', $clonedButton.attr('href'));
            }
            $button.html($swatches.data('button-select-text')).removeClass('ajax_add_to_cart');
          }
        }

        // stock status
        var $productStockMessage = $product.find('.om-product-stock'),
          initialStockValue = $product.find('.om-product-stock > span').text();
        if (typeof $productStockMessage.data('initial-stock-status') === 'undefined') {
          $productStockMessage.data('initial-stock-status', initialStockValue);
        }
        $productStockMessage.removeClass('om-product-stock-low');
        if ($productStockMessage.length) {
          if (variation.is_in_stock && variation.availability_html.indexOf('available-on-backorder') !== -1) {
            $productStockMessage.find('> span').html($product.find('.om-product-stock').data('backorder-text'));
          } else if (variation.is_in_stock) {
            $productStockMessage.find('> span').html($product.find('.om-product-stock').data('in-stock-text'));
          } else {
            $productStockMessage.find('> span').html($product.find('.om-product-stock').data('out-of-stock-text'));
            $productStockMessage.addClass('om-product-stock-low');
          }
          $product.find('.woocommerce-variation-availability').css('display', 'none');
        }
        foundVariation = true;
      });
      $form.on('reset_data', function () {
        if (foundVariation) {
          $image.attr({
            'src': $clonedImage.attr('src'),
            'srcset': $clonedImage.attr('srcset'),
            'sizes': $clonedImage.attr('sizes')
          });
          $price.html($clonedPrice.html());
          if (buttonLayout !== 'layout4') {
            $button.html($swatches.data('button-select-text')).removeClass('ajax_add_to_cart');
          }
          if (!ajaxAddToCart) {
            $button.attr('href', $clonedButton.attr('href'));
          }
          if ($button.hasClass('added')) {
            $button.removeClass('added');
            $button.next('.added_to_cart').remove();
          }
          $product.find('.om-product-stock > span').html($product.find('.om-product-stock').data('initial-stock-status'));
          foundVariation = false;
        }
      });
      self.displayVariationNameOnProductTitle($form);
      self.handleBuyNowButton($form, $product);
    });

    // PERFORMANCE: Use event delegation instead of binding to each swatch
    // This reduces memory usage from N listeners to 1 listener
    this.initSwatchClickHandlers();
  },
  initSwatchClickHandlers: function initSwatchClickHandlers() {
    var self = this;

    // PERFORMANCE: Single delegated event listener for ALL archive swatches
    // Instead of attaching handlers to 50+ individual elements, we use one listener on body
    if (document.body.getAttribute('data-om-archive-delegation-bound')) {
      return; // Already bound
    }
    document.body.setAttribute('data-om-archive-delegation-bound', '1');

    jQuery(document.body).on('click.omArchiveSwatch', '.om-product-swatches--archive .om-variation-item', function(e) {
      e.preventDefault(); // ALWAYS prevent default href="#" behavior
      e.stopImmediatePropagation();

      var $this = jQuery(this);
      var wrapper = $this.closest('.om-variations-wrapper')[0];
      if (!wrapper) return;

      var select = wrapper.querySelector('select');
      if (!select) return;

      var value = $this.attr('value');
      var currentValue = select.value;

      // Remove active class from all swatches in this wrapper
      jQuery(wrapper).find('.om-variation-item').removeClass('active');

      // Toggle selection (deselect if clicking same value)
      if (value === currentValue) {
        select.value = '';
        // No active class when deselected
      } else {
        select.value = value;
        // Add active class to clicked swatch
        $this.addClass('active');
      }

      // Get the form and button
      var $form = jQuery(select).closest('.variations_form');
      var $product = $form.closest('.product');
      var $button = $product.find('.add_to_cart_button, .button.product_type_variable').first();

      // Trigger change event on select to sync with WooCommerce form
      jQuery(select).trigger('change');

      // HYBRID APPROACH: Check if variations data is inline or needs AJAX fetch
      var variations = $form.data('product_variations');

      if (variations && Array.isArray(variations)) {
        // INLINE DATA: Use pre-loaded variation data
        self.updateProductFromInlineData($form, $product, $button, select.value, variations);
      } else if (variations === false) {
        // AJAX FETCH: Load from optimized cache
        self.updateProductFromCache($form, $product, $button, select.value);
      }

      // Trigger check_variations for WooCommerce form sync
      if ($form.length && typeof $form.trigger === 'function') {
        $form.trigger('check_variations');
      }
    });
  },

  updateProductFromInlineData: function updateProductFromInlineData($form, $product, $button, selectedValue, variations) {
    // Find matching variation from inline data
    var matchedVariation = null;

    for (var i = 0; i < variations.length; i++) {
      var variation = variations[i];
      var attributes = variation.attributes;

      // Check if this variation matches the selected attribute
      for (var attrName in attributes) {
        if (attributes[attrName] === selectedValue) {
          matchedVariation = variation;
          break;
        }
      }

      if (matchedVariation) break;
    }

    if (matchedVariation) {
      this.applyVariationToProduct($product, $button, matchedVariation);
    }
  },

  updateProductFromCache: function updateProductFromCache($form, $product, $button, selectedValue) {
    var self = this;
    var productId = $form.data('product_id');

    // Check if we already have cached data from a previous AJAX call
    var cachedVariations = $form.data('om_cached_variations');

    if (cachedVariations) {
      // Use previously fetched cache data
      self.findAndApplyVariation($product, $button, selectedValue, cachedVariations);
      return;
    }

    // PERFORMANCE: Fetch lightweight data for archive pages (50-70% smaller payload)
    jQuery.ajax({
      url: omSwatchSettings.ajax_url,
      method: 'POST',
      data: {
        action: 'aieo_swatch_get_all_variations',
        product_id: productId,
        security: omSwatchSettings.security,
        lightweight: 'true' // Request minimal data for archive pages
      },
      success: function(response) {
        if (response.success && response.data) {
          // Cache the fetched data in the form element for reuse
          $form.data('om_cached_variations', response.data);

          // Apply the variation
          self.findAndApplyVariation($product, $button, selectedValue, response.data);
        }
      },
      error: function() {
        // Fallback: just show select options button
        var selectText = $product.find('.om-product-swatches').data('button-select-text') || 'Select options';
        $button.html(selectText).removeClass('ajax_add_to_cart');
      }
    });
  },

  findAndApplyVariation: function findAndApplyVariation($product, $button, selectedValue, variationsData) {
    var matchedVariation = null;

    // Cached data has different structure - variation_id, attributes, etc.
    for (var i = 0; i < variationsData.length; i++) {
      var varData = variationsData[i];
      var attributes = varData.attributes;

      for (var attrName in attributes) {
        if (attributes[attrName] === selectedValue) {
          matchedVariation = varData;
          break;
        }
      }

      if (matchedVariation) break;
    }

    if (matchedVariation) {
      this.applyVariationToProduct($product, $button, matchedVariation);
    }
  },

  applyVariationToProduct: function applyVariationToProduct($product, $button, variation) {
    // Update button text and attributes based on stock (if available)
    if (variation.is_in_stock || variation.backorders_allowed) {
      var buttonText = $product.find('.om-product-swatches').data('button-add-text') || 'Add to cart';
      $button.html(buttonText).addClass('ajax_add_to_cart add_to_cart_button');
      $button.attr({
        'data-quantity': 1,
        'data-product_id': variation.variation_id,
        'data-product_sku': variation.sku || variation.variation_sku || ''
      });
    } else {
      var selectText = $product.find('.om-product-swatches').data('button-select-text') || 'Select options';
      $button.html(selectText).removeClass('ajax_add_to_cart');
    }

    // Update image if variation has one
    if (variation.image && variation.image.thumb_src) {
      var $image = $product.find('img').first();
      $image.attr({
        'src': variation.image.thumb_src,
        'srcset': variation.image.srcset || '',
        'sizes': variation.image.sizes || ''
      });
    }

    // Update price if variation has one
    if (variation.price_html) {
      var $price = $product.find('.price').first();
      $price.html(jQuery(variation.price_html).html());
    }

    // Update variation caption on selection. Prefer the server-built
    // "description - attribute" label (swatch_label, e.g. "No100 Fair -
    // Ανοιχτόχρωμο") from the swatch cache; fall back to the plain WC
    // variation description on the inline (single-product) path.
    if (variation.swatch_label || variation.variation_description || variation.description) {
      var desc = variation.swatch_label || variation.variation_description || variation.description || '';

      // Strip HTML tags and get plain text
      if (desc) {
        var $tempDiv = jQuery('<div>').html(desc);
        desc = $tempDiv.text().trim();
      }

      if (desc) {
        // Find or create description container
        var $descContainer = $product.find('.om-variation-description');

        if (!$descContainer.length) {
          // Create container if it doesn't exist
          var $insertAfter = $product.find('.om-product-swatches').length ?
            $product.find('.om-product-swatches') :
            $product.find('.woocommerce-loop-product__title, h2, h3').first();

          if ($insertAfter.length) {
            $descContainer = jQuery('<div class="om-variation-description"></div>');
            $insertAfter.after($descContainer);
          }
        }

        if ($descContainer.length) {
          $descContainer.html('<p>' + desc + '</p>').show();
        }
      }
    } else {
      // No description - hide container if it exists
      var $descContainer = $product.find('.om-variation-description');
      if ($descContainer.length) {
        $descContainer.hide().empty();
      }
    }
  },
  handleBuyNowButton: function handleBuyNowButton($form, $product) {
    var self = this,
      hasBuyNowButton = $form.closest('.om-product-swatches').find('.om-buy-now-button').length;
    if (!hasBuyNowButton) {
      return false;
    }
    var $buyNowButton = $form.closest('.om-product-swatches').find('.om-buy-now-button');
    $form.on('found_variation', function (event, variation) {
      $buyNowButton.removeClass('disabled');
      self.updateBuyNowButtonURL(event, variation, $buyNowButton, $product);
    });
    $form.on('reset_data', function () {
      $buyNowButton.addClass('disabled');
    });
  },
  updateBuyNowButtonURL: function updateBuyNowButtonURL(event, variation, $buyNowButton, $product) {
    if (!$buyNowButton.length) {
      return false;
    }
    var url = new URL($buyNowButton.attr('href'));
    url.searchParams.set('variation_id', variation.variation_id);
    $buyNowButton.attr('href', url);
  },
  displayVariationNameOnProductTitle: function displayVariationNameOnProductTitle($form) {
    var self = this;
    if (typeof omSwatchSettings === 'undefined') {
      return false;
    }
    if (omSwatchSettings.customizerOptions.displayVariationNameOnProductTitle == 0) {
      return false;
    }

    if (!document.documentElement.getAttribute('data-om-archive-title-bound')) {
      document.documentElement.setAttribute('data-om-archive-title-bound', '1');

      // Display variation name in the product title
      document.addEventListener('om.swatch.selected', function(e) {
        if (!e.detail || !e.detail.swatch) return;

        var swatch = e.detail.swatch;
        var $swatch = jQuery(swatch);
        var productTitleTag = $swatch.closest('.product').find('.om-wc-loop-product__title');

        if (!productTitleTag.length) return;

        var productTitle = productTitleTag.data('product-title-text') ? productTitleTag.data('product-title-text') : productTitleTag.text();
        var variationName = self.getVariationName($swatch);

        if (typeof productTitleTag.data('product-title-text') === 'undefined') {
          productTitleTag.data('product-title-text', productTitleTag.text());
        }

        productTitleTag.html(productTitle + ' <span class="om-ptitle-variation-name">' + variationName + '</span>');
      });
    }

    $form.off('check_variations.wc-variation-form.omArchiveTitle').on('check_variations.wc-variation-form.omArchiveTitle', function (e) {
      var $form = jQuery(this);
      var variation = $form.find('table.variations tbody tr .om-variation-item.active');
      var productTitleTag = jQuery(variation).closest('.product').find('.om-wc-loop-product__title'),
        productTitle = productTitleTag.data('product-title-text') ? productTitleTag.data('product-title-text') : productTitleTag.text(),
        variationName = self.getVariationName(jQuery(variation));
      if (typeof productTitleTag.data('product-title-text') === 'undefined') {
        productTitleTag.data('product-title-text', productTitleTag.text());
      }
      productTitleTag.html(productTitle + ' <span class="om-ptitle-variation-name">' + variationName + '</span>');
    });
  },
  isButtonSwatchType: function isButtonSwatchType($el) {
    return $el.closest('.om-variation-type-button').length;
  },
  isSelectSwatchType: function isSelectSwatchType($el) {
    return $el.prop('tagName') === 'SELECT';
  },
  handleActiveClassOnSelectSwatchType: function handleActiveClassOnSelectSwatchType() {
    jQuery('select.om-variation-item').off('change').on('change', function () {
      if (jQuery(this).val()) {
        jQuery(this).addClass('active');
      } else {
        jQuery(this).removeClass('active');
      }
    });
  },
  getVariationName: function getVariationName(variationItem) {
    var self = this,
      hasMultipleVariations = variationItem.closest('.product').find('table.variations tbody tr').length > 1;
    var variationName = '';
    if (hasMultipleVariations) {
      var _iterator2 = _createForOfIteratorHelper(variationItem.closest('.product').find('table.variations tbody tr .om-variation-item')),
        _step2;
      try {
        for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
          var variation = _step2.value;
          if (jQuery(variation).hasClass('active')) {
            var isButtonSwatchType = self.isButtonSwatchType(jQuery(variation)),
              isSelectSwatchType = self.isSelectSwatchType(jQuery(variation));
            if (isSelectSwatchType) {
              variationName += jQuery(variation).find('option:selected').text() + ' ';
            }
            if (isButtonSwatchType) {
              variationName += jQuery(variation).text() + ' ';
            }
            if (omSwatchSettings.customizerOptions.tooltip && !isButtonSwatchType) {
              variationName += jQuery(variation).find('.om-variation-tooltip').text() + ' ';
            }
            if (!omSwatchSettings.customizerOptions.tooltip && !isButtonSwatchType) {
              var omSL2 = jQuery(variation).attr('data-swatch-label');
              variationName += (omSL2 ? omSL2 : jQuery(variationItem).find('.om-variation-text').text()) + ' ';
            }
          }
        }
      } catch (err) {
        _iterator2.e(err);
      } finally {
        _iterator2.f();
      }
    } else {
      if (jQuery(variationItem).hasClass('active')) {
        var _isButtonSwatchType2 = self.isButtonSwatchType(jQuery(variationItem)),
          _isSelectSwatchType2 = self.isSelectSwatchType(jQuery(variationItem));
        if (_isSelectSwatchType2) {
          return variationName = jQuery(variationItem).find('option:selected').text();
        }
        if (_isButtonSwatchType2) {
          variationName = jQuery(variationItem).text();
        }
        if (omSwatchSettings.customizerOptions.tooltip && !_isButtonSwatchType2) {
          variationName = jQuery(variationItem).find('.om-variation-tooltip').text();
        }
        if (!omSwatchSettings.customizerOptions.tooltip && !_isButtonSwatchType2) {
          variationName = jQuery(variationItem).find('.om-variation-text').text();
        }
      }
    }
    return variationName;
  }
};

/**
 * Variation Description Enhancement
 */
omProductSwatches.variationDescriptionEnhancement = {
  serverMerged: false,
  init: function init() {
    if (typeof omSwatchSettings === 'undefined') {
      return;
    }

    var options = omSwatchSettings.customizerOptions || {};
    if (!options.appendVariationDescription) {
      return;
    }

    this.serverMerged = !!options.serverMergedDescriptions;

    if (typeof jQuery !== 'function') {
      return;
    }

    var self = this;
    var variationForm = jQuery('.variations_form');
    if (!variationForm.length) {
      return;
    }

    variationForm.each(function () {
      var $form = jQuery(this);
      var productId = $form.data('product_id');
      if (!productId) {
        return;
      }

      self.updateSwatchAltTooltips($form, productId);
      self.handleVariationFound($form);
    });
  },
  updateSwatchAltTooltips: function updateSwatchAltTooltips($form, productId) {
    if (this.serverMerged) {
      return;
    }

    jQuery.ajax({
      url: omSwatchSettings.ajax_url || '',
      method: 'POST',
      data: {
        action: 'aieo_swatch_get_all_variations',
        product_id: productId,
        security: omSwatchSettings.security || ''
      },
      success: function success(response) {
        if (!response.success) {
          console.error('Failed to retrieve variations:', response.data);
          return;
        }

        var variationsData = response.data;
        var variationsMap = {};

        jQuery.each(variationsData, function (index, variation) {
          var attributes = variation.attributes || {};
          var variationDescription = variation.variation_description || '';

          if (variationDescription) {
            var tempDiv = jQuery('<div>').html(variationDescription);
            variationDescription = tempDiv.text().trim();
          }

          if (!variationDescription) {
            return;
          }

          jQuery.each(attributes, function (attr, value) {
            if (!value) {
              return;
            }

            if (!variationsMap[value]) {
              variationsMap[value] = [];
            }

            variationsMap[value].push(variationDescription);
          });
        });

        $form.find('.om-variation-item').each(function () {
          var $item = jQuery(this);
          var value = $item.attr('value');
          var variationDescriptions = variationsMap[value];

          if (!variationDescriptions || !variationDescriptions.length) {
            return;
          }

          var variationDescription = variationDescriptions[0];
          var imgElement = $item.find('img');

          if (imgElement.length) {
            var originalAlt = imgElement.attr('data-original-alt') || imgElement.attr('alt') || '';
            if (!imgElement.attr('data-original-alt')) {
              imgElement.attr('data-original-alt', originalAlt);
            }
            imgElement.attr('alt', originalAlt ? originalAlt + ' - ' + variationDescription : variationDescription);
          }

          var tooltipElement = $item.find('.om-variation-tooltip');
          if (tooltipElement.length) {
            var originalTooltip = tooltipElement.attr('data-original-tooltip') || tooltipElement.text() || '';
            if (!tooltipElement.attr('data-original-tooltip')) {
              tooltipElement.attr('data-original-tooltip', originalTooltip);
            }
            tooltipElement.text(originalTooltip ? originalTooltip + ' - ' + variationDescription : variationDescription);
          }
        });
      },
      error: function error(xhr, status, _error) {
        console.error('AJAX error:', _error);
      }
    });
  },
  handleVariationFound: function handleVariationFound($form) {
    var self = this;

    $form.on('found_variation', function (event, variation) {
      var variationDescription = variation.variation_description || '';

      if (variationDescription) {
        var tempDiv = jQuery('<div>').html(variationDescription);
        variationDescription = tempDiv.text().trim();
      }

      var selectedAttributes = variation.attributes || {};
      var swatchTextArray = [];

      jQuery.each(selectedAttributes, function (attributeName, selectedValue) {
        var $swatch = $form.find('.om-variation-item[value="' + selectedValue + '"]');
        var swatchText = '';

        if ($swatch.length) {
          if (self.serverMerged) {
            var $tooltip = $swatch.find('.om-variation-tooltip');
            if ($tooltip.length && $tooltip.attr('data-original-tooltip')) {
              swatchText = $tooltip.attr('data-original-tooltip');
            }

            if (!swatchText) {
              swatchText = $swatch.attr('data-original-text') || '';
            }

            if (!swatchText) {
              var $textEl = $swatch.find('.om-variation-text');
              if ($textEl.length) {
                swatchText = $textEl.attr('data-original-text') || '';
              }
            }

            if (!swatchText) {
              var $imgEl = $swatch.find('img');
              if ($imgEl.length) {
                swatchText = $imgEl.attr('data-original-alt') || $imgEl.attr('alt') || '';
              }
            }

            if (!swatchText) {
              swatchText = $swatch.text();
            }
          } else {
            var $imgEl2 = $swatch.find('img');
            if ($imgEl2.length) {
              swatchText = $imgEl2.attr('data-original-alt') || $imgEl2.attr('alt') || '';
            }

            if (!swatchText) {
              var $textEl2 = $swatch.find('.om-variation-text');
              if ($textEl2.length) {
                swatchText = $textEl2.attr('data-original-text') || $textEl2.text();
              } else {
                swatchText = $swatch.text();
              }
            }
          }
        } else {
          swatchText = selectedValue.replace(/-/g, ' ');
        }

        if (swatchText) {
          swatchText = swatchText.replace(/\s+/g, ' ').trim();
          swatchTextArray.push(swatchText);
        }
      });

      var swatchTextCombined = swatchTextArray.join(' / ');
      var newDescription = '';

      if (self.serverMerged) {
        newDescription = variationDescription || swatchTextCombined;
      } else {
        newDescription = swatchTextCombined;
        if (variationDescription && newDescription.indexOf(variationDescription) === -1) {
          newDescription += (newDescription ? ' - ' : '') + variationDescription;
        }
      }

      var $descEl = $form.find('.woocommerce-variation.single_variation .woocommerce-variation-description');
      if ($descEl.length) {
        if (newDescription) {
          $descEl.html('<p>' + newDescription + '</p>');
        } else {
          $descEl.empty();
        }
      }

      var $titleVariation = jQuery('.om-ptitle-variation-name');
      if ($titleVariation.length > 0) {
        if (newDescription) {
          $titleVariation.text(newDescription);
        } else {
          $titleVariation.text('');
        }
      }
    });
  }
};


/**
 * Swatch Limit Handler
 */
omProductSwatches.swatchLimitHandler = {
  init: function init(context) {
    if (typeof jQuery !== 'function') {
      return;
    }

    // Only apply on shop/archive pages, not on single product pages
    if (jQuery('body').hasClass('single-product')) {
      return;
    }

    var swatchLimit = this.getSwatchLimit();
    if (!swatchLimit) {
      return;
    }

    var $context = context ? jQuery(context) : jQuery(document);
    if (!$context.length) {
      $context = jQuery(document);
    }

    var $wrappers = $context.filter('.om-variations-wrapper');
    if (!$wrappers.length) {
      $wrappers = $context.find('.om-variations-wrapper');
    }

    if (!$wrappers.length) {
      return;
    }

    var self = this;

    $wrappers.each(function () {
      var $wrapper = jQuery(this);

      if ($wrapper.hasClass('om-widget-variations')) {
        return;
      }

      if ($wrapper.closest('.om-quick-view-modal, .botiga-quick-view-popup-content, .brx-popup-content, .om-modal').length) {
        return;
      }

      var $imageSwatches = $wrapper.find('.om-variation-type-image > a:not(.om-more-variations)');
      if (!$imageSwatches.length) {
        return;
      }

      // Handle server-side limited swatches - add "+XX more" counter using cached total
      if ($wrapper.data('server-swatch-limit')) {
        self.applyServerLimitCounter($wrapper, $imageSwatches);
        return;
      }

      self.resetSwatches($wrapper, $imageSwatches);

      if ($imageSwatches.length > swatchLimit) {
        self.applyLimit($wrapper, $imageSwatches, swatchLimit);
      }
    });
  },

  getSwatchLimit: function getSwatchLimit() {
    if (typeof omSwatchSettings === 'undefined' || !omSwatchSettings.customizerOptions) {
      return 0;
    }

    var limit = parseInt(omSwatchSettings.customizerOptions.imageSwatchLimitShopArchive, 10);

    if (isNaN(limit) || limit < 1) {
      return 0;
    }

    return limit;
  },

  resetSwatches: function resetSwatches($wrapper, $swatches) {
    if ($wrapper.data('server-swatch-limit')) {
      return;
    }

    if ($wrapper.data('js-swatch-limit')) {
      $wrapper.removeData('js-swatch-limit');
    }

    $wrapper.find('.om-more-variations').remove();

    $swatches.each(function () {
      jQuery(this)
        .show()
        .removeClass('om-variation-item--hidden')
        .removeAttr('aria-hidden')
        .removeAttr('tabindex');
    });
  },

  applyLimit: function applyLimit($wrapper, $swatches, limit) {
    var totalCount = $swatches.length;
    var remainingCount = totalCount - limit;

    if (remainingCount <= 0) {
      return;
    }

    var productLink = this.getProductLink($wrapper);

    // Hide swatches beyond the limit and ensure visible ones stay accessible
    $swatches.each(function (index) {
      var $swatch = jQuery(this);

      if (index >= limit) {
        $swatch
          .hide()
          .addClass('om-variation-item--hidden')
          .attr('aria-hidden', 'true')
          .attr('tabindex', '-1');
      } else {
        $swatch
          .show()
          .removeClass('om-variation-item--hidden')
          .removeAttr('aria-hidden')
          .removeAttr('tabindex');
      }
    });

    var ariaLabel = this.getMoreVariationsLabel(remainingCount);

    // Add the "+X" link
    var $moreLink = jQuery('<a>', {
      'href': productLink,
      'class': 'om-more-variations',
      'aria-label': ariaLabel,
      'data-remaining-count': remainingCount
    }).text('+' + remainingCount);

    $wrapper.find('.om-variation-type-image').append($moreLink);
    $wrapper.data('js-swatch-limit', true);
  },

  getMoreVariationsLabel: function getMoreVariationsLabel(remainingCount) {
    var labelTemplate = 'View %d more variations';

    if (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.customizerOptions && omSwatchSettings.customizerOptions.moreVariationsLabel) {
      labelTemplate = omSwatchSettings.customizerOptions.moreVariationsLabel;
    }

    return labelTemplate.replace(/%1\$d|%d/, remainingCount);
  },

  getProductLink: function getProductLink($wrapper) {
    var productLink = '#';
    var $product = $wrapper.closest('.product, .wc-block-grid__product');

    if ($product.length) {
      var selectors = [
        'a.woocommerce-LoopProduct-link',
        'a.woocommerce-loop-product__link',
        'a.wc-block-grid__product-link',
        'a.product-loop-link'
      ];

      for (var i = 0; i < selectors.length; i++) {
        var $link = $product.find(selectors[i]).first();
        if ($link.length) {
          productLink = $link.attr('href');
          break;
        }
      }

      if (productLink === '#') {
        var $fallbackLink = $product.find('a[href]').first();
        if ($fallbackLink.length) {
          productLink = $fallbackLink.attr('href');
        }
      }
    }

    return productLink;
  },

  /**
   * Apply counter for server-side limited swatches
   * When PHP has already limited the swatches, we use the stored total to show "+XX more"
   */
  applyServerLimitCounter: function applyServerLimitCounter($wrapper, $visibleSwatches) {
    // Don't add counter if already exists
    if ($wrapper.find('.om-more-variations').length) {
      return;
    }

    var totalVariations = parseInt($wrapper.data('total-variations'), 10);
    var storedCount = $visibleSwatches.length;

    // Honour the per-device display limit (imageSwatchLimitShopArchive) even
    // when PHP cached MORE swatches than this breakpoint should show. Without
    // this, the absolute-positioned thumbnail column grew tall enough (5+ images
    // + the "+N" counter) to overflow out of the product image and into the
    // title. Capping the visible thumbnails keeps the column centred on the
    // image; the counter is recomputed against what is actually shown.
    var limit = this.getSwatchLimit();
    if (!limit || limit < 1) { limit = storedCount; }
    var shown = Math.min(storedCount, limit);

    if (isNaN(totalVariations) || totalVariations < storedCount) {
      totalVariations = storedCount;
    }

    // Hide stored swatches beyond the display limit (keep the first `shown`).
    $visibleSwatches.each(function (index) {
      var $s = jQuery(this);
      if (index >= shown) {
        $s.hide().addClass('om-variation-item--hidden').attr('aria-hidden', 'true').attr('tabindex', '-1');
      } else {
        $s.show().removeClass('om-variation-item--hidden').removeAttr('aria-hidden').removeAttr('tabindex');
      }
    });

    var remainingCount = totalVariations - shown;
    if (remainingCount <= 0) {
      return;
    }

    var productLink = this.getProductLink($wrapper);
    var ariaLabel = this.getMoreVariationsLabel(remainingCount);

    var $moreLink = jQuery('<a>', {
      'href': productLink,
      'class': 'om-more-variations',
      'aria-label': ariaLabel,
      'data-remaining-count': remainingCount
    }).text('+' + remainingCount);

    $wrapper.find('.om-variation-type-image').append($moreLink);
  }
};

/**
 * Multi-Select Mode Handler
 * Allows customers to select multiple variations and add them all to cart
 * Supports two display modes: 'grid' (standard swatches) and 'dropdown' (list with checkboxes)
 */
omProductSwatches.multiSelectHandler = {
  selectedVariations: [],
  $addButton: null,
  $form: null,
  $dropdownContainer: null,
  initialized: false,
  displayStyle: 'grid',
  showQuantity: false,
  userHasPendingChanges: false, // Flag to track if user has made changes not yet synced

  init: function init() {
    if (typeof omSwatchSettings === 'undefined') {
      return;
    }

    if (!omSwatchSettings.customizerOptions.multiSelectEnabled) {
      return;
    }

    // Only on single product pages
    if (!jQuery('body').hasClass('single-product')) {
      return;
    }

    var $multiSelectWrappers = jQuery('.om-variations-wrapper[data-multi-select="1"]');
    if (!$multiSelectWrappers.length) {
      return;
    }

    this.$form = jQuery('.variations_form').first();
    if (!this.$form.length) {
      return;
    }

    // Prevent double initialization
    if (this.initialized) {
      return;
    }
    this.initialized = true;

    // Add class to form for CSS targeting (hide reset_variations, variation price, etc.)
    this.$form.addClass('om-multi-select-enabled');

    // Get display style from data attribute (per-product setting) or fall back to global
    // Per-product settings take priority over global settings
    this.displayStyle = $multiSelectWrappers.first().data('display-style') ||
                        omSwatchSettings.customizerOptions.multiSelectDisplayStyle ||
                        'grid';

    // Get show quantity from data attribute (per-product setting)
    // data-show-quantity="1" means show, "0" means hide, undefined falls back to global
    var showQuantityAttr = $multiSelectWrappers.first().data('show-quantity');
    if (typeof showQuantityAttr !== 'undefined') {
      this.showQuantity = showQuantityAttr === 1 || showQuantityAttr === '1';
    } else {
      this.showQuantity = omSwatchSettings.customizerOptions.multiSelectShowQuantity || false;
    }

    if (this.displayStyle === 'dropdown') {
      this.initDropdownMode();
    } else {
      this.initGridMode();
    }
  },

  /**
   * Initialize Grid Mode (standard swatch display)
   */
  initGridMode: function initGridMode() {
    var self = this;
    this.createAddButton();
    this.bindEvents();
    this.hideDefaultAddToCart();

    // Populate from cart data after a short delay to ensure cartSync is ready
    setTimeout(function() {
      self.populateFromCart();
    }, 200);
  },

  /**
   * Initialize Dropdown Mode (list with checkboxes)
   */
  initDropdownMode: function initDropdownMode() {
    this.createDropdownUI();
    this.createAddButton();
    this.bindDropdownEvents();
    this.hideDefaultAddToCart();

    // Hide original swatch wrappers
    jQuery('.om-variations-wrapper[data-multi-select="1"]').hide();
  },

  /**
   * Create the dropdown UI
   */
  createDropdownUI: function createDropdownUI() {
    var self = this;

    // Create dropdown container
    this.$dropdownContainer = jQuery('<div>', {
      'class': 'om-multi-select-dropdown'
    });

    // Create header
    var $header = jQuery('<div>', {
      'class': 'om-multi-select-dropdown-header'
    });

    var i18n = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n : {};
    var $headerText = jQuery('<span>', {
      'class': 'om-multi-select-dropdown-title',
      'text': i18n.selectVariations || 'Select variations'
    });

    var $toggleIcon = jQuery('<span>', {
      'class': 'om-multi-select-dropdown-toggle',
      'html': '&#9660;' // Down arrow
    });

    $header.append($headerText).append($toggleIcon);

    // Create dropdown list
    var $list = jQuery('<div>', {
      'class': 'om-multi-select-dropdown-list'
    });

    // Add loading indicator
    var loadingText = i18n.loadingVariations || 'Loading variations...';
    $list.html('<div class="om-multi-select-loading">' + loadingText + '</div>');

    this.$dropdownContainer.append($header);
    this.$dropdownContainer.append($list);

    // Insert before variations table
    var $insertBefore = this.$form.find('.variations').first();
    if ($insertBefore.length) {
      $insertBefore.before(this.$dropdownContainer);
    } else {
      this.$form.prepend(this.$dropdownContainer);
    }

    // Fetch ALL variations via AJAX (including out-of-stock)
    var productId = this.$form.data('product_id');
    if (!productId) {
      // Fallback to form variations if no product ID
      var variations = this.$form.data('product_variations');
      if (variations && Array.isArray(variations)) {
        self.populateDropdownList($list, variations);
      }
      return;
    }

    jQuery.ajax({
      url: omSwatchSettings.ajax_url,
      method: 'POST',
      data: {
        action: 'aieo_swatch_get_all_variations',
        product_id: productId,
        include_out_of_stock: 'true',
        security: omSwatchSettings.security
      },
      success: function(response) {
        if (response.success && response.data && Array.isArray(response.data)) {
          self.populateDropdownList($list, response.data);
        } else {
          // Fallback to form variations
          var variations = self.$form.data('product_variations');
          if (variations && Array.isArray(variations)) {
            self.populateDropdownList($list, variations);
          } else {
            var noVarText = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n.noVariations : 'No variations available';
            $list.html('<div class="om-multi-select-error">' + (noVarText || 'No variations available') + '</div>');
          }
        }
      },
      error: function() {
        // Fallback to form variations
        var variations = self.$form.data('product_variations');
        if (variations && Array.isArray(variations)) {
          self.populateDropdownList($list, variations);
        } else {
          var failedText = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n.failedToLoad : 'Failed to load variations';
          $list.html('<div class="om-multi-select-error">' + (failedText || 'Failed to load variations') + '</div>');
        }
      }
    });
  },

  /**
   * Populate dropdown list with variations
   * @param {jQuery} $list - The dropdown list container
   * @param {Array} variations - Array of variation data
   */
  populateDropdownList: function populateDropdownList($list, variations) {
    var self = this;
    $list.empty();

    variations.forEach(function(variation) {
      var isOutOfStock = self.isVariationOutOfStock(variation);
      var $item = self.createDropdownItem(variation, isOutOfStock);
      $list.append($item);
    });

    // After populating the list, populate selections from cart data
    // Use a small delay to ensure DOM is ready
    setTimeout(function() {
      self.populateFromCart();
    }, 100);
  },

  /**
   * Check if a variation is out of stock
   * Uses multiple methods: WooCommerce variation data AND DOM swatch elements
   */
  isVariationOutOfStock: function isVariationOutOfStock(variation) {
    var variationId = variation.variation_id;
    var attributes = variation.attributes;

    // Method 1: Check WooCommerce variation data properties
    if (variation.is_in_stock === false) {
      return true;
    }

    if (variation.is_purchasable === false) {
      return true;
    }

    if (variation.stock_status === 'outofstock') {
      return true;
    }

    if (variation.availability_html) {
      var availText = variation.availability_html.toLowerCase();
      if (availText.indexOf('out-of-stock') !== -1 || availText.indexOf('outofstock') !== -1) {
        return true;
      }
    }

    if (variation.max_qty === 0) {
      return true;
    }

    if (variation.backorders_allowed === false && variation.stock_quantity !== null && variation.stock_quantity <= 0) {
      return true;
    }

    // Method 2: Check DOM swatch elements (PHP computes this correctly)
    // Check each attribute term's swatch element for out-of-stock status
    for (var attrKey in attributes) {
      var attrValue = attributes[attrKey];
      if (!attrValue) continue;

      // Find the swatch element for this attribute value
      // Look in the form's variations wrappers
      var $swatch = this.$form.find('.om-variation-item[value="' + attrValue + '"]').first();

      if ($swatch.length) {
        // Check if this swatch is marked as out-of-stock by PHP
        if ($swatch.hasClass('permanent-disabled')) {
          return true;
        }

        // Also check the title attribute for "Out of stock"
        var title = $swatch.attr('title') || '';
        if (title.toLowerCase().indexOf('out of stock') !== -1) {
          return true;
        }
      }
    }

    return false;
  },

  /**
   * Create a single dropdown item
   * @param {Object} variation - WooCommerce variation data
   * @param {boolean} isOutOfStock - Whether this variation is out of stock
   */
  createDropdownItem: function createDropdownItem(variation, isOutOfStock) {
    var self = this;
    var variationId = variation.variation_id;
    var attributes = variation.attributes;
    var price = variation.display_price;
    var priceHtml = variation.price_html;
    var showQuantity = this.showQuantity;

    // Build variation name from attributes - get human-readable names instead of slugs
    var variationName = [];
    for (var attrKey in attributes) {
      var attrValue = attributes[attrKey];
      if (attrValue) {
        // Try to get the human-readable name from multiple sources
        var readableName = this.getReadableAttributeName(attrKey, attrValue);
        variationName.push(readableName);
      }
    }
    var displayName = variationName.join(' / ');

    // Create item with out-of-stock class if applicable
    var itemClass = 'om-multi-select-dropdown-item';
    if (isOutOfStock) {
      itemClass += ' om-out-of-stock';
    }
    if (showQuantity) {
      itemClass += ' om-has-quantity';
    }

    var $item = jQuery('<div>', {
      'class': itemClass,
      'data-variation-id': variationId,
      'data-price': price
    });

    // Add title tooltip for out-of-stock items
    if (isOutOfStock) {
      $item.attr('title', 'This variation is out of stock and cannot be selected');
    }

    // Checkbox - disabled if out of stock
    var $checkbox = jQuery('<input>', {
      'type': 'checkbox',
      'class': 'om-multi-select-checkbox',
      'id': 'om-variation-' + variationId,
      'value': variationId
    });

    if (isOutOfStock) {
      $checkbox.prop('disabled', true);
    }

    // Left side: name and price
    var $leftSide = jQuery('<div>', {
      'class': 'om-multi-select-item-left'
    });

    var $label = jQuery('<label>', {
      'for': 'om-variation-' + variationId,
      'class': 'om-multi-select-item-label',
      'text': displayName
    });

    // Price or out-of-stock message
    var $priceSpan = jQuery('<span>', {
      'class': 'om-multi-select-item-price'
    });

    if (isOutOfStock) {
      $priceSpan.html('<span class="om-stock-status om-out-of-stock-text">Out of stock</span>');
    } else if (priceHtml) {
      $priceSpan.html(priceHtml);
    } else if (price) {
      // Fallback: format price if price_html is not available
      $priceSpan.html('<span class="woocommerce-Price-amount amount">' + this.formatPrice(parseFloat(price)) + '</span>');
    }

    $leftSide.append($label).append($priceSpan);

    // Center: Quantity input (if enabled)
    var $centerSection = null;
    if (showQuantity && !isOutOfStock) {
      $centerSection = jQuery('<div>', {
        'class': 'om-multi-select-item-quantity'
      });

      var $qtyWrapper = jQuery('<div>', {
        'class': 'om-qty-wrapper'
      });

      var $minusBtn = jQuery('<button>', {
        'type': 'button',
        'class': 'om-qty-btn om-qty-minus',
        'text': '−',
        'tabindex': '-1'
      });

      var $qtyInput = jQuery('<input>', {
        'type': 'number',
        'class': 'om-qty-input',
        'value': '1',
        'min': '0',
        'max': variation.max_qty || '9999',
        'data-variation-id': variationId
      });

      var $plusBtn = jQuery('<button>', {
        'type': 'button',
        'class': 'om-qty-btn om-qty-plus',
        'text': '+',
        'tabindex': '-1'
      });

      $qtyWrapper.append($minusBtn).append($qtyInput).append($plusBtn);
      $centerSection.append($qtyWrapper);
    }

    // Right side: swatch preview
    var $rightSide = jQuery('<div>', {
      'class': 'om-multi-select-item-right'
    });

    // Try to get swatch preview from existing swatches
    var $swatchPreview = this.getSwatchPreview(variation);
    if ($swatchPreview) {
      $rightSide.append($swatchPreview);
    }

    $item.append($checkbox);
    $item.append($leftSide);
    if ($centerSection) {
      $item.append($centerSection);
    }
    $item.append($rightSide);

    return $item;
  },

  /**
   * Get human-readable attribute name from slug
   * Checks: 1) Select option text, 2) Swatch tooltip, 3) Swatch data-original-text, 4) Fallback to slug cleanup
   */
  getReadableAttributeName: function getReadableAttributeName(attrKey, attrSlug) {
    // Method 1: Try to get from the hidden select dropdown option text
    if (this.$form && this.$form.length) {
      var selectName = attrKey.replace('attribute_', '');
      var $select = this.$form.find('select[name="' + attrKey + '"], select[data-attribute_name="' + attrKey + '"], select[name="attribute_' + selectName + '"]');

      if ($select.length) {
        var $option = $select.find('option[value="' + attrSlug + '"]');
        if ($option.length && $option.text()) {
          return $option.text();
        }
      }
    }

    // Method 2: Try to get from the swatch tooltip (data-original-tooltip or tooltip text)
    var $swatch = jQuery('.om-variations-wrapper[data-multi-select="1"] .om-variation-item[value="' + attrSlug + '"]').first();
    if ($swatch.length) {
      // Check tooltip
      var $tooltip = $swatch.find('.om-variation-tooltip');
      if ($tooltip.length) {
        var tooltipText = $tooltip.attr('data-original-tooltip') || $tooltip.text();
        if (tooltipText && tooltipText.trim()) {
          return tooltipText.trim();
        }
      }

      // Check data-original-text attribute
      var originalText = $swatch.attr('data-original-text');
      if (originalText && originalText.trim()) {
        return originalText.trim();
      }

      // Check .om-variation-text element
      var $textEl = $swatch.find('.om-variation-text');
      if ($textEl.length) {
        var textContent = $textEl.attr('data-original-text') || $textEl.text();
        if (textContent && textContent.trim()) {
          return textContent.trim();
        }
      }
    }

    // Method 3: Fallback - clean up slug (replace hyphens with spaces, capitalize)
    return attrSlug.replace(/-/g, ' ');
  },

  /**
   * Get swatch preview for a variation
   */
  getSwatchPreview: function getSwatchPreview(variation) {
    var attributes = variation.attributes;
    var $preview = jQuery('<div>', {
      'class': 'om-multi-select-swatch-preview'
    });

    for (var attrKey in attributes) {
      var attrValue = attributes[attrKey];
      if (!attrValue) continue;

      // Find the original swatch for this attribute value
      var $originalSwatch = jQuery('.om-variations-wrapper[data-multi-select="1"] .om-variation-item[value="' + attrValue + '"]').first();

      if ($originalSwatch.length) {
        // Clone the swatch visual (color/image only)
        var $swatchText = $originalSwatch.find('.om-variation-text').clone();
        if ($swatchText.length) {
          $swatchText.removeClass().addClass('om-multi-select-swatch-mini');
          // Remove tooltip elements
          $swatchText.find('.om-variation-tooltip').remove();
          $preview.append($swatchText);
        }
      }
    }

    return $preview.children().length ? $preview : null;
  },

  /**
   * Bind events for dropdown mode
   */
  bindDropdownEvents: function bindDropdownEvents() {
    var self = this;

    // Toggle dropdown open/close
    this.$dropdownContainer.on('click', '.om-multi-select-dropdown-header', function(e) {
      e.stopPropagation();
      self.$dropdownContainer.toggleClass('open');
    });

    // Checkbox change
    this.$dropdownContainer.on('change', '.om-multi-select-checkbox', function(e) {
      var $checkbox = jQuery(this);
      var variationId = parseInt($checkbox.val(), 10);
      var $item = $checkbox.closest('.om-multi-select-dropdown-item');

      if ($checkbox.is(':checked')) {
        $item.addClass('selected');
        // Get quantity from input if exists
        var qty = 1;
        var $qtyInput = $item.find('.om-qty-input');
        if ($qtyInput.length) {
          qty = parseInt($qtyInput.val(), 10) || 1;
        }
        self.addVariationById(variationId, qty);
      } else {
        $item.removeClass('selected');
        self.removeVariationById(variationId);
      }

      self.updateUI();
    });

    // Quantity input change
    this.$dropdownContainer.on('change input', '.om-qty-input', function(e) {
      var $input = jQuery(this);
      var $item = $input.closest('.om-multi-select-dropdown-item');
      var $checkbox = $item.find('.om-multi-select-checkbox');
      var variationId = parseInt($input.data('variation-id'), 10);
      var qty = parseInt($input.val(), 10) || 0;

      // Handle quantity of 0 - uncheck and remove
      if (qty <= 0) {
        qty = 1; // Reset to default unchecked state
        $input.val(1);
        if ($checkbox.is(':checked')) {
          $checkbox.prop('checked', false);
          $item.removeClass('selected');
          self.removeVariationById(variationId);
          self.updateUI();
        }
        return;
      }

      // Auto-check when quantity > 1 (default is 1 unchecked)
      if (qty > 1 && !$checkbox.is(':checked')) {
        $checkbox.prop('checked', true);
        $item.addClass('selected');
        self.addVariationById(variationId, qty);
        self.updateUI();
      } else if ($checkbox.is(':checked')) {
        // Update quantity for already checked item
        self.updateVariationQuantity(variationId, qty);
        self.updateUI();
      }
    });

    // Quantity minus button
    this.$dropdownContainer.on('click', '.om-qty-minus', function(e) {
      e.preventDefault();
      e.stopPropagation();
      var $btn = jQuery(this);
      var $input = $btn.siblings('.om-qty-input');
      var $item = $input.closest('.om-multi-select-dropdown-item');
      var $checkbox = $item.find('.om-multi-select-checkbox');
      var currentVal = parseInt($input.val(), 10) || 1;

      if ($checkbox.is(':checked')) {
        // If checked, allow going down to 1 (min qty when selected)
        // Going below 1 will uncheck
        if (currentVal > 1) {
          $input.val(currentVal - 1).trigger('change');
        } else {
          // Going from 1 to "uncheck" - reset to 1 unchecked
          $checkbox.prop('checked', false);
          $item.removeClass('selected');
          var variationId = parseInt($input.data('variation-id'), 10);
          self.removeVariationById(variationId);
          self.updateUI();
        }
      }
      // If not checked and value is 1 (default), minus does nothing
    });

    // Quantity plus button
    this.$dropdownContainer.on('click', '.om-qty-plus', function(e) {
      e.preventDefault();
      e.stopPropagation();
      var $btn = jQuery(this);
      var $input = $btn.siblings('.om-qty-input');
      var currentVal = parseInt($input.val(), 10) || 0;
      var maxVal = parseInt($input.attr('max'), 10) || 9999;
      if (currentVal < maxVal) {
        $input.val(currentVal + 1).trigger('change');
      }
    });

    // Prevent dropdown from closing when clicking on quantity controls
    this.$dropdownContainer.on('click', '.om-multi-select-item-quantity', function(e) {
      e.stopPropagation();
    });

    // Prevent closing when clicking inside list
    this.$dropdownContainer.on('click', '.om-multi-select-dropdown-list', function(e) {
      e.stopPropagation();
    });

    // Close dropdown when clicking outside
    jQuery(document).on('click.omMultiSelectDropdown', function(e) {
      if (!self.$dropdownContainer.is(e.target) && self.$dropdownContainer.has(e.target).length === 0) {
        self.$dropdownContainer.removeClass('open');
      }
    });

    // Add button click - use syncCart for full cart sync functionality
    this.$addButton.on('click', function(e) {
      e.preventDefault();
      if (!jQuery(this).hasClass('disabled')) {
        self.syncCart();
      }
    });

    // Listen for cart data refresh to repopulate selections
    // BUT only if user hasn't made changes that haven't been synced yet
    jQuery(document.body).on('om_cart_data_refreshed', function() {
      if (!self.userHasPendingChanges) {
        self.populateFromCart();
      }
    });

    // Clear selections on form reset
    this.$form.on('reset_data', function() {
      self.clearSelections();
    });
  },

  /**
   * Add variation by ID (for dropdown mode)
   * @param {number} variationId - Variation ID
   * @param {number} quantity - Quantity to add (default 1)
   */
  addVariationById: function addVariationById(variationId, quantity) {
    if (this.isVariationSelected(variationId)) {
      return;
    }

    var qty = quantity || 1;
    var cartItemKey = '';

    // Check if this variation is in the cart - preserve its cart_item_key
    var productId = this.$form.data('product_id');
    var cartSyncEnabled = typeof omProductSwatches !== 'undefined' &&
                          omProductSwatches.cartSync &&
                          omProductSwatches.cartSync.enabled !== false;

    if (cartSyncEnabled && productId) {
      var cartData = omProductSwatches.cartSync.cartData[productId] || {};
      var cartItem = cartData[variationId];
      if (cartItem && cartItem.cart_item_key) {
        cartItemKey = cartItem.cart_item_key;
        // If item is in cart, use cart quantity as default (unless user specified different)
        if (!quantity && cartItem.qty > 0) {
          qty = cartItem.qty;
        }
      }
    }

    var variations = this.$form.data('product_variations');
    var variation = null;

    for (var i = 0; i < variations.length; i++) {
      if (variations[i].variation_id === variationId) {
        variation = variations[i];
        break;
      }
    }

    if (variation) {
      var variationName = [];
      for (var attrKey in variation.attributes) {
        if (variation.attributes[attrKey]) {
          // Use readable name instead of slug
          var readableName = this.getReadableAttributeName(attrKey, variation.attributes[attrKey]);
          variationName.push(readableName);
        }
      }

      this.selectedVariations.push({
        variation_id: variationId,
        quantity: qty,
        label: variationName.join(' / '),
        cart_item_key: cartItemKey
      });

      // Mark that user has pending changes
      this.userHasPendingChanges = true;
    }
  },

  /**
   * Update quantity for a selected variation
   * @param {number} variationId - Variation ID
   * @param {number} quantity - New quantity
   */
  updateVariationQuantity: function updateVariationQuantity(variationId, quantity) {
    for (var i = 0; i < this.selectedVariations.length; i++) {
      if (this.selectedVariations[i].variation_id === variationId) {
        this.selectedVariations[i].quantity = quantity;
        // Mark that user has pending changes
        this.userHasPendingChanges = true;
        break;
      }
    }
  },

  /**
   * Remove variation by ID (for dropdown mode)
   */
  removeVariationById: function removeVariationById(variationId) {
    this.selectedVariations = this.selectedVariations.filter(function(item) {
      return item.variation_id !== variationId;
    });
    // Mark that user has pending changes
    this.userHasPendingChanges = true;
  },

  createAddButton: function createAddButton() {
    var self = this;
    var buttonText = omSwatchSettings.customizerOptions.multiSelectButtonText || 'Add Selected to Cart';
    var buttonPosition = omSwatchSettings.customizerOptions.multiSelectButtonPosition || 'above';

    // Surface-scoped namespacing: when the form lives inside an
    // archive-card wrapper, also tag the button with the new
    // aieo-archive-add-to-cart classes so CSS/analytics can target
    // archive cart-adds independently from the PDP submit.
    var $btnOmRoot   = self.$form && self.$form.closest ? self.$form.closest('.om-product-swatches') : null;
    var btnIsArchive = !!($btnOmRoot && $btnOmRoot.length && $btnOmRoot.hasClass('om-product-swatches--archive'));
    var btnClasses   = 'om-multi-select-add-to-cart button alt disabled has-background';
    if (btnIsArchive) {
      btnClasses += ' aieo-archive-add-to-cart aieo-archive-add-to-cart--swatches';
    }

    this.$addButton = jQuery('<button>', {
      'type': 'button',
      'class': btnClasses,
      'disabled': true,
      'text': buttonText
    });

    // Create reset link
    var i18n = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n : {};
    var $resetLink = jQuery('<a>', {
      'href': '#',
      'class': 'om-multi-select-reset',
      'text': i18n.clearSelections || 'Clear selections'
    });

    // Bind reset click handler
    $resetLink.on('click', function(e) {
      e.preventDefault();
      self.clearSelections();
      // Also trigger WooCommerce reset
      self.$form.find('.reset_variations').trigger('click');
    });

    // Create button row container (button + reset link side by side)
    var $buttonRow = jQuery('<div>', {
      'class': 'om-multi-select-button-row'
    });
    $buttonRow.append(this.$addButton);
    $buttonRow.append($resetLink);

    // Summary will be populated by updateUI() - show prompt message initially
    var $summary = jQuery('<div>', {
      'class': 'om-multi-select-summary'
    });
    var summaryI18n = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n : {};
    // Show prompt message in initial state
    var promptMessage = summaryI18n.promptMessage || 'Select the variations you want to add to your cart';
    var initialHtml = '<div class="om-multi-select-prompt">' + promptMessage + '</div>';
    $summary.html(initialHtml);

    // Check button position setting
    if (buttonPosition === 'below') {
      // Create separate containers: message above, button below
      this.$messageContainer = jQuery('<div>', {
        'class': 'om-multi-select-container om-multi-select-message-container'
      });
      this.$messageContainer.append($summary);

      this.$buttonContainer = jQuery('<div>', {
        'class': 'om-multi-select-container om-multi-select-button-container'
      });
      this.$buttonContainer.append($buttonRow);

      // Insert message container BEFORE the variations/dropdown
      if (this.displayStyle === 'dropdown' && this.$dropdownContainer) {
        this.$dropdownContainer.before(this.$messageContainer);
        // Insert button container AFTER the dropdown
        this.$dropdownContainer.after(this.$buttonContainer);
      } else {
        var $variationsTable = this.$form.find('.variations').first();
        if ($variationsTable.length) {
          $variationsTable.before(this.$messageContainer);
          // Insert button after variations table
          $variationsTable.after(this.$buttonContainer);
        } else {
          this.$form.prepend(this.$messageContainer);
          this.$form.append(this.$buttonContainer);
        }
      }

      // Store flag for split mode
      this.buttonPositionBelow = true;
    } else {
      // Default behavior: everything above (original logic)
      this.$mainContainer = jQuery('<div>', {
        'class': 'om-multi-select-container'
      });
      this.$mainContainer.append($summary);
      this.$mainContainer.append($buttonRow);

      // Insert BEFORE the variations table or dropdown so selections are visible while selecting
      if (this.displayStyle === 'dropdown' && this.$dropdownContainer) {
        this.$dropdownContainer.before(this.$mainContainer);
      } else {
        var $insertBefore = this.$form.find('.variations').first();
        if ($insertBefore.length) {
          $insertBefore.before(this.$mainContainer);
        } else {
          this.$form.prepend(this.$mainContainer);
        }
      }

      this.buttonPositionBelow = false;
    }
  },

  hideDefaultAddToCart: function hideDefaultAddToCart() {
    // Hide the default single_add_to_cart_button
    this.$form.find('.single_add_to_cart_button').hide();
    this.$form.find('.woocommerce-variation-add-to-cart').hide();
    // Hide quantity selector
    this.$form.find('.quantity').hide();
    // Hide WooCommerce reset variations link (we have our own)
    this.$form.find('.reset_variations').hide();
    // Hide the single variation price display (confusing in multi-select mode)
    this.$form.find('.woocommerce-variation-price').hide();
    this.$form.find('.single_variation_wrap .woocommerce-variation').hide();
  },

  bindEvents: function bindEvents() {
    var self = this;

    // Override default swatch click behavior for multi-select mode
    jQuery(document).off('click.omMultiSelect').on('click.omMultiSelect', '.om-variations-wrapper[data-multi-select="1"] .om-variation-item', function(e) {
      e.preventDefault();
      e.stopImmediatePropagation();

      var $swatch = jQuery(this);

      // Don't allow selecting disabled/out-of-stock items
      if ($swatch.hasClass('disabled') || $swatch.hasClass('permanent-disabled')) {
        return false;
      }

      // Toggle selection
      if ($swatch.hasClass('om-multi-selected')) {
        $swatch.removeClass('om-multi-selected active');
        // Remove checkmark element
        $swatch.find('.om-multi-select-checkmark').remove();
        self.removeVariation($swatch);
      } else {
        $swatch.addClass('om-multi-selected active');
        // Add checkmark element
        if (!$swatch.find('.om-multi-select-checkmark').length) {
          $swatch.append('<span class="om-multi-select-checkmark">&#10003;</span>');
        }
        self.addVariation($swatch);
      }

      self.updateUI();
      return false;
    });

    // Add button click - use syncCart for full cart sync functionality
    this.$addButton.on('click', function(e) {
      e.preventDefault();
      if (!jQuery(this).hasClass('disabled')) {
        self.syncCart();
      }
    });

    // Listen for cart data refresh to repopulate selections
    // BUT only if user hasn't made changes that haven't been synced yet
    jQuery(document.body).on('om_cart_data_refreshed', function() {
      if (!self.userHasPendingChanges) {
        self.populateFromCart();
      }
    });

    // Clear selections on form reset
    this.$form.on('reset_data', function() {
      self.clearSelections();
    });
  },

  addVariation: function addVariation($swatch) {
    var variationId = this.getVariationIdForSwatch($swatch);

    if (variationId && !this.isVariationSelected(variationId)) {
      // Check if this variation is in the cart - if so, preserve its quantity and cart_item_key
      var productId = this.$form.data('product_id');
      var cartSyncEnabled = typeof omProductSwatches !== 'undefined' &&
                            omProductSwatches.cartSync &&
                            omProductSwatches.cartSync.enabled !== false;

      var quantity = 1;
      var cartItemKey = '';

      if (cartSyncEnabled && productId) {
        var cartData = omProductSwatches.cartSync.cartData[productId] || {};
        var cartItem = cartData[variationId];
        if (cartItem && cartItem.qty > 0) {
          quantity = cartItem.qty;
          cartItemKey = cartItem.cart_item_key || '';
        }
      }

      this.selectedVariations.push({
        variation_id: variationId,
        quantity: quantity,
        $swatch: $swatch,
        label: this.getSwatchLabel($swatch),
        cart_item_key: cartItemKey
      });

      // Mark that user has pending changes
      this.userHasPendingChanges = true;
    }
  },

  removeVariation: function removeVariation($swatch) {
    var variationId = this.getVariationIdForSwatch($swatch);

    this.selectedVariations = this.selectedVariations.filter(function(item) {
      return item.variation_id !== variationId;
    });

    // Mark that user has pending changes
    this.userHasPendingChanges = true;
  },

  isVariationSelected: function isVariationSelected(variationId) {
    return this.selectedVariations.some(function(item) {
      return item.variation_id === variationId;
    });
  },

  getVariationIdForSwatch: function getVariationIdForSwatch($swatch) {
    var value = $swatch.attr('value');
    var $wrapper = $swatch.closest('.om-variations-wrapper');
    var attributeName = $wrapper.find('select').attr('name');

    // Get variations data from form
    var variations = this.$form.data('product_variations');

    if (!variations || !Array.isArray(variations)) {
      return null;
    }

    // Find matching variation
    for (var i = 0; i < variations.length; i++) {
      var variation = variations[i];
      var attributes = variation.attributes;

      for (var attrKey in attributes) {
        // Match by attribute value
        if (attributes[attrKey] === value || attributes[attrKey] === '') {
          // If attribute is empty string, it matches any value - we need more specific match
          if (attributes[attrKey] === value) {
            return variation.variation_id;
          }
        }
      }
    }

    return null;
  },

  getSwatchLabel: function getSwatchLabel($swatch) {
    // Prefer the `data-original-text` attribute the server emits on every
    // swatch (color, button, AND image variants). This is the only path
    // that yields a real label for IMAGE swatches — the inner
    // `.om-variation-text` element on those holds just an `<img>` with
    // no text content, so the previous `$text.text()` fallback returned
    // an empty string and the multi-select selection summary rendered
    // blank when image swatches were picked. Reported on igora-vafi.
    var orig = ($swatch.attr('data-original-text') || '').trim();
    if (orig) { return orig; }

    // Native `title` attribute — always present now (we emit it for every
    // swatch state, including OOS). Strip the OOS suffix if present so the
    // selection summary doesn't include "- Out of stock" mid-line.
    var title = ($swatch.attr('title') || '').trim();
    if (title) {
      return title.replace(/\s*[-–]\s*[^-–]*[Ss]tock[^-–]*$/i, '').trim();
    }

    var $tooltip = $swatch.find('.om-variation-tooltip');
    if ($tooltip.length) {
      return $tooltip.attr('data-original-tooltip') || $tooltip.text();
    }

    var $text = $swatch.find('.om-variation-text');
    if ($text.length) {
      var t = $text.text().trim();
      if (t) { return t; }
    }

    return $swatch.attr('value') || '';
  },

  updateUI: function updateUI() {
    // Use the correct container reference based on button position
    var $container = this.buttonPositionBelow ? this.$messageContainer : this.$mainContainer;

    // Skip updateUI if we're in success state - let the success message timeout handle the transition
    if ($container.hasClass('om-success-state')) {
      return;
    }

    var $summary = $container.find('.om-multi-select-summary');

    // Split selections by "already in cart" vs "newly picked this session".
    // The bottom cart-summary box (om-cart-summary, rendered by
    // applyCartIndicators) shows items already in cart — so the TOP
    // summary should show only NEW picks to avoid double-display. But
    // if the shopper has BOTH cart items AND new picks, we previously
    // wiped the top summary entirely, leaving them with no list of
    // their new picks. Now: render the top summary for new picks only,
    // and hide it only when every selection is already a cart item.
    var newSelections = this.selectedVariations.filter(function(v) {
      return !v.cart_item_key || v.cart_item_key === '';
    });
    var count = newSelections.length;
    var totalSelected = this.selectedVariations.length;

    if (count === 0 && totalSelected > 0) {
      // Every selection is already a cart item — bottom summary covers
      // them, so wipe the top summary.
      if ($summary.length) {
        $summary.empty();
      }
      if (this.buttonPositionBelow && this.$messageContainer) {
        this.$messageContainer.hide();
      }
      this.$addButton.removeClass('disabled').prop('disabled', false);
      return;
    }

    // Show the message container (in case it was hidden)
    if (this.buttonPositionBelow && this.$messageContainer) {
      this.$messageContainer.show();
    }

    // No items in cart - show the selection summary for first-time additions
    // Create summary container if it doesn't exist
    if (!$summary.length) {
      $summary = jQuery('<div class="om-multi-select-summary"></div>');
      $container.prepend($summary);
    }

    var i18n = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n : {};

    if (count === 0) {
      // No selections - show prompt message
      var promptMessage = i18n.promptMessage || 'Select the variations you want to add to your cart';
      $summary.html('<div class="om-multi-select-prompt">' + promptMessage + '</div>');
      this.$addButton.addClass('disabled').prop('disabled', true);
      return;
    }

    // Get variation prices from form data
    var variations = this.$form.data('product_variations');
    var subtotal = 0;
    var totalItems = 0;
    var selectedNames = [];
    var showQuantity = this.showQuantity;

    // Iterate the NEW-picks subset (not all selectedVariations) so the
    // top summary list reflects exactly what the shopper just added,
    // separate from the bottom cart-summary which already lists items
    // already in cart from prior sessions.
    newSelections.forEach(function(item) {
      var qty = parseInt(item.quantity, 10) || 1;
      if (item.label) {
        selectedNames.push(qty + '× ' + item.label);
      }
      totalItems += qty;

      if (variations && Array.isArray(variations)) {
        var itemVariationId = parseInt(item.variation_id, 10);
        for (var i = 0; i < variations.length; i++) {
          if (parseInt(variations[i].variation_id, 10) === itemVariationId) {
            subtotal += (parseFloat(variations[i].display_price) || 0) * qty;
            break;
          }
        }
      }
    });

    var formattedSubtotal = this.formatPrice(subtotal);
    var variationWord = count !== 1 ? (i18n.variations || 'variations') : (i18n.variation || 'variation');
    var countText = count + ' ' + variationWord;
    if (showQuantity && totalItems > count) {
      countText += ' (' + totalItems + ' ' + (i18n.items || 'items') + ')';
    }

    var summaryHtml = '<div class="om-multi-select-count">' + countText + '</div>';
    summaryHtml += '<div class="om-multi-select-names">' + selectedNames.join(', ') + '</div>';
    summaryHtml += '<div class="om-multi-select-subtotal">' + (i18n.subtotal || 'Subtotal') + ': <strong>' + formattedSubtotal + '</strong></div>';

    $summary.html(summaryHtml);
    this.$addButton.removeClass('disabled').prop('disabled', false);
  },

  /**
   * Format price with currency symbol
   */
  formatPrice: function formatPrice(price) {
    // Get currency settings from WooCommerce
    var currencySymbol = '€';
    var currencyPosition = 'right_space'; // Default for Euro
    var decimalSeparator = ',';
    var thousandSeparator = '.';
    var decimals = 2;

    // Try to get from WooCommerce settings
    if (typeof wc_add_to_cart_params !== 'undefined' && wc_add_to_cart_params.currency_symbol) {
      currencySymbol = wc_add_to_cart_params.currency_symbol;
    } else if (typeof woocommerce_params !== 'undefined' && woocommerce_params.currency_symbol) {
      currencySymbol = woocommerce_params.currency_symbol;
    }

    // Format number
    var formattedNumber = price.toFixed(decimals).replace('.', decimalSeparator);

    // Add thousand separator
    var parts = formattedNumber.split(decimalSeparator);
    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator);
    formattedNumber = parts.join(decimalSeparator);

    // Position currency symbol (Euro typically goes after with space)
    if (currencySymbol === '€' || currencyPosition === 'right_space') {
      return formattedNumber + ' ' + currencySymbol;
    } else if (currencyPosition === 'right') {
      return formattedNumber + currencySymbol;
    } else if (currencyPosition === 'left_space') {
      return currencySymbol + ' ' + formattedNumber;
    } else {
      return currencySymbol + formattedNumber;
    }
  },

  clearSelections: function clearSelections(skipUpdateUI) {
    if (this.displayStyle === 'dropdown') {
      // Clear dropdown checkboxes
      if (this.$dropdownContainer) {
        this.$dropdownContainer.find('.om-multi-select-checkbox').prop('checked', false);
        this.$dropdownContainer.find('.om-multi-select-dropdown-item').removeClass('selected');
        // Reset quantity inputs to 1 (default unchecked state)
        this.$dropdownContainer.find('.om-qty-input').val(1);
      }
    } else {
      // Remove visual selection from all swatches (grid mode)
      // Keep om-in-cart class for items already in cart!
      jQuery('.om-variations-wrapper[data-multi-select="1"] .om-variation-item').removeClass('om-multi-selected active');
      // Remove all checkmark elements
      jQuery('.om-variations-wrapper[data-multi-select="1"] .om-multi-select-checkmark').remove();
    }

    this.selectedVariations = [];
    if (!skipUpdateUI) {
      this.updateUI();
    }
  },

  /**
   * Mark swatches as "in cart" after successful batch add
   * This gives immediate visual feedback with green border
   */
  markAddedSwatchesInCart: function markAddedSwatchesInCart(variationsData) {
    var self = this;

    if (this.displayStyle === 'dropdown') {
      // For dropdown mode, mark items by variation ID
      // Find container - try multiple selectors
      var $container = this.$dropdownContainer;
      if (!$container || !$container.length) {
        $container = this.$form.find('.om-multi-select-items-list');
      }
      if (!$container || !$container.length) {
        $container = this.$form;
      }

      variationsData.forEach(function(item) {
        var $item = $container.find('[data-variation-id="' + item.variation_id + '"]');

        if ($item.length) {
          $item.addClass('om-in-cart');
        }
      });
    } else {
      // For grid mode, we have $swatch references in selectedVariations
      this.selectedVariations.forEach(function(selection) {
        if (selection.$swatch) {
          selection.$swatch.addClass('om-in-cart');
          // Add cart badge with quantity
          var $badge = selection.$swatch.find('.om-cart-badge');
          if (!$badge.length) {
            $badge = jQuery('<span class="om-cart-badge"></span>');
            selection.$swatch.append($badge);
          }
          $badge.text(selection.quantity).attr('title', selection.quantity + ' in cart');
        }
      });
    }
  },

  /**
   * Populate selections from cart data
   * Called on init and when cart data is refreshed
   */
  populateFromCart: function populateFromCart() {
    var self = this;

    // Get product ID from form
    var productId = this.$form.data('product_id');
    if (!productId) {
      return;
    }

    // Get cart data for this product
    var cartSyncEnabled = typeof omProductSwatches !== 'undefined' &&
                          omProductSwatches.cartSync &&
                          omProductSwatches.cartSync.enabled !== false;

    if (!cartSyncEnabled) {
      return;
    }

    var cartData = omProductSwatches.cartSync.cartData[productId] || {};

    // Clear current selections first
    this.selectedVariations = [];

    if (this.displayStyle === 'dropdown') {
      // For dropdown mode with quantities
      if (this.$dropdownContainer && this.$dropdownContainer.length) {
        this.$dropdownContainer.find('.om-multi-select-dropdown-item').each(function() {
          var $item = jQuery(this);
          var variationId = $item.data('variation-id');
          var cartItem = cartData[variationId];

          if (cartItem && cartItem.qty > 0) {
            // Check the checkbox
            var $checkbox = $item.find('.om-multi-select-checkbox');
            $checkbox.prop('checked', true);
            $item.addClass('selected');

            // Set quantity input
            var $qtyInput = $item.find('.om-qty-input');
            if ($qtyInput.length) {
              $qtyInput.val(cartItem.qty);
            }

            // Get label from item
            var label = $item.find('.om-multi-select-item-label').text() || '';

            // Add to selectedVariations with cart_item_key
            self.selectedVariations.push({
              variation_id: variationId,
              quantity: cartItem.qty,
              label: label,
              cart_item_key: cartItem.cart_item_key || ''
            });
          } else {
            // Ensure unchecked items are reset
            var $checkbox = $item.find('.om-multi-select-checkbox');
            $checkbox.prop('checked', false);
            $item.removeClass('selected');
            var $qtyInput = $item.find('.om-qty-input');
            if ($qtyInput.length) {
              $qtyInput.val(1);
            }
          }
        });
      }
    } else {
      // For grid/image mode - preserve cart quantities!
      // First, reset all swatches
      jQuery('.om-variations-wrapper[data-multi-select="1"] .om-variation-item').each(function() {
        var $swatch = jQuery(this);
        $swatch.removeClass('om-multi-selected active');
        $swatch.find('.om-multi-select-checkmark').remove();
      });

      // Get variations data to map variation_id to swatch value
      var variations = this.$form.data('product_variations');
      if (!variations || !Array.isArray(variations)) {
        this.updateButtonState();
        this.updateUI();
        return;
      }

      // Build a map of variation_id -> attribute values
      var variationMap = {};
      for (var i = 0; i < variations.length; i++) {
        var variation = variations[i];
        variationMap[variation.variation_id] = variation.attributes;
      }

      // Now iterate cart data and find matching swatches
      Object.keys(cartData).forEach(function(varId) {
        var variationId = parseInt(varId, 10);
        var cartItem = cartData[varId];

        if (!cartItem || cartItem.qty <= 0) {
          return;
        }

        // Get the attribute values for this variation
        var attrs = variationMap[variationId];
        if (!attrs) {
          return;
        }

        // Find the swatch that matches this variation's attribute value
        Object.keys(attrs).forEach(function(attrKey) {
          var attrValue = attrs[attrKey];
          if (!attrValue) return; // Skip "any" attributes

          // Find swatch with this value
          var $swatch = jQuery('.om-variations-wrapper[data-multi-select="1"] .om-variation-item[value="' + attrValue + '"]');
          if ($swatch.length) {
            $swatch.addClass('om-multi-selected active');

            // Add checkmark if not exists
            if (!$swatch.find('.om-multi-select-checkmark').length) {
              $swatch.append('<span class="om-multi-select-checkmark">✓</span>');
            }

            // Use cart item name for label (more reliable than swatch title)
            var label = cartItem.name || $swatch.attr('title') || '';
            // Extract just the variation part if it's "Product Name - Variation"
            if (label.indexOf(' - ') !== -1) {
              label = label.split(' - ').slice(1).join(' - ');
            }

            self.selectedVariations.push({
              variation_id: variationId,
              quantity: cartItem.qty,
              label: label,
              $swatch: $swatch,
              cart_item_key: cartItem.cart_item_key || ''
            });
          }
        });
      });
    }

    this.updateButtonState();
    this.updateUI();
  },

  /**
   * Update button state based on selections
   * Shows "Update cart" when items exist in cart, "Add to cart" otherwise
   */
  updateButtonState: function updateButtonState() {
    var i18n = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n : {};

    // Check if any selected variation has a cart_item_key (meaning it's in the cart)
    var hasCartItems = this.selectedVariations.some(function(v) {
      return v.cart_item_key && v.cart_item_key !== '';
    });

    // Also check if this product has any items in cart (even if not currently selected)
    var productId = this.$form.data('product_id');
    var cartSyncEnabled = typeof omProductSwatches !== 'undefined' &&
                          omProductSwatches.cartSync &&
                          omProductSwatches.cartSync.enabled !== false;

    if (!hasCartItems && cartSyncEnabled && productId) {
      var cartData = omProductSwatches.cartSync.cartData[productId] || {};
      hasCartItems = Object.keys(cartData).length > 0;
    }

    // Store original button text if not already stored
    if (!this.$addButton.data('add-text')) {
      this.$addButton.data('add-text', this.$addButton.text());
    }
    if (!this.$addButton.data('update-text')) {
      this.$addButton.data('update-text', i18n.updateCart || 'Update cart');
    }

    if (hasCartItems) {
      this.$addButton.text(this.$addButton.data('update-text'));
      this.$addButton.addClass('om-update-mode');
    } else {
      this.$addButton.text(this.$addButton.data('add-text'));
      this.$addButton.removeClass('om-update-mode');
    }
  },

  /**
   * Sync cart with current selections
   * Handles add, update, and remove operations in a single request
   */
  syncCart: function syncCart() {
    var self = this;

    // Build sync items array
    var items = [];
    var productId = this.$form.data('product_id');

    // Get cart data for this product to find items that need to be removed
    var cartSyncEnabled = typeof omProductSwatches !== 'undefined' &&
                          omProductSwatches.cartSync &&
                          omProductSwatches.cartSync.enabled !== false;

    var cartData = {};
    if (cartSyncEnabled && productId) {
      cartData = omProductSwatches.cartSync.cartData[productId] || {};
    }

    // Items from current selection (add or update)
    this.selectedVariations.forEach(function(v) {
      items.push({
        variation_id: v.variation_id,
        quantity: v.quantity,
        cart_item_key: v.cart_item_key || ''
      });
    });

    // Items in cart that are now deselected (need to be removed)
    var selectedIds = this.selectedVariations.map(function(v) {
      return parseInt(v.variation_id, 10);
    });

    Object.keys(cartData).forEach(function(varId) {
      var variationId = parseInt(varId, 10);
      var inSelection = selectedIds.indexOf(variationId) !== -1;

      if (!inSelection && cartData[varId].cart_item_key) {
        // Item in cart but not selected - remove it
        items.push({
          variation_id: variationId,
          quantity: 0,
          cart_item_key: cartData[varId].cart_item_key
        });
      }
    });

    // If no items to sync, return early
    if (!items.length) {
      var i18n = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n : {};
      this.showNotice('info', i18n.noChanges || 'No changes to sync.');
      return;
    }

    // Set global flag to prevent side cart close from triggering duplicate refresh
    if (typeof omProductSwatches !== 'undefined' && omProductSwatches.cartSync) {
      omProductSwatches.cartSync.batchAddInProgress = true;
    }

    // Disable button and show loading
    this.$addButton.addClass('loading').prop('disabled', true);

    jQuery.ajax({
      url: omSwatchSettings.ajax_url,
      method: 'POST',
      data: {
        action: 'aieo_swatch_sync_cart',
        security: omSwatchSettings.security,
        product_id: productId,
        items: JSON.stringify(items)
      },
      success: function(response) {
        if (response.success) {
          // ── Attribution: log a single cart-add event for the parent
          //    product when at least one variation is being NEWLY
          //    added. The standard aieo-attribution-client.js click
          //    listener only catches WC's `.single_add_to_cart_button`
          //    / `.add_to_cart_button` — the multi-select swatch
          //    button (`.om-multi-select-add-to-cart`) calls THIS
          //    syncCart() path, which the listener never sees, so
          //    bundle reco-clicks for variable products were silently
          //    failing to attribute. We fire one beacon at the parent
          //    grain (the attribution-table JOIN keys on parent pid).
          //    Skip when the operation is purely deselects (every
          //    item has quantity=0) — that's a removal, not a buy.
          try {
            var addedQty = 0;
            for (var _i = 0; _i < items.length; _i++) {
              var _q = parseInt(items[_i].quantity, 10) || 0;
              // Count quantity ONLY when this is a fresh add
              // (no existing cart_item_key). Removals (qty=0) and
              // existing-item updates are not "new attributions".
              if (_q > 0 && (!items[_i].cart_item_key || items[_i].cart_item_key === '')) {
                addedQty += _q;
              }
            }
            if (addedQty > 0 && productId) {
              var _attrFd = new FormData();
              _attrFd.append('action',     'aieo_attribution_log');
              _attrFd.append('event_id',   (window.crypto && crypto.randomUUID)
                ? crypto.randomUUID()
                : ('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx').replace(/[xy]/g, function(c){
                    var r = Math.random()*16|0, v = c==='x' ? r : (r&0x3|0x8); return v.toString(16);
                  })
              );
              _attrFd.append('product_id',   String(productId));
              _attrFd.append('variation_id', '0');
              _attrFd.append('quantity',     String(addedQty));
              // Detect surface: archive-card swatches vs PDP. The archive
              // wrapper is rendered with .om-product-swatches--archive,
              // and the form is nested inside it.
              var _$omRoot     = self.$form && self.$form.closest ? self.$form.closest('.om-product-swatches') : null;
              var _isArchive   = !!(_$omRoot && _$omRoot.length && _$omRoot.hasClass('om-product-swatches--archive'));
              var _sourceLabel = _isArchive ? 'archive' : 'single_product';
              _attrFd.append('source',       _sourceLabel);
              _attrFd.append('source_pid',   String(productId));
              if (_isArchive) {
                var _archiveCfg = (window.aieoAttribution && window.aieoAttribution.archive)
                              || (window.aieoRecoObserver  && window.aieoRecoObserver.archive)
                              || null;
                if (_archiveCfg && _archiveCfg.term_id) {
                  try {
                    _attrFd.append('source_meta', JSON.stringify({
                      archive_term_id:   _archiveCfg.term_id,
                      archive_term_tax:  _archiveCfg.tax,
                      archive_term_slug: _archiveCfg.slug,
                      archive_term_name: _archiveCfg.name
                    }));
                  } catch (_smErr) { /* noop */ }
                }
              }
              var _ajaxUrl = (window.aieoAttribution && window.aieoAttribution.ajaxUrl)
                || (window.wc_add_to_cart_params && window.wc_add_to_cart_params.ajax_url)
                || omSwatchSettings.ajax_url
                || '/wp-admin/admin-ajax.php';
              if (navigator.sendBeacon) {
                navigator.sendBeacon(_ajaxUrl, _attrFd);
              } else {
                jQuery.post(_ajaxUrl, _attrFd);
              }
            }
          } catch (_attrErr) { /* attribution must never block the cart-sync UX */ }

          // Update cart fragments (mini cart)
          if (response.data.fragments) {
            jQuery.each(response.data.fragments, function(selector, fragment) {
              jQuery(selector).replaceWith(fragment);
            });
          }

          // Trigger WooCommerce events
          jQuery(document.body).trigger('added_to_cart', [response.data.fragments, response.data.cart_hash]);
          jQuery(document.body).trigger('wc_fragment_refresh');

          // Update local cart data from product_cart_state (COMPLETE cart state for this product)
          // This includes ALL items for this product, not just changed ones
          if (typeof omProductSwatches !== 'undefined' && omProductSwatches.cartSync && response.data.product_cart_state) {
            omProductSwatches.cartSync.cartData[productId] = response.data.product_cart_state;
            omProductSwatches.cartSync.applyCartIndicators();
          }

          // Clear pending changes flag BEFORE repopulating (sync was successful)
          self.userHasPendingChanges = false;

          // Now repopulate selections from the updated cart data
          self.populateFromCart();

          // Hide any WooCommerce notices that may appear at the top
          jQuery('.woocommerce-notices-wrapper .woocommerce-message').not('.om-multi-select-notice').hide();

          // Show success message with total items in cart for this product
          var totalItems = 0;
          if (response.data.product_cart_state) {
            // Calculate from complete cart state (all items for this product)
            Object.keys(response.data.product_cart_state).forEach(function(varId) {
              var item = response.data.product_cart_state[varId];
              totalItems += parseInt(item.qty, 10) || 0;
            });
          }
          self.showSuccessState(totalItems);

          // Show any errors
          if (response.data.errors && response.data.errors.length) {
            setTimeout(function() {
              self.showNotice('warning', response.data.errors.join(' '));
            }, 3000);
          }
        } else {
          var errorI18n = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n : {};
          self.showNotice('error', response.data.message || errorI18n.couldNotUpdate || 'Could not update cart.');
        }
      },
      error: function() {
        var errorI18n = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n : {};
        self.showNotice('error', errorI18n.networkError || 'Network error. Please try again.');
      },
      complete: function() {
        self.$addButton.removeClass('loading').prop('disabled', false);

        // Clear the batch add flag after a delay
        setTimeout(function() {
          if (typeof omProductSwatches !== 'undefined' && omProductSwatches.cartSync) {
            omProductSwatches.cartSync.batchAddInProgress = false;
          }
        }, 2000);
      }
    });
  },

  batchAddToCart: function batchAddToCart() {
    var self = this;

    if (!this.selectedVariations.length) {
      return;
    }

    // Set global flag to prevent side cart close from triggering duplicate refresh
    if (typeof omProductSwatches !== 'undefined' && omProductSwatches.cartSync) {
      omProductSwatches.cartSync.batchAddInProgress = true;
    }

    // Disable button and show loading
    this.$addButton.addClass('loading').prop('disabled', true);

    // Prepare data
    var variationsData = this.selectedVariations.map(function(item) {
      return {
        variation_id: item.variation_id,
        quantity: item.quantity
      };
    });

    jQuery.ajax({
      url: omSwatchSettings.ajax_url,
      method: 'POST',
      data: {
        action: 'aieo_swatch_batch_add_to_cart',
        security: omSwatchSettings.security,
        variations: variationsData
      },
      success: function(response) {
        if (response.success) {
          // ── Attribution: log a single cart-add event for the parent
          //    product. The standard aieo-attribution-client.js click
          //    listener only catches WC's `.single_add_to_cart_button`
          //    / `.add_to_cart_button` — the multi-select swatch
          //    button (`.om-multi-select-add-to-cart`) bypasses it,
          //    which is why bundle reco-clicks for variable products
          //    were never showing up in the attribution table. We
          //    fire a single beacon here, with quantity = total items
          //    across selected variations, so the (cart-add, reco
          //    touch) JOIN in the admin attribution report can match.
          //    Per-variation telemetry is NOT needed — the attribution
          //    table operates at the parent-product grain.
          try {
            var parentPid = parseInt(self.$form.data('product_id'), 10) || 0;
            if (parentPid > 0) {
              var totalQty = 0;
              for (var _i = 0; _i < variationsData.length; _i++) {
                totalQty += parseInt(variationsData[_i].quantity, 10) || 1;
              }
              var attrPayload = new FormData();
              attrPayload.append('action',     'aieo_attribution_log');
              attrPayload.append('event_id',   (window.crypto && crypto.randomUUID)
                ? crypto.randomUUID()
                : ('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx').replace(/[xy]/g, function(c){
                    var r = Math.random()*16|0, v = c==='x' ? r : (r&0x3|0x8); return v.toString(16);
                  })
              );
              attrPayload.append('product_id',   String(parentPid));
              attrPayload.append('variation_id', '0');
              attrPayload.append('quantity',     String(totalQty || 1));
              attrPayload.append('source',       'single_product');
              attrPayload.append('source_pid',   String(parentPid));
              var ajaxUrl = (window.aieoAttribution && window.aieoAttribution.ajaxUrl)
                || (window.wc_add_to_cart_params && window.wc_add_to_cart_params.ajax_url)
                || '/wp-admin/admin-ajax.php';
              if (navigator.sendBeacon) {
                navigator.sendBeacon(ajaxUrl, attrPayload);
              } else {
                jQuery.post(ajaxUrl, attrPayload);
              }
            }
          } catch (_attrErr) { /* attribution must never block the cart-add UX */ }

          // IMMEDIATELY mark added swatches as "in cart" with green border
          // Do this BEFORE clearing selections so we have the $swatch references
          self.markAddedSwatchesInCart(variationsData);

          // Update cart fragments (mini cart)
          if (response.data.fragments) {
            jQuery.each(response.data.fragments, function(selector, fragment) {
              jQuery(selector).replaceWith(fragment);
            });
          }

          // Trigger WooCommerce events
          // This will trigger refreshCartData via the added_to_cart listener,
          // which reads the correct totals from fragments - no need to manually update
          jQuery(document.body).trigger('added_to_cart', [response.data.fragments, response.data.cart_hash]);
          jQuery(document.body).trigger('wc_fragment_refresh');

          // Re-apply indicators after a short delay to ensure fragments are processed
          // The added_to_cart event handler in cartSync.init() will update cartData from fragments
          if (typeof omProductSwatches !== 'undefined' && omProductSwatches.cartSync) {
            setTimeout(function() {
              omProductSwatches.cartSync.applyCartIndicators();
            }, 150);
          }

          // Hide any WooCommerce notices that may appear at the top
          jQuery('.woocommerce-notices-wrapper .woocommerce-message').not('.om-multi-select-notice').hide();

          // Show success message in the summary area (not as a notice at top)
          var totalItemsAdded = response.data.added_count || variationsData.reduce(function(sum, item) { return sum + item.quantity; }, 0);
          self.showSuccessState(totalItemsAdded);

          // Clear selections after successful add (skip updateUI since showSuccessState handles the transition)
          self.clearSelections(true);

          // Show any errors for items that couldn't be added (in the summary area)
          if (response.data.errors && response.data.errors.length) {
            setTimeout(function() {
              self.showNotice('warning', response.data.errors.join(' '));
            }, 3000);
          }
        } else {
          var errorI18n = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n : {};
          self.showNotice('error', response.data.message || errorI18n.couldNotAdd || 'Could not add items to cart.');
        }
      },
      error: function() {
        var errorI18n = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n : {};
        self.showNotice('error', errorI18n.networkError || 'Network error. Please try again.');
      },
      complete: function() {
        self.$addButton.removeClass('loading').prop('disabled', false);
        // Don't call updateUI() here - it will overwrite the success message
        // Success state handles its own transition via setTimeout

        // Clear the batch add flag after a delay to ensure side cart close events are skipped
        setTimeout(function() {
          if (typeof omProductSwatches !== 'undefined' && omProductSwatches.cartSync) {
            omProductSwatches.cartSync.batchAddInProgress = false;
          }
        }, 2000);
      }
    });
  },

  /**
   * Show success state in the summary area after adding to cart
   * @param {number} count - Number of items added
   */
  showSuccessState: function showSuccessState(count) {
    var self = this;
    // Use the correct container reference based on button position
    var $container = this.buttonPositionBelow ? this.$messageContainer : this.$mainContainer;
    var $summary = $container.find('.om-multi-select-summary');

    var i18n = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n : {};
    var successMessage = i18n.successMessage || 'Great! {count} items added to your cart';

    // Replace {count} placeholder with actual count
    successMessage = successMessage.replace('{count}', count);

    // Show only the success message (details are in the cart summary box)
    var successHtml = '<div class="om-multi-select-success">';
    successHtml += '<span class="om-success-icon">✓</span>';
    successHtml += '<span class="om-success-message">' + successMessage + '</span>';
    successHtml += '</div>';

    // Show the message container for the success message
    if (this.buttonPositionBelow && this.$messageContainer) {
      this.$messageContainer.show();
    }

    $summary.html(successHtml);
    $container.addClass('om-success-state');

    // After 5 seconds, hide the success message (cart summary box handles the rest)
    setTimeout(function() {
      $container.removeClass('om-success-state');
      $summary.empty();
      // Hide the message container again if items are in cart
      if (self.buttonPositionBelow && self.$messageContainer) {
        self.$messageContainer.hide();
      }
    }, 5000);
  },

  showNotice: function showNotice(type, message) {
    var $notices = jQuery('.woocommerce-notices-wrapper').first();

    if (!$notices.length) {
      $notices = jQuery('<div class="woocommerce-notices-wrapper"></div>');
      jQuery('.woocommerce-message, .entry-summary, .summary').first().before($notices);
    }

    var noticeClass = 'woocommerce-message';
    if (type === 'error') {
      noticeClass = 'woocommerce-error';
    } else if (type === 'warning') {
      noticeClass = 'woocommerce-info';
    }

    var $notice = jQuery('<div class="' + noticeClass + ' om-multi-select-notice">' + message + '</div>');

    $notices.empty().append($notice);

    // Auto-hide after 5 seconds
    setTimeout(function() {
      $notice.fadeOut(300, function() {
        jQuery(this).remove();
      });
    }, 5000);

    // Scroll to notice
    jQuery('html, body').animate({
      scrollTop: $notices.offset().top - 100
    }, 300);
  }
};

jQuery(document).ready(function () {
  // Only init productSwatch on single product pages
  if (jQuery('body').hasClass('single-product')) {
    omProductSwatches.productSwatch.init();

    // Initialize multi-select mode if enabled
    omProductSwatches.multiSelectHandler.init();

    // Synchronous price update
    jQuery('.variations_form').on('found_variation.omPriceDisplay', function(event, variation) {
      var $form = jQuery(this);
      var $priceDiv = $form.find('.woocommerce-variation-price');

      // If price div is empty but we have price_html, populate it immediately
      if ($priceDiv.is(':empty') && variation.price_html) {
        $priceDiv.html(variation.price_html);
      }
    });
  }

  omProductSwatches.archiveProductSwatch.init();
  omProductSwatches.variationDescriptionEnhancement.init();
  omProductSwatches.swatchLimitHandler.init();

  // Quick-View
  window.addEventListener('om.quickview.ajax.loaded', function (e) {
    var wrapper = document.querySelectorAll('.om-quick-view-modal .om-variations-wrapper:not(.om-widget-variations)');
    omProductSwatches.productSwatch.init(e, wrapper);
  });

  // Bricks Builder Custom Popup Template
  document.addEventListener('bricks/ajax/popup/loaded', function (e) {
    var wrapper = document.querySelectorAll('.brx-popup-content .om-variations-wrapper:not(.om-widget-variations)');
    omProductSwatches.productSwatch.init(e, wrapper);
  });

  if ('MutationObserver' in window) {
    var swatchWrapperSet = new Set();
    var pendingWrappers = [];
    var pendingForms = jQuery();
    var debounceTimer = null;

    // PERFORMANCE: Debounced processing to avoid multiple rapid initializations
    var processPendingNodes = function() {
      if (pendingWrappers.length) {
        omProductSwatches.productSwatch.init(null, pendingWrappers);
        pendingWrappers = [];
      }

      if (pendingForms.length) {
        omProductSwatches.archiveProductSwatch.init(pendingForms);
        pendingForms = jQuery();
      }
    };

    var observer = new MutationObserver(function (mutations) {
      var enqueueWrapper = function enqueueWrapper(node) {
        if (!node || node.getAttribute('data-om-swatches-ready')) {
          return;
        }

        if (!swatchWrapperSet.has(node)) {
          swatchWrapperSet.add(node);
          pendingWrappers.push(node);
        }
      };

      var enqueueForm = function enqueueForm(node) {
        if (!node || node.getAttribute('data-om-swatches-archive-ready')) {
          return;
        }

        pendingForms = pendingForms.add(node);
      };

      mutations.forEach(function (mutation) {
        mutation.addedNodes.forEach(function (node) {
          if (!(node instanceof HTMLElement)) {
            return;
          }

          if (node.matches && node.matches('.om-variations-wrapper:not(.om-widget-variations)')) {
            enqueueWrapper(node);
          }

          if (node.matches && node.matches('.om-product-swatches .variations_form')) {
            enqueueForm(node);
          }

          if (typeof node.querySelectorAll === 'function') {
            node.querySelectorAll('.om-variations-wrapper:not(.om-widget-variations)').forEach(function (childWrapper) {
              enqueueWrapper(childWrapper);
            });

            node.querySelectorAll('.om-product-swatches .variations_form').forEach(function (childForm) {
              enqueueForm(childForm);
            });
          }
        });
      });

      // PERFORMANCE: Debounce with 50ms delay to batch rapid DOM changes
      if (debounceTimer) {
        clearTimeout(debounceTimer);
      }
      debounceTimer = setTimeout(processPendingNodes, 50);
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true
    });
  }
});

/**
 * See All Variations Modal Handler
 * Allows customers to view all product variations and quick add-to-cart from archive pages
 */
omProductSwatches.seeAllModalHandler = {
  $modal: null,
  currentProductId: null,
  isLoading: false,

  init: function init() {
    var self = this;
    this.$modal = jQuery('#om-see-all-variations-modal');

    // QV consolidation 2026-04-26: the modal markup is no longer emitted
    // server-side, so this.$modal is empty. We must NOT early-return here
    // anymore — the click handlers below now redirect to the side-cart
    // Quick View and don't need the modal element to exist.
    // (Original behaviour bailed when no modal was on the page.)

    // Bind click events for opening modal.
    //
    // Consolidation step 1: when AIEO side-cart Quick View on archive is
    // enabled, the same product card already carries a `.aieo-quick-view-
    // trigger` button. Clicking the see-all link should behave like the
    // "Discover it" button — open the side-cart QV — instead of popping
    // the standalone modal. This is the lightweight half of the planned
    // QV consolidation; the heavier half (rendering visual swatches with
    // multi-select inside the QV body) is tracked in
    // ff/ai-eshop-optimizer/docs/QV-CONSOLIDATION-PLAN.md and lands later.
    function tryOpenSideCartQv($link, productId) {
      if (!productId) { return false; }
      var pid = parseInt(productId, 10);
      if (pid <= 0) { return false; }
      // Trigger may live on the same card OR be appended at the end of
      // the loop item by `woocommerce_after_shop_loop_item` hook. Prefer
      // a card-scoped match, then fall back to a document-wide one.
      var $card = $link.closest('li.product, .product, .om-product-swatches').first();
      var $trigger = $card.find('.aieo-quick-view-trigger[data-aieo-quick-view="' + pid + '"]').first();
      if (!$trigger.length) {
        $trigger = jQuery('.aieo-quick-view-trigger[data-aieo-quick-view="' + pid + '"]').first();
      }
      if (!$trigger.length) { return false; }
      $trigger.trigger('click');
      return true;
    }

    jQuery(document).on('click', '.om-see-all-variations-link', function (e) {
      e.preventDefault();
      e.stopImmediatePropagation();
      var $link = jQuery(this);
      var productId = $link.data('product-id');
      if (tryOpenSideCartQv($link, productId)) { return; }
      var productData = self.extractProductDataFromPage($link, productId);
      self.openModal(productId, productData);
    });

    // Also allow clicking on the "+XX more" counter to open modal
    jQuery(document).on('click', '.om-product-swatches--archive .om-more-variations', function (e) {
      e.preventDefault();
      e.stopImmediatePropagation();
      var $link = jQuery(this);
      var $wrapper = $link.closest('.om-product-swatches');
      var $form = $wrapper.find('.variations_form');
      var productId = $form.data('product_id');

      if (productId) {
        if (tryOpenSideCartQv($link, productId)) { return; }
        var productData = self.extractProductDataFromPage($link, productId);
        self.openModal(productId, productData);
      }
    });

    // Close modal events
    this.$modal.on('click', '.om-modal-close, .om-modal-overlay', function () {
      self.closeModal();
    });

    // Close on escape key
    jQuery(document).on('keydown', function (e) {
      if (e.key === 'Escape' && self.$modal.is(':visible')) {
        self.closeModal();
      }
    });

  },

  /**
   * Extract product data from the page to show immediately in modal
   */
  extractProductDataFromPage: function extractProductDataFromPage($clickedElement, productId) {
    var $product = $clickedElement.closest('li.product, .product, .wc-block-grid__product');
    var productData = {
      name: '',
      image_url: '',
      permalink: ''
    };

    if ($product.length) {
      // Try to find product name
      var $title = $product.find('.woocommerce-loop-product__title, .wc-block-grid__product-title, .product-title, h2 a, h3 a, .product_title');
      if ($title.length) {
        productData.name = $title.first().text().trim();
      }

      // Try to find product image
      var $img = $product.find('.attachment-woocommerce_thumbnail, .wp-post-image, .woocommerce-product-gallery__image img, img').first();
      if ($img.length) {
        // Get the src or data-src (for lazy loaded images)
        productData.image_url = $img.attr('src') || $img.data('src') || $img.data('lazy-src') || '';
      }

      // Try to find product link
      var $link = $product.find('a.woocommerce-LoopProduct-link, a[href*="/product/"]').first();
      if ($link.length) {
        productData.permalink = $link.attr('href');
      }
    }

    return productData;
  },

  openModal: function openModal(productId, productData) {
    // QV consolidation 2026-04-26: the modal is retired. If anything still
    // calls openModal (theme, third-party plugin, cached JS), short-circuit
    // and route to the side-cart Quick View. Final hard guard while the
    // full QV-with-real-swatches consolidation lands (see
    // ff/ai-eshop-optimizer/QV-CONSOLIDATION-PLAN.md).
    var pid = parseInt(productId, 10) || 0;
    if (pid > 0) {
      var $trigger = jQuery('.aieo-quick-view-trigger[data-aieo-quick-view="' + pid + '"]').first();
      if ($trigger.length) {
        $trigger.trigger('click');
        return;
      }
    }
    // Side-cart QV unavailable on this page → silent no-op (legacy modal
    // is retired). Original modal code below is kept as dead code for
    // reference; will be removed when the full consolidation lands.
    return;
    var self = this;

    if (this.isLoading) {
      return;
    }

    this.currentProductId = productId;
    this.$modal.show();
    jQuery('body').addClass('om-modal-open');

    // Hide product info section and show skeleton loading state
    this.$modal.find('.om-modal-product-info').addClass('om-loading-skeleton');
    this.$modal.find('.om-modal-product-image').removeAttr('src').attr('alt', '');
    this.$modal.find('.om-modal-product-title').text('');
    this.$modal.find('.om-modal-view-product').attr('href', '#').hide();

    // Show loading state for variations
    this.$modal.find('.om-modal-variations-grid').html('<div class="om-modal-loading"><span class="om-spinner"></span>' + (omSwatchSettings.i18n.loadingVariations || 'Loading variations...') + '</div>');

    this.isLoading = true;

    // Fetch all variations via AJAX
    jQuery.ajax({
      url: omSwatchSettings.ajax_url,
      type: 'POST',
      data: {
        action: 'aieo_swatch_get_modal_variations',
        security: omSwatchSettings.security,
        product_id: productId
      },
      success: function success(response) {
        self.isLoading = false;

        if (response.success && response.data) {
          self.renderModal(response.data);
        } else {
          self.$modal.find('.om-modal-variations-grid').html('<div class="om-modal-error">' + (response.data && response.data.message || omSwatchSettings.i18n.failedToLoad || 'Failed to load variations') + '</div>');
        }
      },
      error: function error() {
        self.isLoading = false;
        self.$modal.find('.om-modal-variations-grid').html('<div class="om-modal-error">' + (omSwatchSettings.i18n.networkError || 'Network error. Please try again.') + '</div>');
      }
    });
  },

  renderModal: function renderModal(data) {
    var self = this;
    var product = data.product;

    // Update product title and link, but keep skeleton on image until it loads
    this.$modal.find('.om-modal-product-title').text(product.name);
    this.$modal.find('.om-modal-view-product').attr('href', product.permalink).show();

    // Remove skeleton from text elements only
    this.$modal.find('.om-modal-product-title, .om-modal-view-product').css({
      'background': 'none',
      'animation': 'none',
      'color': ''
    });

    // Load image with onload handler to prevent broken image icon
    var $productInfo = this.$modal.find('.om-modal-product-info');
    var $img = this.$modal.find('.om-modal-product-image');

    if (product.image_url) {
      // Create a new image to preload
      var tempImg = new Image();
      tempImg.onload = function() {
        // Image loaded successfully, now set it and show
        $img.attr('src', product.image_url).attr('alt', product.name);
        $productInfo.removeClass('om-loading-skeleton');
      };
      tempImg.onerror = function() {
        // Image failed to load, just remove skeleton without setting src
        $productInfo.removeClass('om-loading-skeleton');
      };
      tempImg.src = product.image_url;
    } else {
      // No image URL, just remove skeleton
      $productInfo.removeClass('om-loading-skeleton');
    }

    // Insert the form HTML (contains swatches and add-to-cart button)
    var $grid = this.$modal.find('.om-modal-variations-grid');
    $grid.html('<div class="om-modal-form-wrapper">' + data.form_html + '</div>');

    // Initialize WooCommerce variation form
    var $form = $grid.find('.variations_form');
    if ($form.length && typeof $form.wc_variation_form === 'function') {
      $form.wc_variation_form();

      // Initialize our swatch handlers for the modal form
      if (typeof omProductSwatches !== 'undefined' && omProductSwatches.productSwatch) {
        // Find the actual .om-variations-wrapper elements within the modal grid
        var $modalWrappers = $grid.find('.om-variations-wrapper');
        if ($modalWrappers.length) {
          omProductSwatches.productSwatch.init(null, $modalWrappers.toArray());
        }
      }

      // Initialize multi-select mode if enabled. The legacy bundled JS
      // referenced `multiSelectMode` (typo) but the object is actually
      // registered as `multiSelectHandler` (line 1460). Without this fix
      // the modal opens with the WC variation form but no multi-select
      // batch UI — clicking swatches doesn't accumulate selections.
      if (typeof omProductSwatches !== 'undefined' && omProductSwatches.multiSelectHandler) {
        omProductSwatches.multiSelectHandler.init();
      }
    }

    // Set inputmode="numeric" on quantity input for mobile numeric keypad
    var $qtyInput = $grid.find('.quantity .qty');
    if ($qtyInput.length) {
      $qtyInput.attr('inputmode', 'numeric');
      $qtyInput.attr('pattern', '[0-9]*');
    }

    // Re-initialize Botiga quantity buttons in modal
    this.initModalQuantityButtons($grid);

    // Handle form submission via AJAX to stay in modal
    // (only for non-multi-select mode - multi-select has its own handler)
    var isMultiSelect = $form.closest('.om-variations-wrapper').data('multi-select');

    if (!isMultiSelect) {
      $form.on('submit', function (e) {
        e.preventDefault();
        self.handleFormSubmit($form);
      });

      // Also bind to the add-to-cart button click
      $grid.find('.single_add_to_cart_button').on('click', function (e) {
        e.preventDefault();
        self.handleFormSubmit($form);
      });
    }

    // Add "View Product Page" button after the form
    var i18n = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n : {};
    var viewProductText = i18n.viewProductPage || 'View product page';
    var $viewProductBtn = jQuery('<a>', {
      'href': product.permalink,
      'class': 'om-modal-view-product-btn',
      'text': viewProductText
    });
    $grid.find('.om-modal-form-wrapper').append($viewProductBtn);
  },

  handleFormSubmit: function handleFormSubmit($form) {
    var self = this;
    var $btn = $form.find('.single_add_to_cart_button');

    if ($btn.hasClass('disabled') || $btn.hasClass('om-adding')) {
      return;
    }

    var variationId = $form.find('input[name="variation_id"]').val();
    var quantity = $form.find('input[name="quantity"]').val() || 1;

    if (!variationId || variationId === '0') {
      // No variation selected
      return;
    }

    var originalText = $btn.text();
    $btn.addClass('om-adding').text(omSwatchSettings.i18n.addingToCart || 'Adding...');

    jQuery.ajax({
      url: omSwatchSettings.ajax_url,
      type: 'POST',
      data: {
        action: 'aieo_swatch_quick_add_to_cart',
        security: omSwatchSettings.security,
        variation_id: variationId,
        quantity: quantity
      },
      success: function success(response) {
        $btn.removeClass('om-adding');

        if (response.success) {
          $btn.addClass('om-added added').text(omSwatchSettings.i18n.addedToCart || 'Added!');

          // Update cart fragments
          if (response.data.fragments) {
            jQuery.each(response.data.fragments, function (key, value) {
              jQuery(key).replaceWith(value);
            });
          }

          // Trigger WooCommerce event
          jQuery(document.body).trigger('added_to_cart', [response.data.fragments, response.data.cart_hash]);

          // Reset button after delay
          setTimeout(function () {
            $btn.removeClass('om-added added').text(originalText);
          }, 2000);
        } else {
          $btn.text(originalText);
          alert(response.data && response.data.message || omSwatchSettings.i18n.couldNotAdd || 'Could not add to cart.');
        }
      },
      error: function error() {
        $btn.removeClass('om-adding').text(originalText);
        alert(omSwatchSettings.i18n.networkError || 'Network error. Please try again.');
      }
    });
  },

  /**
   * Initialize quantity plus/minus buttons in modal.
   * Re-initializes Botiga theme's quantity handlers or creates our own.
   */
  initModalQuantityButtons: function initModalQuantityButtons($container) {
    var $quantityWrappers = $container.find('.quantity');

    $quantityWrappers.each(function() {
      var $wrapper = jQuery(this);
      var $input = $wrapper.find('.qty');
      var $plus = $wrapper.find('.botiga-quantity-plus');
      var $minus = $wrapper.find('.botiga-quantity-minus');

      // If Botiga buttons don't exist, we need to create them
      if (!$plus.length || !$minus.length) {
        // Check if buttons are hidden or not created
        if (!$plus.length) {
          $plus = jQuery('<a href="#" class="botiga-quantity-plus">+</a>');
          $wrapper.append($plus);
        }
        if (!$minus.length) {
          $minus = jQuery('<a href="#" class="botiga-quantity-minus">−</a>');
          $wrapper.prepend($minus);
        }
      }

      // Remove any existing data-qty-initialized to allow re-initialization
      $wrapper.removeAttr('data-qty-initialized');

      // Bind click handlers for plus/minus buttons
      $plus.off('click.omModal').on('click.omModal', function(e) {
        e.preventDefault();
        var max = Number($input.attr('max')) || 99999;
        var min = Number($input.attr('min')) || 1;
        var step = Number($input.attr('step')) || 1;
        var currentVal = Number($input.val()) || min;
        var newVal = Math.min(max, currentVal + step);

        $input.val(newVal).trigger('change');
      });

      $minus.off('click.omModal').on('click.omModal', function(e) {
        e.preventDefault();
        var max = Number($input.attr('max')) || 99999;
        var min = Number($input.attr('min')) || 1;
        var step = Number($input.attr('step')) || 1;
        var currentVal = Number($input.val()) || min;
        var newVal = Math.max(min, currentVal - step);

        $input.val(newVal).trigger('change');
      });

      // Show the buttons
      $plus.addClass('show');
      $minus.addClass('show');
    });

    // Try to use Botiga's quantity handler if available
    if (typeof botiga !== 'undefined' && botiga.qtyBtn && typeof botiga.qtyBtn.events === 'function') {
      botiga.qtyBtn.events();
    }
  },

  closeModal: function closeModal() {
    this.$modal.hide();
    jQuery('body').removeClass('om-modal-open');
    this.currentProductId = null;
  },

  escapeHtml: function escapeHtml(text) {
    var div = document.createElement('div');
    div.appendChild(document.createTextNode(text));
    return div.innerHTML;
  }
};

// Initialize See All Modal on document ready
jQuery(document).ready(function () {
  omProductSwatches.seeAllModalHandler.init();
});

/**
 * Cart Sync Module
 * Shows which variations are already in the cart with visual indicators
 */
omProductSwatches.cartSync = {
  // Store cart variations data: { product_id: { variation_id: {qty, name} } }
  cartData: {},
  debug: false, // Set to true to enable console logging (disable in production)
  batchAddInProgress: false, // Flag set by multiSelect.batchAddToCart to prevent duplicate refreshes
  recentlyMarkedVariations: {}, // Track recently marked variations to prevent double-processing

  log: function() {
    if (this.debug && window.console) {
      console.log.apply(console, ['[CartSync]'].concat(Array.prototype.slice.call(arguments)));
    }
  },

  init: function() {
    var self = this;

    // Check if cart sync feature is enabled
    // Priority: 1. DOM data attribute (fastest, pre-cached), 2. JS settings object
    var cartSyncFromDOM = jQuery('.om-swatches-wrapper[data-cart-sync]').first().data('cart-sync');

    if (typeof cartSyncFromDOM !== 'undefined') {
      // Use pre-cached setting from DOM (fastest path)
      this.enabled = cartSyncFromDOM === 1 || cartSyncFromDOM === '1' || cartSyncFromDOM === true;
      this.log('Cart sync enabled (from DOM cache):', this.enabled);
    } else {
      // Fallback to JS settings object
      var customizerOptions = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.customizerOptions) ? omSwatchSettings.customizerOptions : {};
      this.enabled = customizerOptions.cartSyncEnabled !== false; // Default to true if not set
      this.log('Cart sync enabled (from JS settings):', this.enabled);
    }

    if (!this.enabled) {
      this.log('Cart sync is disabled in settings - skipping initialization');
      return;
    }

    // Load initial cart data from page
    if (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.cartVariations) {
      this.cartData = omSwatchSettings.cartVariations;
      this.log('Initial cart data loaded:', this.cartData);
      this.log('Cart data keys (product IDs):', Object.keys(this.cartData));
    } else {
      this.log('No initial cart data found in omSwatchSettings');
    }

    // Apply cart indicators on page load - with retry for async loading
    this.applyCartIndicators();

    // Retry after a short delay in case variation data is still loading
    setTimeout(function() {
      self.log('Retrying applyCartIndicators after 500ms delay');
      self.applyCartIndicators();
    }, 500);

    // Another retry after forms are likely fully loaded
    setTimeout(function() {
      self.log('Retrying applyCartIndicators after 1500ms delay');
      self.applyCartIndicators();
    }, 1500);

    // Listen for cart updates
    jQuery(document.body).on('added_to_cart removed_from_cart wc_fragments_refreshed', function(e) {
      self.log('Cart event detected:', e.type);
      // For removed_from_cart, use AJAX refresh to ensure accurate data
      // DOM fragments might not be fully updated yet
      if (e.type === 'removed_from_cart') {
        setTimeout(function() {
          self.refreshAllProductsCartData();
        }, 150);
      } else if (e.type === 'wc_fragments_refreshed') {
        // Only refresh on fragments_refreshed, not on added_to_cart
        // added_to_cart fires BEFORE fragments are updated, causing flicker
        // because stale fragment data overwrites our optimistic update
        // wc_fragments_refreshed fires AFTER fragments have been updated
        self.refreshCartData();
      }
      // For added_to_cart: do nothing here - markActiveSwatchOnAddToCart
      // already provided optimistic UI update, and wc_fragments_refreshed
      // will sync with actual server data shortly after
    });

    // Also listen for our custom batch add event
    jQuery(document).on('om_batch_added_to_cart', function(e, data) {
      self.log('Batch add event received:', data);
      if (data && data.product_id && data.variations) {
        self.updateLocalCartData(data.product_id, data.variations);
        self.applyCartIndicators();
      }
    });

    // Listen for OM Side Cart close event (from om-free-shipping plugin)
    // When user closes the side cart (after making changes), refresh the swatches
    // IMPORTANT: Always use AJAX here because side cart doesn't update WooCommerce fragments
    // Use debounce to prevent double-refresh when side cart auto-opens/closes after add-to-cart
    var sideCartCloseTimer = null;
    var recentlyAddedToCart = false;

    // Track when add-to-cart starts/ends to avoid duplicate refresh
    // The side cart opens DURING add-to-cart, before added_to_cart event fires
    jQuery(document.body).on('adding_to_cart', function(e, $button, data) {
      recentlyAddedToCart = true;
      self.log('adding_to_cart event fired. Button:', $button ? $button.attr('class') : 'null', 'Data:', data);

      // Immediate visual feedback for variable products with swatches
      self.markActiveSwatchOnAddToCart($button, data);
    });

    // Also handle added_to_cart for Botiga theme which doesn't fire adding_to_cart
    // Botiga passes: [fragments, cart_hash, $button]
    jQuery(document.body).on('added_to_cart', function(e, fragments, cart_hash, $button) {
      recentlyAddedToCart = true;
      self.log('added_to_cart event fired. Button:', $button ? $button.attr('class') : 'null');

      // Mark swatch immediately on added_to_cart (for Botiga single AJAX add-to-cart)
      // This handles the case where adding_to_cart wasn't triggered
      if ($button && $button.length) {
        self.markActiveSwatchOnAddToCart($button, null);
      }

      // Keep flag true for a bit longer to catch side cart close events
      setTimeout(function() {
        recentlyAddedToCart = false;
      }, 2000);
    });

    jQuery(document.body).on('om_side_cart_closed', function() {
      // Skip if we're in the middle of or just completed an add-to-cart operation
      // because added_to_cart event already triggers refreshCartData with correct data
      if (recentlyAddedToCart || self.batchAddInProgress || jQuery('.om-adding').length > 0) {
        return;
      }

      // Debounce multiple close events
      if (sideCartCloseTimer) {
        clearTimeout(sideCartCloseTimer);
      }
      sideCartCloseTimer = setTimeout(function() {
        self.refreshAllProductsCartData();
        sideCartCloseTimer = null;
      }, 300);
    });

    // Also listen for Botiga side mini-cart close event (fallback)
    // IMPORTANT: Always use AJAX here because side cart doesn't update WooCommerce fragments
    window.addEventListener('side-mini-cart-toggle', function() {
      var sideCart = document.getElementsByClassName('botiga-side-mini-cart')[0];
      if (sideCart && !sideCart.classList.contains('show')) {
        self.refreshAllProductsCartData();
      }
    });

    // ARCHIVE PAGES: Direct click handler for add-to-cart buttons
    // WooCommerce's adding_to_cart event may not fire reliably on archive pages
    // This ensures instant visual feedback when user clicks add-to-cart
    jQuery(document.body).on('click.omCartSyncArchive', '.om-product-swatches--archive + .add_to_cart_button.ajax_add_to_cart, .om-product-swatches--archive ~ .add_to_cart_button.ajax_add_to_cart, .loop-button-wrap .add_to_cart_button.ajax_add_to_cart', function(e) {
      var $button = jQuery(this);
      self.log('Archive add-to-cart button clicked:', $button.attr('class'));

      // Only process if button has a variation ID set (meaning a variation was selected)
      var variationId = $button.data('product_id');
      if (!variationId) {
        self.log('No product_id on button, skipping');
        return;
      }

      // Find the associated form
      var $form = null;
      var $container = $button.closest('.loop-button-wrap, .product, .wc-block-grid__product');
      if ($container.length) {
        $form = $container.find('.variations_form');
      }
      if (!$form || !$form.length) {
        $form = $button.siblings('.om-product-swatches').find('.variations_form');
      }
      if (!$form || !$form.length) {
        $form = $button.prev('.om-product-swatches').find('.variations_form');
      }

      self.log('Archive click - Form found:', $form ? $form.length : 0, 'Variation ID:', variationId);

      if ($form && $form.length) {
        var productId = $form.data('product_id');
        var data = { product_id: variationId, quantity: $button.data('quantity') || 1 };

        // Trigger the marking function
        self.markActiveSwatchOnAddToCart($button, data);
      }
    });

    self.log('CartSync initialized with archive click handler');

    // FAILSAFE: Hook into jQuery AJAX to catch WooCommerce add-to-cart requests
    // This works even if WooCommerce events don't fire properly
    jQuery(document).ajaxComplete(function(event, xhr, settings) {
      // Check if this is a WooCommerce add-to-cart request
      if (settings.url && settings.url.indexOf('wc-ajax=add_to_cart') !== -1) {
        self.log('AJAX add-to-cart detected via ajaxComplete');

        // Parse the response to get variation info
        try {
          var response = JSON.parse(xhr.responseText);
          if (response && !response.error) {
            self.log('Add-to-cart successful, refreshing cart data');
            // Small delay to let WooCommerce update the DOM
            setTimeout(function() {
              self.refreshCartData();
              self.applyCartIndicators();
            }, 200);
          }
        } catch(e) {
          // Response might not be JSON, try refreshing anyway
          setTimeout(function() {
            self.refreshCartData();
            self.applyCartIndicators();
          }, 200);
        }
      }
    });

    self.log('CartSync fully initialized');
  },

  /**
   * Refresh cart data — three-tier lookup, fastest first:
   *   1. window.aieoCartVariations — populated by the side-cart JS
   *      after every cart action (since the side-cart response now
   *      ships the cart-wide variation map). Zero round trips.
   *   2. .om-cart-variations-data DOM fragment — legacy WC-fragments
   *      path, kept for non-side-cart contexts (cart page, checkout).
   *   3. AJAX fallback — last resort, single call (NOT a per-product
   *      fan-out; the server ignores product_id and returns the
   *      whole cart map regardless).
   */
  refreshCartData: function() {
    var self = this;

    // Early bail-out if cart sync is disabled
    if (this.enabled === false) {
      return;
    }

    // Tier 1 — window blob populated by side-cart JS. Hot path.
    if (window.aieoCartVariations && typeof window.aieoCartVariations === 'object') {
      self.cartData = { variations: window.aieoCartVariations };
      self.applyCartIndicators();
      jQuery(document.body).trigger('om_cart_data_refreshed');
      return;
    }

    // Tier 2 — WC fragment (legacy, non-side-cart contexts).
    var $fragmentData = jQuery('.om-cart-variations-data');
    if ($fragmentData.length) {
      try {
        var cartData = JSON.parse($fragmentData.text());
        self.cartData = cartData;
        self.applyCartIndicators();
        jQuery(document.body).trigger('om_cart_data_refreshed');
        return;
      } catch (e) {
        // Parse failed, fall through to AJAX
      }
    }

    // Tier 3 — single AJAX (no per-product fan-out).
    self.refreshCartDataViaAjax();
  },

  /**
   * Refresh cart data via AJAX — single call, NOT a fan-out. The
   * `aieo_swatch_get_cart_variations` handler ignores `product_id`
   * and always returns the cart-wide `{variation_id: qty}` map, so
   * we use the response directly instead of re-querying per product.
   *
   * Old behaviour fired N parallel calls for N products on the page,
   * each returning identical data — typical PDP burnt 8-10 round
   * trips on cart change. Fixed 2026-05-06 alongside the side-cart
   * pre-fetch so even this fallback is rarely hit.
   */
  refreshCartDataViaAjax: function() {
    var self = this;

    jQuery.ajax({
      url: omSwatchSettings.ajax_url,
      type: 'POST',
      data: {
        action: 'aieo_swatch_get_cart_variations',
        security: omSwatchSettings.security,
        product_id: 0
      },
      success: function(response) {
        if (response && response.success && response.data) {
          self.cartData = response.data;
          // Cache for sibling widgets that may mount next.
          window.aieoCartVariations = response.data.variations || {};
          self.applyCartIndicators();
          jQuery(document.body).trigger('om_cart_data_refreshed');
        }
      }
    });
  },

  /**
   * Refresh cart data for all products visible on the page
   */
  refreshAllProductsCartData: function() {
    var self = this;

    // Early bail-out if cart sync is disabled
    if (this.enabled === false) {
      return;
    }

    var productIds = [];

    // Collect all product IDs from variation forms on the page
    jQuery('.variations_form').each(function() {
      var productId = jQuery(this).data('product_id');
      if (productId && productIds.indexOf(productId) === -1) {
        productIds.push(productId);
      }
    });

    // Also check for swatches with product IDs
    jQuery('.om-product-swatches[data-product-id]').each(function() {
      var productId = jQuery(this).data('product-id');
      if (productId && productIds.indexOf(productId) === -1) {
        productIds.push(productId);
      }
    });

    if (!productIds.length) {
      return;
    }

    // Fetch cart data for each product
    var fetchPromises = productIds.map(function(productId) {
      return jQuery.ajax({
        url: omSwatchSettings.ajax_url,
        type: 'POST',
        data: {
          action: 'aieo_swatch_get_cart_variations',
          security: omSwatchSettings.security,
          product_id: productId
        }
      });
    });

    jQuery.when.apply(jQuery, fetchPromises).done(function() {
      // Process responses
      var responses = productIds.length === 1 ? [arguments] : arguments;

      for (var i = 0; i < productIds.length; i++) {
        var response = responses[i];
        if (response && response[0] && response[0].success && response[0].data) {
          self.cartData[productIds[i]] = response[0].data.variations || {};
        }
      }

      self.applyCartIndicators();
      // Trigger event to notify multiSelect to repopulate
      jQuery(document.body).trigger('om_cart_data_refreshed');
    });
  },

  /**
   * Update local cart data after adding items
   * @param {number} productId - Product ID
   * @param {object} variations - Object with variation_id as key and quantity as value
   */
  updateLocalCartData: function(productId, variations) {
    this.log('Updating local cart data for product', productId, 'with', variations);

    if (!this.cartData[productId]) {
      this.cartData[productId] = {};
    }

    for (var variationId in variations) {
      if (variations.hasOwnProperty(variationId)) {
        var qty = parseInt(variations[variationId], 10);
        var existing = this.cartData[productId][variationId];

        if (existing) {
          // Handle both old format (number) and new format ({qty, name})
          if (typeof existing === 'object') {
            existing.qty += qty;
          } else {
            this.cartData[productId][variationId] = existing + qty;
          }
        } else {
          this.cartData[productId][variationId] = qty;
        }
      }
    }

    this.log('Updated cart data:', this.cartData);

    // Keep Botiga button in sync with cart
    var $form = jQuery('.variations_form[data-product_id="' + productId + '"]');
    if ($form.length) {
      this.updateBotigaInCartButton($form, productId);
    }
  },

  /**
   * Apply visual indicators to swatches that are in cart
   */
  applyCartIndicators: function() {
    var self = this;

    // Check if feature is enabled
    if (this.enabled === false) {
      return;
    }

    var formsFound = jQuery('.variations_form').length;

    this.log('applyCartIndicators called. Forms found:', formsFound);
    this.log('Current cartData:', JSON.stringify(this.cartData));

    // Process each product's variation form
    jQuery('.variations_form').each(function() {
      var $form = jQuery(this);
      var productId = $form.data('product_id');

      self.log('Processing form for product ID:', productId);

      if (!productId) {
        self.log('Form has no product_id data attribute');
        return;
      }

      if (!self.cartData[productId]) {
        self.log('No cart data for product', productId, '- clearing indicators');
        // Remove cart indicators if no items in cart
        $form.find('.om-cart-summary').remove();
        $form.find('.om-in-cart').removeClass('om-in-cart');
        $form.find('.om-cart-badge').remove();
        return;
      }

      var productCartData = self.cartData[productId];
      self.log('Cart data for product', productId, ':', JSON.stringify(productCartData));

      // Handle dropdown mode items (multi-select with data-variation-id)
      self.applyDropdownCartIndicators($form, productCartData);

      // For grid mode (multi-select), mark swatches that match cart variations
      self.applyGridCartIndicators($form, productCartData);

      // Show cart summary with details
      self.showCartSummary($form, productCartData);

      // Update Botiga in-cart-flag button text to match actual cart data
      self.updateBotigaInCartButton($form, productId);
    });
  },

  /**
   * Apply cart indicators to grid mode swatches
   * Finds swatches that correspond to variations in cart and marks them
   */
  applyGridCartIndicators: function($form, productCartData) {
    var self = this;

    // Get product variations data
    var variations = $form.data('product_variations');

    // Handle case where variations might be a string (needs parsing)
    if (typeof variations === 'string') {
      try {
        variations = JSON.parse(variations);
      } catch(e) {
        this.log('Failed to parse product_variations string:', e);
        variations = null;
      }
    }

    if (!variations || !Array.isArray(variations)) {
      this.log('No product_variations data found on form. Form has data:', $form.data());
      // Try to get from window.product_variations as fallback
      var productId = $form.data('product_id');
      if (window.product_variations && window.product_variations[productId]) {
        variations = window.product_variations[productId];
        this.log('Using window.product_variations fallback');
      } else {
        return;
      }
    }

    var variationIdsInCart = Object.keys(productCartData);
    this.log('Applying grid cart indicators. Product ID:', $form.data('product_id'));
    this.log('Variations in cart:', variationIdsInCart);
    this.log('Total variations available:', variations.length);

    // First, clear existing indicators
    $form.find('.om-variations-wrapper .om-variation-item').removeClass('om-in-cart');
    $form.find('.om-variations-wrapper .om-cart-badge').remove();

    // For each variation in cart, find the matching swatch and mark it
    for (var variationId in productCartData) {
      if (!productCartData.hasOwnProperty(variationId)) continue;

      var cartItem = productCartData[variationId];
      var qty = typeof cartItem === 'object' ? (cartItem.qty || cartItem) : cartItem;

      this.log('Processing variation ID:', variationId, 'qty:', qty);

      // Find this variation in the variations data
      var variation = null;
      for (var i = 0; i < variations.length; i++) {
        if (String(variations[i].variation_id) === String(variationId)) {
          variation = variations[i];
          break;
        }
      }

      if (!variation) {
        this.log('Variation', variationId, 'not found in variations data. Available IDs:', variations.slice(0, 5).map(function(v) { return v.variation_id; }));
        continue;
      }

      // Get the attribute values for this variation
      var attributes = variation.attributes || {};
      this.log('Variation', variationId, 'attributes:', JSON.stringify(attributes));

      // Find swatch(es) that match this variation's attributes
      for (var attrKey in attributes) {
        if (!attributes.hasOwnProperty(attrKey)) continue;

        var attrValue = attributes[attrKey];
        if (!attrValue) continue; // Skip "any" attribute

        // Try multiple selectors to find the swatch
        // Value might be slug, might need URL encoding handled
        var $swatch = $form.find('.om-variation-item[value="' + attrValue + '"]');

        // If not found, try case-insensitive or with different escaping
        if (!$swatch.length) {
          $swatch = $form.find('.om-variation-item').filter(function() {
            var val = jQuery(this).attr('value');
            return val && val.toLowerCase() === attrValue.toLowerCase();
          });
        }

        this.log('Looking for swatch with value "' + attrValue + '" - found:', $swatch.length);

        // Debug: log all swatch values on first iteration
        if (self.debug && attrKey === Object.keys(attributes)[0]) {
          var allValues = [];
          $form.find('.om-variation-item').each(function() {
            allValues.push(jQuery(this).attr('value'));
          });
          this.log('Available swatch values in DOM:', allValues.slice(0, 10));
        }

        if ($swatch.length) {
          // Mark as in cart
          $swatch.addClass('om-in-cart');

          // Add or update cart badge
          var $badge = $swatch.find('.om-cart-badge');
          if (!$badge.length) {
            $badge = jQuery('<span class="om-cart-badge"></span>');
            $swatch.append($badge);
          }

          // If multiple variations with same attribute value are in cart,
          // the badge will show the last one's quantity. That's acceptable.
          $badge.text(qty).attr('title', qty + ' in cart');
          this.log('Marked swatch as in-cart:', attrValue);
        }
      }
    }
  },

  /**
   * Show a summary of cart items for this product
   */
  showCartSummary: function($form, productCartData) {
    var self = this;
    var variationIds = Object.keys(productCartData);

    this.log('showCartSummary called with', variationIds.length, 'variations');

    // Operator-controlled visibility (admin: DMM → Swatches → "In cart"
    // summary badge). When the toggle is off we skip the whole
    // summary render — green-border swatch highlights still apply
    // because applyCartIndicators() runs them via a separate path.
    var summaryEnabled = (typeof omSwatchSettings !== 'undefined'
        && omSwatchSettings.customizerOptions
        && omSwatchSettings.customizerOptions.cartSummaryVisible !== false);
    if (!summaryEnabled) {
      $form.find('.om-cart-summary').remove();
      return;
    }

    if (!variationIds.length) {
      $form.find('.om-cart-summary').remove();
      return;
    }

    // Remove existing summary
    $form.find('.om-cart-summary').remove();

    // Build summary HTML
    var totalItems = 0;
    var summaryItems = [];

    for (var variationId in productCartData) {
      if (productCartData.hasOwnProperty(variationId)) {
        var itemData = productCartData[variationId];
        // Handle both old format (just qty number) and new format ({qty, name})
        var qty = typeof itemData === 'object' ? (itemData.qty || 0) : itemData;
        var name = typeof itemData === 'object' ? itemData.name : null;

        totalItems += qty;
        summaryItems.push({
          id: variationId,
          qty: qty,
          name: name
        });
      }
    }

    if (!totalItems) {
      return;
    }

    // Get i18n strings
    var i18n = (typeof omSwatchSettings !== 'undefined' && omSwatchSettings.i18n) ? omSwatchSettings.i18n : {};

    // Check if we're on an archive page (compact mode)
    var isArchive = $form.closest('.om-product-swatches--archive').length > 0;

    // Create summary element
    var $summary = jQuery('<div class="om-cart-summary"></div>');

    // Build header text - shorter for archive pages
    var variationCount = summaryItems.length;
    var itemWord = totalItems === 1 ? (i18n.item || 'item') : (i18n.items || 'items');
    var summaryText;

    if (isArchive) {
      // Compact text for archive: "3 items in cart"
      summaryText = totalItems + ' ' + itemWord + ' ' + (i18n.inCart || 'in cart');
    } else {
      // Full text for single product: "10 items in 3 variations of this product in cart"
      var variationWord = variationCount === 1 ? (i18n.variationOf || 'variation of this product') : (i18n.variationsOf || 'variations of this product');
      summaryText = totalItems + ' ' + itemWord + ' ' + (i18n.in || 'in') + ' ' + variationCount + ' ' + variationWord + ' ' + (i18n.inCart || 'in cart');
    }

    var $summaryContent = jQuery('<div class="om-cart-summary-content"></div>');
    $summaryContent.append('<span class="om-cart-summary-icon">&#10003;</span>');
    $summaryContent.append('<span class="om-cart-summary-text">' + summaryText + '</span>');

    // Add "view" link to expand details
    if (summaryItems.length > 0) {
      var viewText = i18n.viewDetails || 'View details';
      var $viewLink = jQuery('<a href="#" class="om-cart-summary-toggle" data-view-text="' + viewText + '" data-hide-text="' + (i18n.hideDetails || 'Hide details') + '">' + viewText + '</a>');
      $summaryContent.append($viewLink);
    }

    $summary.append($summaryContent);

    // Create details section (hidden by default)
    var $details = jQuery('<div class="om-cart-summary-details" style="display: none;"></div>');

    // Calculate subtotal for items in cart - skip on archive pages (not needed)
    var subtotal = 0;
    if (!isArchive) {
      subtotal = self.calculateCartSubtotal(summaryItems, $form);
    }

    // We'll need variation data to show names - fetch asynchronously
    self.loadVariationNames(summaryItems, $details, subtotal, i18n);

    $summary.append($details);

    // Toggle details on click
    $summary.on('click', '.om-cart-summary-toggle', function(e) {
      e.preventDefault();
      var $toggle = jQuery(this);
      var $detailsEl = $summary.find('.om-cart-summary-details');

      $detailsEl.slideToggle(200, function() {
        // Update text AFTER animation completes based on new state
        var newVisible = $detailsEl.is(':visible');
        $toggle.text(newVisible ? $toggle.data('hide-text') : $toggle.data('view-text'));
      });
    });

    // Insert before the variations table
    var $variationsTable = $form.find('.variations');
    if ($variationsTable.length) {
      $variationsTable.before($summary);
    } else {
      $form.prepend($summary);
    }
  },

  /**
   * Calculate subtotal for cart items
   */
  calculateCartSubtotal: function(items, $form) {
    var subtotal = 0;

    // Try to get variations data from form
    if (!$form || !$form.length) {
      $form = jQuery('.variations_form').first();
    }
    var variations = $form.data('product_variations');

    if (variations && Array.isArray(variations)) {
      items.forEach(function(item) {
        var variationId = parseInt(item.id, 10);
        for (var i = 0; i < variations.length; i++) {
          if (parseInt(variations[i].variation_id, 10) === variationId) {
            var price = parseFloat(variations[i].display_price) || 0;
            subtotal += price * item.qty;
            break;
          }
        }
      });
    }

    return subtotal;
  },

  /**
   * Build variation details list with subtotal
   */
  loadVariationNames: function(items, $container, subtotal, i18n) {
    var self = this;
    i18n = i18n || {};

    // Try to get product_variations data from the form
    var $form = $container.closest('form.variations_form');
    var productVariations = $form.length ? $form.data('product_variations') : null;

    var html = '<ul class="om-cart-summary-list">';

    items.forEach(function(item) {
      var displayName = item.name || null;
      var variationId = parseInt(item.id, 10);

      // If name looks like a slug (contains only lowercase, numbers, dashes), try to find better name
      if (!displayName || /^[a-z0-9-]+$/.test(displayName.replace(/\s+/g, '-').toLowerCase())) {
        // Try to find name from product_variations data
        if (productVariations && Array.isArray(productVariations)) {
          for (var i = 0; i < productVariations.length; i++) {
            if (parseInt(productVariations[i].variation_id, 10) === variationId) {
              // Use variation_description if available (contains the proper name)
              if (productVariations[i].variation_description) {
                displayName = productVariations[i].variation_description;
                break;
              }
            }
          }
        }

        // Try to find name from swatch element's data-original-text
        if (!displayName || /^[a-z0-9-]+$/.test(displayName.replace(/\s+/g, '-').toLowerCase())) {
          var $swatch = $form.find('.om-variation-item[data-variation-id="' + variationId + '"], .om-variation-item').filter(function() {
            // Check if this swatch corresponds to the variation
            var swatchValue = jQuery(this).attr('value');
            if (productVariations && Array.isArray(productVariations)) {
              for (var j = 0; j < productVariations.length; j++) {
                if (parseInt(productVariations[j].variation_id, 10) === variationId) {
                  var attrs = productVariations[j].attributes || {};
                  for (var attrKey in attrs) {
                    if (attrs[attrKey] === swatchValue) {
                      return true;
                    }
                  }
                }
              }
            }
            return false;
          }).first();

          if ($swatch.length && $swatch.data('original-text')) {
            displayName = $swatch.data('original-text');
          }
        }
      }

      // Final fallback
      if (!displayName) {
        displayName = 'Variation #' + item.id;
      }

      html += '<li class="om-cart-summary-item">';
      html += '<span class="om-cart-item-name">' + self.escapeHtml(displayName) + '</span>';
      html += '<span class="om-cart-item-qty"> × ' + item.qty + '</span>';
      html += '</li>';
    });

    html += '</ul>';

    // Add subtotal if available
    if (subtotal > 0) {
      var formattedSubtotal = omProductSwatches.multiSelectHandler.formatPrice(subtotal);
      html += '<div class="om-cart-summary-subtotal">' + (i18n.subtotal || 'Subtotal') + ': <strong>' + formattedSubtotal + '</strong></div>';
    }

    $container.html(html);
  },

  /**
   * Escape HTML for safe display
   */
  escapeHtml: function(text) {
    if (!text) return '';
    var div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
  },

  /**
   * Mark a swatch as being in the cart
   */
  markSwatchInCart: function($swatch, quantity) {
    $swatch.addClass('om-in-cart');

    // Add or update the cart indicator badge
    var $badge = $swatch.find('.om-cart-badge');
    if (!$badge.length) {
      $badge = jQuery('<span class="om-cart-badge"></span>');
      $swatch.append($badge);
    }
    $badge.text(quantity).attr('title', quantity + ' ' + (omSwatchSettings.i18n.inCart || 'in cart'));
  },

  /**
   * Remove cart indicator from a swatch
   */
  unmarkSwatchInCart: function($swatch) {
    $swatch.removeClass('om-in-cart');
    $swatch.find('.om-cart-badge').remove();
  },

  /**
   * Mark the active swatch when single add-to-cart is triggered
   * Provides immediate visual feedback for non-bulk mode add to cart
   * Works with both WooCommerce adding_to_cart and Botiga added_to_cart events
   */
  markActiveSwatchOnAddToCart: function($button, data) {
    // Skip if bulk mode (multi-select) - it has its own handling
    if (this.batchAddInProgress) {
      this.log('markActiveSwatchOnAddToCart: Skipped - batch add in progress');
      return false;
    }

    this.log('markActiveSwatchOnAddToCart: Starting. Button exists:', !!($button && $button.length));

    // Find the form containing the add-to-cart button
    // Try multiple selectors for compatibility with different themes
    var $form = null;
    if ($button && $button.length) {
      $form = $button.closest('.variations_form');
      this.log('markActiveSwatchOnAddToCart: Try 1 (closest variations_form):', $form.length);

      // Botiga uses form.cart which contains variations_form
      if (!$form.length) {
        $form = $button.closest('form.cart').find('.variations_form');
        this.log('markActiveSwatchOnAddToCart: Try 2 (form.cart > variations_form):', $form.length);
      }
      // Direct parent form check
      if (!$form.length) {
        $form = $button.closest('form.cart');
        this.log('markActiveSwatchOnAddToCart: Try 3 (closest form.cart):', $form.length);
      }
      // Archive page: button is sibling to .om-product-swatches which contains the form
      if (!$form.length) {
        var $swatchesWrapper = $button.siblings('.om-product-swatches');
        this.log('markActiveSwatchOnAddToCart: Try 4 (sibling .om-product-swatches):', $swatchesWrapper.length);
        if ($swatchesWrapper.length) {
          $form = $swatchesWrapper.find('.variations_form');
          this.log('markActiveSwatchOnAddToCart: Try 4 found form:', $form.length);
        }
      }
      // Archive page alternative: button and swatches are in same parent container
      if (!$form.length) {
        var $container = $button.closest('.loop-button-wrap, .product, .wc-block-grid__product');
        this.log('markActiveSwatchOnAddToCart: Try 5 (container):', $container.length, $container.attr('class'));
        if ($container.length) {
          $form = $container.find('.variations_form');
          this.log('markActiveSwatchOnAddToCart: Try 5 found form:', $form.length);
        }
      }
    }

    // If no form found from button, try to find from data
    if (!$form || !$form.length) {
      // Try to find form by product_id from data (single product page)
      if (data && data.product_id) {
        $form = jQuery('.variations_form[data-product_id="' + data.product_id + '"]');
        this.log('markActiveSwatchOnAddToCart: Try 6 (form by data.product_id):', $form.length);
      }
    }

    // On archive pages, data.product_id is actually the VARIATION ID
    // Find the form that contains this variation
    if (!$form || !$form.length) {
      if (data && data.product_id) {
        var variationIdToFind = data.product_id;
        this.log('markActiveSwatchOnAddToCart: Try 7 (find form by variation_id in data):', variationIdToFind);
        jQuery('.variations_form').each(function() {
          var $f = jQuery(this);
          var variations = $f.data('product_variations');
          if (variations && Array.isArray(variations)) {
            for (var i = 0; i < variations.length; i++) {
              if (String(variations[i].variation_id) === String(variationIdToFind)) {
                $form = $f;
                return false; // break out of each loop
              }
            }
          }
        });
        this.log('markActiveSwatchOnAddToCart: Try 7 result:', $form ? $form.length : 0);
      }
    }

    // Fallback: find active form on page (single product page)
    if (!$form || !$form.length) {
      $form = jQuery('.variations_form').first();
      this.log('markActiveSwatchOnAddToCart: Try 8 (first form fallback):', $form.length);
    }

    if (!$form || !$form.length) {
      this.log('markActiveSwatchOnAddToCart: No form found');
      return false;
    }

    // Get product ID - try multiple sources
    var productId = $form.data('product_id');
    if (!productId && data && data.product_id) {
      productId = data.product_id;
    }
    if (!productId) {
      // Try to get from form input
      productId = $form.find('input[name="product_id"]').val();
    }

    // Get variation ID - try multiple sources
    var variationId = null;
    if (data && data.variation_id) {
      variationId = data.variation_id;
    }
    if (!variationId) {
      variationId = $form.find('input[name="variation_id"]').val();
    }
    // On archive pages, the button's data-product_id IS the variation_id
    // Check if it's different from the form's product_id (which is the parent)
    if (!variationId && $button && $button.length) {
      var buttonProductId = $button.data('product_id');
      if (buttonProductId && buttonProductId != productId) {
        variationId = buttonProductId;
      }
    }
    // Also try data.product_id if it's different from productId (WooCommerce archive AJAX)
    if (!variationId && data && data.product_id && data.product_id != productId) {
      variationId = data.product_id;
    }

    // Get quantity
    var quantity = 1;
    if (data && data.quantity) {
      quantity = data.quantity;
    } else {
      var qtyInput = $form.find('input[name="quantity"]');
      if (qtyInput.length) {
        quantity = qtyInput.val() || 1;
      }
    }

    this.log('markActiveSwatchOnAddToCart: Product ID:', productId, 'Variation ID:', variationId, 'Qty:', quantity);

    if (!variationId || variationId === '0') {
      this.log('markActiveSwatchOnAddToCart: No valid variation ID');
      return false;
    }

    // Prevent double-processing: check if this variation was recently marked
    var recentKey = productId + '_' + variationId;
    var now = Date.now();
    if (this.recentlyMarkedVariations[recentKey] && (now - this.recentlyMarkedVariations[recentKey]) < 2000) {
      this.log('markActiveSwatchOnAddToCart: Skipping - variation', variationId, 'was recently marked (within 2s)');
      return false;
    }
    // Mark this variation as recently processed
    this.recentlyMarkedVariations[recentKey] = now;

    // Clean up old entries (older than 5 seconds) to prevent memory leak
    var self = this;
    setTimeout(function() {
      delete self.recentlyMarkedVariations[recentKey];
    }, 5000);

    // Calculate the new quantity (existing + adding)
    var existingQty = 0;
    if (productId && this.cartData[productId] && this.cartData[productId][variationId]) {
      var cartItem = this.cartData[productId][variationId];
      existingQty = typeof cartItem === 'object' ? (cartItem.qty || 0) : cartItem;
    }
    var newQty = parseInt(existingQty) + parseInt(quantity);

    // Try to find variation data to identify which swatches to mark
    var variations = $form.data('product_variations');
    if (typeof variations === 'string') {
      try {
        variations = JSON.parse(variations);
      } catch(e) {
        variations = null;
      }
    }

    var swatchesMarked = false;

    // Method 1: Use variations data to find swatches by attribute values
    if (variations && Array.isArray(variations)) {
      var targetVariation = null;
      for (var i = 0; i < variations.length; i++) {
        if (String(variations[i].variation_id) === String(variationId)) {
          targetVariation = variations[i];
          break;
        }
      }

      if (targetVariation) {
        var attributes = targetVariation.attributes || {};
        for (var attrKey in attributes) {
          if (!attributes.hasOwnProperty(attrKey)) continue;

          var attrValue = attributes[attrKey];
          if (!attrValue) continue;

          // Find the swatch with this attribute value
          var $swatch = $form.find('.om-variation-item[value="' + attrValue + '"]');

          // Try case-insensitive match if not found
          if (!$swatch.length) {
            $swatch = $form.find('.om-variation-item').filter(function() {
              var val = jQuery(this).attr('value');
              return val && val.toLowerCase() === attrValue.toLowerCase();
            });
          }

          if ($swatch.length) {
            this.log('markActiveSwatchOnAddToCart: Marking swatch', attrValue, 'with qty', newQty);
            this.markSwatchInCart($swatch, newQty);
            swatchesMarked = true;
          }
        }
      }
    }

    // Method 2: Fallback - mark currently active swatches directly
    // This works when variations data isn't available or lookup failed
    if (!swatchesMarked) {
      this.log('markActiveSwatchOnAddToCart: Using fallback - marking active swatches');
      var self = this;
      $form.find('.om-variation-item.active').each(function() {
        self.log('markActiveSwatchOnAddToCart: Marking active swatch with qty', newQty);
        self.markSwatchInCart(jQuery(this), newQty);
        swatchesMarked = true;
      });
    }

    // Update local cart data optimistically if we marked any swatches
    if (swatchesMarked && productId) {
      if (!this.cartData[productId]) {
        this.cartData[productId] = {};
      }

      // Build variation name from active swatches or variation attributes
      var variationName = this.getVariationNameFromForm($form, variationId, variations);

      this.cartData[productId][variationId] = { qty: newQty, name: variationName };

      // Also update the cart summary box immediately
      this.showCartSummary($form, this.cartData[productId]);

      // Update Botiga in-cart-flag button text (shows "X variations in cart")
      this.updateBotigaInCartButton($form, productId);
    }

    return swatchesMarked;
  },

  /**
   * Get variation name from form by reading active swatches or variation attributes
   * Used for optimistic cart data updates
   */
  getVariationNameFromForm: function($form, variationId, variations) {
    var nameparts = [];

    // Method 1: Get names from currently active swatches
    $form.find('.om-variation-item.active').each(function() {
      var $swatch = jQuery(this);
      var text = '';

      // Try tooltip first (usually has the clean name)
      text = $swatch.attr('title') || '';

      // Try data-original-text
      if (!text) {
        text = $swatch.attr('data-original-text') || '';
      }

      // Try .om-variation-text element
      if (!text) {
        var $textEl = $swatch.find('.om-variation-text');
        if ($textEl.length) {
          text = $textEl.attr('data-original-text') || $textEl.text() || '';
        }
      }

      // Fallback to swatch text content
      if (!text) {
        text = $swatch.text().trim();
      }

      if (text && text.trim()) {
        nameparts.push(text.trim());
      }
    });

    if (nameparts.length > 0) {
      return nameparts.join(', ');
    }

    // Method 2: Build from variation attributes using variations data
    if (variations && Array.isArray(variations)) {
      for (var i = 0; i < variations.length; i++) {
        if (String(variations[i].variation_id) === String(variationId)) {
          var variation = variations[i];
          var attributes = variation.attributes || {};

          for (var attrKey in attributes) {
            if (!attributes.hasOwnProperty(attrKey)) continue;
            var attrValue = attributes[attrKey];
            if (!attrValue) continue;

            // Try to find readable name from swatch or select
            var readableName = this.getReadableNameForAttribute($form, attrKey, attrValue);
            if (readableName) {
              nameparts.push(readableName);
            }
          }
          break;
        }
      }
    }

    return nameparts.join(', ');
  },

  /**
   * Get human-readable name for an attribute value
   */
  getReadableNameForAttribute: function($form, attrKey, attrValue) {
    // Try to find from the hidden select dropdown option text
    var selectName = attrKey.replace('attribute_', '');
    var $select = $form.find('select[name="' + attrKey + '"], select[data-attribute_name="' + selectName + '"]');
    if ($select.length) {
      var $option = $select.find('option[value="' + attrValue + '"]');
      if ($option.length && $option.text().trim()) {
        return $option.text().trim();
      }
    }

    // Try to find from swatch with matching value
    var $swatch = $form.find('.om-variation-item[value="' + attrValue + '"]');
    if ($swatch.length) {
      // Try tooltip
      var tooltip = $swatch.attr('title');
      if (tooltip && tooltip.trim()) {
        return tooltip.trim();
      }

      // Try data-original-text
      var originalText = $swatch.attr('data-original-text');
      if (originalText && originalText.trim()) {
        return originalText.trim();
      }

      // Try .om-variation-text element
      var $textEl = $swatch.find('.om-variation-text');
      if ($textEl.length) {
        var textContent = $textEl.attr('data-original-text') || $textEl.text();
        if (textContent && textContent.trim()) {
          return textContent.trim();
        }
      }
    }

    // Fallback: clean up slug (replace dashes with spaces, capitalize)
    return attrValue.replace(/-/g, ' ').replace(/\b\w/g, function(l) { return l.toUpperCase(); });
  },

  /**
   * Update Botiga Pro's in-cart-flag button text
   * This provides immediate feedback without waiting for server response
   */
  updateBotigaInCartButton: function($form, productId) {
    // Check if Botiga in-cart-flag is enabled (body class check)
    if (!jQuery('body').hasClass('botiga-in-cart-flag')) {
      return;
    }

    // Find the add-to-cart button - try multiple selectors
    var $button = $form.find('.single_add_to_cart_button');
    if (!$button.length) {
      $button = $form.closest('form.cart').find('.single_add_to_cart_button');
    }
    if (!$button.length) {
      // Try finding in the product wrapper
      var $wrapper = $form.closest('.product');
      if ($wrapper.length) {
        $button = $wrapper.find('.single_add_to_cart_button');
      }
    }

    if (!$button.length) {
      this.log('updateBotigaInCartButton: Button not found');
      return;
    }

    // Count variations in cart for this product
    var variationsCount = 0;
    if (this.cartData[productId]) {
      for (var varId in this.cartData[productId]) {
        if (this.cartData[productId].hasOwnProperty(varId)) {
          var item = this.cartData[productId][varId];
          var qty = typeof item === 'object' ? (item.qty || 0) : item;
          if (qty > 0) {
            variationsCount++;
          }
        }
      }
    }

    // Store original button text on first call (for restoring when cart is empty)
    if (!$button.data('om-original-text')) {
      var currentText = $button.text().trim();
      // Only store if it's not already a "X variations in cart" text
      if (!currentText.match(/^\d+\s+/)) {
        $button.data('om-original-text', currentText);
      }
    }

    // If no variations in cart, restore original button text
    if (variationsCount === 0) {
      var originalText = $button.data('om-original-text');
      if (originalText) {
        $button.text(originalText);
        $button.removeClass('added');
        this.log('updateBotigaInCartButton: Restored to "' + originalText + '"');
      }
      return;
    }

    // Build the new button text
    // Greek: "X παραλλαγές στο καλάθι" / English: "X variations in cart"
    var buttonText = variationsCount + ' ';

    // Try to get the localized suffix from existing button text
    var currentText = $button.text().trim();
    var variationTextMatch = currentText.match(/^\d+\s+(.+)$/);

    if (variationTextMatch && variationTextMatch[1]) {
      // Use the same suffix (localized text)
      buttonText += variationTextMatch[1];
    } else {
      // Fallback - use generic text or try to detect language
      if (typeof botigaInCartFlag !== 'undefined' && botigaInCartFlag.variations_text) {
        buttonText += botigaInCartFlag.variations_text;
      } else {
        // Greek fallback based on document language
        var isGreek = document.documentElement.lang && document.documentElement.lang.indexOf('el') === 0;
        buttonText += isGreek ? 'παραλλαγές στο καλάθι' : 'variations in cart';
      }
    }

    // Update button text and add visual indicator
    $button.text(buttonText);

    // Ensure the button has the "added" class for visual feedback
    if (!$button.hasClass('added')) {
      $button.addClass('added');
    }

    this.log('updateBotigaInCartButton: Updated to "' + buttonText + '"');
  },

  /**
   * Apply cart indicators to dropdown mode (multi-select)
   */
  applyDropdownCartIndicators: function($form, productCartData) {
    var self = this;

    this.log('applyDropdownCartIndicators called');

    // Find multi-select container - try multiple selectors
    var $container = $form.find('.om-multi-select-dropdown-container');
    if (!$container.length) {
      $container = $form.find('.om-multi-select-items-list');
    }
    if (!$container.length) {
      // Also search within the whole form for any dropdown items
      $container = $form;
    }

    var $items = $container.find('.om-multi-select-dropdown-item');
    this.log('Found', $items.length, 'dropdown items to process');

    // Update dropdown items
    $items.each(function() {
      var $item = jQuery(this);
      var variationId = $item.data('variation-id');

      self.log('Checking dropdown item variation ID:', variationId, 'in cart:', !!productCartData[variationId]);

      if (variationId && productCartData[variationId]) {
        var cartItem = productCartData[variationId];
        // Handle both formats: {qty: X, name: Y} or just a number
        var quantity = typeof cartItem === 'object' ? (cartItem.qty || 0) : cartItem;

        self.log('Marking variation', variationId, 'as in-cart with qty:', quantity);

        $item.addClass('om-in-cart om-was-in-cart');
      } else {
        $item.removeClass('om-in-cart om-was-in-cart');
      }
    });
  },

  /**
   * Get cart quantity for a specific variation
   */
  getVariationCartQuantity: function(productId, variationId) {
    if (this.cartData[productId] && this.cartData[productId][variationId]) {
      return this.cartData[productId][variationId];
    }
    return 0;
  }
};

// Initialize Cart Sync on document ready
jQuery(document).ready(function() {
  omProductSwatches.cartSync.init();

  // Also re-apply indicators when WooCommerce variation forms are fully loaded
  // This handles the case where product_variations data loads asynchronously
  jQuery('.variations_form').on('wc_variation_form', function() {
    omProductSwatches.cartSync.log('wc_variation_form event fired - re-applying indicators');
    omProductSwatches.cartSync.applyCartIndicators();
  });

  // Listen for swatches being initialized
  jQuery(document).on('om.swatches.refresh', function() {
    omProductSwatches.cartSync.log('om.swatches.refresh event - re-applying indicators');
    setTimeout(function() {
      omProductSwatches.cartSync.applyCartIndicators();
    }, 100);
  });
});

/**
 * Variation caption under the product title — cache-driven + block-theme safe.
 *
 * The legacy `displayVariationNameOnProductTitle` hangs off the single-product
 * swatch object, which does NOT initialise when the page renders swatches with
 * the `--archive` class (block themes like roosterx-ff). It is also driven by
 * `getVariationName` which, for image swatches with the tooltip off, had no text
 * to read. This instead reads the **preloaded `data-swatch-label`** that the
 * server stamps onto every swatch straight from the swatch cache
 * (`variations_json` -> "<description> - <attribute>", e.g. "No 40 Coral Red -
 * Ανοιχτό Τριανταφυλλί"). So the caption is set INSTANTLY on click from cached
 * data — no `found_variation` wait, no per-variation AJAX lookup. Gated by the
 * same "display variation name on product title" setting; the title selector
 * also covers block themes (.wp-block-post-title / .ff-product-title) which have
 * no .entry-summary / .product_title.
 */
jQuery(function ($) {
  if (typeof omSwatchSettings === 'undefined' || !omSwatchSettings.customizerOptions) { return; }
  if (omSwatchSettings.customizerOptions.displayVariationNameOnProductTitle == 0) { return; }
  if (!$('body').hasClass('single-product')) { return; }
  var titleSel = '.entry-summary .product_title, h1.product_title, .ff-product-title, .wp-block-post-title';
  function setCaption(text) {
    var $title = $(titleSel).first();
    if (!$title.length) { return; }
    $title.find('.om-ptitle-variation-name').remove();
    if (text) { $title.append(' <span class="om-ptitle-variation-name">' + $('<div>').text(text).html() + '</span>'); }
  }
  // Instant, cache-driven: the clicked swatch already carries the cached
  // "description - attribute" label. Re-read .active after the selection
  // handler has run so de-selecting (clicking the active swatch again) clears.
  $(document).on('click.omTitleDesc', '.om-variation-item', function () {
    var $sw = $(this);
    if ($sw.hasClass('disabled') || $sw.hasClass('permanent-disabled')) { return; }
    var label = $sw.attr('data-swatch-label') || '';
    setTimeout(function () { setCaption($sw.hasClass('active') ? label : ''); }, 0);
  });
  // Clear when WooCommerce resets the form (e.g. "Clear" / no selection).
  $(document).on('reset_data.omTitleDesc', '.variations_form', function () { setCaption(''); });
});
