text: /**
   * Copyright © Magento, Inc. All rights reserved.
   * See COPYING.txt for license details.
   */
  
  define([
      'jquery',
      'underscore',
      'mage/template',
      'mage/smart-keyboard-handler',
      'mage/translate',
      'priceUtils',
      'jquery/ui',
      'jquery/jquery.parsequery',
      'mage/validation/validation'
  ], function ($, _, mageTemplate, keyboardHandler, $t, priceUtils) {
      'use strict';
  
      /**
       * Extend form validation to support swatch accessibility
       */
      $.widget('mage.validation', $.mage.validation, {
          /**
           * Handle form with swatches validation. Focus on first invalid swatch block.
           *
           * @param {jQuery.Event} event
           * @param {Object} validation
           */
          listenFormValidateHandler: function (event, validation) {
              var swatchWrapper, firstActive, swatches, swatch, successList, errorList, firstSwatch;
  
              this._superApply(arguments);
  
              swatchWrapper = '.swatch-attribute-options';
              swatches = $(event.target).find(swatchWrapper);
  
              if (!swatches.length) {
                  return;
              }
  
              swatch = '.swatch-attribute';
              firstActive = $(validation.errorList[0].element || []);
              successList = validation.successList;
              errorList = validation.errorList;
              firstSwatch = $(firstActive).parent(swatch).find(swatchWrapper);
  
              keyboardHandler.focus(swatches);
  
              $.each(successList, function (index, item) {
                  $(item).parent(swatch).find(swatchWrapper).attr('aria-invalid', false);
              });
  
              $.each(errorList, function (index, item) {
                  $(item.element).parent(swatch).find(swatchWrapper).attr('aria-invalid', true);
              });
  
              if (firstSwatch.length) {
                  $(firstSwatch).focus();
              }
          }
      });
  
      /**
       * Render tooltips by attributes (only to up).
       * Required element attributes:
       *  - option-type (integer, 0-3)
       *  - option-label (string)
       *  - option-tooltip-thumb
       *  - option-tooltip-value
       *  - thumb-width
       *  - thumb-height
       */
      $.widget('mage.SwatchRendererTooltip', {
          options: {
              delay: 200,                             //how much ms before tooltip to show
              tooltipClass: 'swatch-option-tooltip'  //configurable, but remember about css
          },
  
          /**
           * @private
           */
          _init: function () {
              var $widget = this,
                  $this = this.element,
                  $element = $('.' + $widget.options.tooltipClass),
                  timer,
                  type = parseInt($this.attr('option-type'), 10),
                  label = $this.attr('option-label'),
                  thumb = $this.attr('option-tooltip-thumb'),
                  value = $this.attr('option-tooltip-value'),
                  width = $this.attr('thumb-width'),
                  height = $this.attr('thumb-height'),
                  $image,
                  $title,
                  $corner;
  
              if (!$element.length) {
                  $element = $('<div class="' +
                      $widget.options.tooltipClass +
                      '"><div class="image"></div><div class="title"></div><div class="corner"></div></div>'
                  );
                  $('body').append($element);
              }
  
              $image = $element.find('.image');
              $title = $element.find('.title');
              $corner = $element.find('.corner');
  
              $this.hover(function () {
                  if (!$this.hasClass('disabled')) {
                      timer = setTimeout(
                          function () {
                              var leftOpt = null,
                                  leftCorner = 0,
                                  left,
                                  $window;
  
                              if (type === 2) {
                                  // Image
                                  $image.css({
                                      'background': 'url("' + thumb + '") no-repeat center', //Background case
                                      'background-size': 'initial',
                                      'width': width + 'px',
                                      'height': height + 'px'
                                  });
                                  $image.show();
                              } else if (type === 1) {
                                  // Color
                                  $image.css({
                                      background: value
                                  });
                                  $image.show();
                              } else if (type === 0 || type === 3) {
                                  // Default
                                  $image.hide();
                              }
  
                              $title.text(label);
  
                              leftOpt = $this.offset().left;
                              left = leftOpt + $this.width() / 2 - $element.width() / 2;
                              $window = $(window);
  
                              // the numbers (5 and 5) is magick constants for offset from left or right page
                              if (left < 0) {
                                  left = 5;
                              } else if (left + $element.width() > $window.width()) {
                                  left = $window.width() - $element.width() - 5;
                              }
  
                              // the numbers (6,  3 and 18) is magick constants for offset tooltip
                              leftCorner = 0;
  
                              if ($element.width() < $this.width()) {
                                  leftCorner = $element.width() / 2 - 3;
                              } else {
                                  leftCorner = (leftOpt > left ? leftOpt - left : left - leftOpt) + $this.width() / 2 - 6;
                              }
  
                              $corner.css({
                                  left: leftCorner
                              });
                              $element.css({
                                  left: left,
                                  top: $this.offset().top - $element.height() - $corner.height() - 18
                              }).show();
                          },
                          $widget.options.delay
                      );
                  }
              }, function () {
                  $element.hide();
                  clearTimeout(timer);
              });
  
              $(document).on('tap', function () {
                  $element.hide();
                  clearTimeout(timer);
              });
  
              $this.on('tap', function (event) {
                  event.stopPropagation();
              });
          }
      });
  
      /**
       * Render swatch controls with options and use tooltips.
       * Required two json:
       *  - jsonConfig (magento's option config)
       *  - jsonSwatchConfig (swatch's option config)
       *
       *  Tuning:
       *  - numberToShow (show "more" button if options are more)
       *  - onlySwatches (hide selectboxes)
       *  - moreButtonText (text for "more" button)
       *  - selectorProduct (selector for product container)
       *  - selectorProductPrice (selector for change price)
       */
      $.widget('mage.SwatchRenderer', {
          options: {
              classes: {
                  attributeClass: 'swatch-attribute',
                  attributeLabelClass: 'swatch-attribute-label',
                  attributeSelectedOptionLabelClass: 'swatch-attribute-selected-option',
                  attributeOptionsWrapper: 'swatch-attribute-options',
                  attributeInput: 'swatch-input',
                  optionClass: 'swatch-option',
                  selectClass: 'swatch-select',
                  moreButton: 'swatch-more',
                  loader: 'swatch-option-loading'
              },
              // option's json config
              jsonConfig: {},
  
              // swatch's json config
              jsonSwatchConfig: {},
  
              // selector of parental block of prices and swatches (need to know where to seek for price block)
              selectorProduct: '.product-info-main',
  
              // selector of price wrapper (need to know where set price)
              selectorProductPrice: '[data-role=priceBox]',
  
              //selector of product images gallery wrapper
              mediaGallerySelector: '[data-gallery-role=gallery-placeholder]',
  
              // selector of category product tile wrapper
              selectorProductTile: '.product-item',
  
              // number of controls to show (false or zero = show all)
              numberToShow: false,
  
              // show only swatch controls
              onlySwatches: false,
  
              // enable label for control
              enableControlLabel: true,
  
              // control label id
              controlLabelId: '',
  
              // text for more button
              moreButtonText: $t('More'),
  
              // Callback url for media
              mediaCallback: '',
  
              // Local media cache
              mediaCache: {},
  
              // Cache for BaseProduct images. Needed when option unset
              mediaGalleryInitial: [{}],
  
              // Use ajax to get image data
              useAjax: false,
  
              /**
               * Defines the mechanism of how images of a gallery should be
               * updated when user switches between configurations of a product.
               *
               * As for now value of this option can be either 'replace' or 'prepend'.
               *
               * @type {String}
               */
              gallerySwitchStrategy: 'replace',
  
              // whether swatches are rendered in product list or on product page
              inProductList: false,
  
              // sly-old-price block selector
              slyOldPriceSelector: '.sly-old-price',
  
              // tier prise selectors start
              tierPriceTemplateSelector: '#tier-prices-template',
              tierPriceBlockSelector: '[data-role="tier-price-block"]',
              tierPriceTemplate: '',
              // tier prise selectors end
  
              // A price label selector
              normalPriceLabelSelector: '.normal-price .price-label'
          },
  
          /**
           * Get chosen product
           *
           * @returns int|null
           */
          getProduct: function () {
              var products = this._CalcProducts();
  
              return _.isArray(products) ? products[0] : null;
          },
  
          /**
           * @private
           */
          _init: function () {
              if (_.isEmpty(this.options.jsonConfig.images)) {
                  this.options.useAjax = true;
                  // creates debounced variant of _LoadProductMedia()
                  // to use it in events handlers instead of _LoadProductMedia()
                  this._debouncedLoadProductMedia = _.debounce(this._LoadProductMedia.bind(this), 500);
              }
  
              if (this.options.jsonConfig !== '' && this.options.jsonSwatchConfig !== '') {
                  // store unsorted attributes
                  this.options.jsonConfig.mappedAttributes = _.clone(this.options.jsonConfig.attributes);
                  this._sortAttributes();
                  this._RenderControls();
                  this._setPreSelectedGallery();
                  $(this.element).trigger('swatch.initialized');
              } else {
                  console.log('SwatchRenderer: No input data received');
              }
              this.options.tierPriceTemplate = $(this.options.tierPriceTemplateSelector).html();
          },
  
          /**
           * @private
           */
          _sortAttributes: function () {
              this.options.jsonConfig.attributes = _.sortBy(this.options.jsonConfig.attributes, function (attribute) {
                  return parseInt(attribute.position, 10);
              });
          },
  
          /**
           * @private
           */
          _create: function () {
              var options = this.options,
                  gallery = $('[data-gallery-role=gallery-placeholder]', '.column.main'),
                  productData = this._determineProductData(),
                  $main = productData.isInProductView ?
                      this.element.parents('.column.main') :
                      this.element.parents('.product-item-info');
  
              if (productData.isInProductView) {
                  gallery.data('gallery') ?
                      this._onGalleryLoaded(gallery) :
                      gallery.on('gallery:loaded', this._onGalleryLoaded.bind(this, gallery));
              } else {
                  options.mediaGalleryInitial = [{
                      'img': $main.find('.product-image-photo').attr('src')
                  }];
              }
  
              this.productForm = this.element.parents(this.options.selectorProductTile).find('form:first');
              this.inProductList = this.productForm.length > 0;
          },
  
          /**
           * Determine product id and related data
           *
           * @returns {{productId: *, isInProductView: bool}}
           * @private
           */
          _determineProductData: function () {
              // Check if product is in a list of products.
              var productId,
                  isInProductView = false;
  
              productId = this.element.parents('.product-item-details')
                      .find('.price-box.price-final_price').attr('data-product-id');
  
              if (!productId) {
                  // Check individual product.
                  productId = $('[name=product]').val();
                  isInProductView = productId > 0;
              }
  
              return {
                  productId: productId,
                  isInProductView: isInProductView
              };
          },
  
          /**
           * Render controls
           *
           * @private
           */
          _RenderControls: function () {
              var $widget = this,
                  container = this.element,
                  classes = this.options.classes,
                  chooseText = this.options.jsonConfig.chooseText;
  
              $widget.optionsMap = {};
  
              $.each(this.options.jsonConfig.attributes, function () {
                  var item = this,
                      controlLabelId = 'option-label-' + item.code + '-' + item.id,
                      options = $widget._RenderSwatchOptions(item, controlLabelId),
                      select = $widget._RenderSwatchSelect(item, chooseText),
                      input = $widget._RenderFormInput(item),
                      listLabel = '',
                      label = '';
  
                  // Show only swatch controls
                  if ($widget.options.onlySwatches && !$widget.options.jsonSwatchConfig.hasOwnProperty(item.id)) {
                      return;
                  }
  
                  if ($widget.options.enableControlLabel) {
                      label +=
                          '<span id="' + controlLabelId + '" class="' + classes.attributeLabelClass + '">' +
                              item.label +
                          '</span>' +
                          '<span class="' + classes.attributeSelectedOptionLabelClass + '"></span>';
                  }
  
                  if ($widget.inProductList) {
                      $widget.productForm.append(input);
                      input = '';
                      listLabel = 'aria-label="' + item.label + '"';
                  } else {
                      listLabel = 'aria-labelledby="' + controlLabelId + '"';
                  }
  
                  // Create new control
                  container.append(
                      '<div class="' + classes.attributeClass + ' ' + item.code + '" ' +
                           'attribute-code="' + item.code + '" ' +
                           'attribute-id="' + item.id + '">' +
                          label +
                          '<div aria-activedescendant="" ' +
                               'tabindex="0" ' +
                               'aria-invalid="false" ' +
                               'aria-required="true" ' +
                               'role="listbox" ' + listLabel +
                               'class="' + classes.attributeOptionsWrapper + ' clearfix">' +
                              options + select +
                          '</div>' + input +
                      '</div>'
                  );
  
                  $widget.optionsMap[item.id] = {};
  
                  // Aggregate options array to hash (key => value)
                  $.each(item.options, function () {
                      if (this.products.length > 0) {
                          $widget.optionsMap[item.id][this.id] = {
                              price: parseInt(
                                  $widget.options.jsonConfig.optionPrices[this.products[0]].finalPrice.amount,
                                  10
                              ),
                              products: this.products
                          };
                      }
                  });
              });
  
              // Connect Tooltip
              container
                  .find('[option-type="1"], [option-type="2"], [option-type="0"], [option-type="3"]')
                  .SwatchRendererTooltip();
  
              // Hide all elements below more button
              $('.' + classes.moreButton).nextAll().hide();
  
              // Handle events like click or change
              $widget._EventListener();
  
              // Rewind options
              $widget._Rewind(container);
  
              //Emulate click on all swatches from Request
              $widget._EmulateSelected($.parseQuery());
              $widget._EmulateSelected($widget._getSelectedAttributes());
          },
  
          /**
           * Render swatch options by part of config
           *
           * @param {Object} config
           * @param {String} controlId
           * @returns {String}
           * @private
           */
          _RenderSwatchOptions: function (config, controlId) {
              var optionConfig = this.options.jsonSwatchConfig[config.id],
                  optionClass = this.options.classes.optionClass,
                  sizeConfig = this.options.jsonSwatchImageSizeConfig,
                  moreLimit = parseInt(this.options.numberToShow, 10),
                  moreClass = this.options.classes.moreButton,
                  moreText = this.options.moreButtonText,
                  countAttributes = 0,
                  html = '';
  
              if (!this.options.jsonSwatchConfig.hasOwnProperty(config.id)) {
                  return '';
              }
  
              $.each(config.options, function () {
                  var id,
                      type,
                      value,
                      thumb,
                      label,
                      width,
                      height,
                      attr,
                      swatchImageWidth,
                      swatchImageHeight;
  
                  if (!optionConfig.hasOwnProperty(this.id)) {
                      return '';
                  }
  
                  // Add more button
                  if (moreLimit === countAttributes++) {
                      html += '<a href="#" class="' + moreClass + '">' + moreText + '</a>';
                  }
  
                  id = this.id;
                  type = parseInt(optionConfig[id].type, 10);
                  value = optionConfig[id].hasOwnProperty('value') ? optionConfig[id].value : '';
                  thumb = optionConfig[id].hasOwnProperty('thumb') ? optionConfig[id].thumb : '';
                  width = _.has(sizeConfig, 'swatchThumb') ? sizeConfig.swatchThumb.width : 110;
                  height = _.has(sizeConfig, 'swatchThumb') ? sizeConfig.swatchThumb.height : 90;
                  label = this.label ? this.label : '';
                  attr =
                      ' id="' + controlId + '-item-' + id + '"' +
                      ' aria-checked="false"' +
                      ' aria-describedby="' + controlId + '"' +
                      ' tabindex="0"' +
                      ' option-type="' + type + '"' +
                      ' option-id="' + id + '"' +
                      ' option-label="' + label + '"' +
                      ' aria-label="' + label + '"' +
                      ' option-tooltip-thumb="' + thumb + '"' +
                      ' option-tooltip-value="' + value + '"' +
                      ' role="option"' +
                      ' thumb-width="' + width + '"' +
                      ' thumb-height="' + height + '"';
  
                  swatchImageWidth = _.has(sizeConfig, 'swatchImage') ? sizeConfig.swatchImage.width : 30;
                  swatchImageHeight = _.has(sizeConfig, 'swatchImage') ? sizeConfig.swatchImage.height : 20;
  
                  if (!this.hasOwnProperty('products') || this.products.length <= 0) {
                      attr += ' option-empty="true"';
                  }
  
                  if (type === 0) {
                      // Text
                      html += '<div class="' + optionClass + ' text" ' + attr + '>' + (value ? value : label) +
                          '</div>';
                  } else if (type === 1) {
                      // Color
                      html += '<div class="' + optionClass + ' color" ' + attr +
                          ' style="background: ' + value +
                          ' no-repeat center; background-size: initial;">' + '' +
                          '</div>';
                  } else if (type === 2) {
                      // Image
                      html += '<div class="' + optionClass + ' image" ' + attr +
                          ' style="background: url(' + value + ') no-repeat center; background-size: initial;width:' +
                          swatchImageWidth + 'px; height:' + swatchImageHeight + 'px">' + '' +
                          '</div>';
                  } else if (type === 3) {
                      // Clear
                      html += '<div class="' + optionClass + '" ' + attr + '></div>';
                  } else {
                      // Default
                      html += '<div class="' + optionClass + '" ' + attr + '>' + label + '</div>';
                  }
              });
  
              return html;
          },
  
          /**
           * Render select by part of config
           *
           * @param {Object} config
           * @param {String} chooseText
           * @returns {String}
           * @private
           */
          _RenderSwatchSelect: function (config, chooseText) {
              var html;
  
              if (this.options.jsonSwatchConfig.hasOwnProperty(config.id)) {
                  return '';
              }
  
              html =
                  '<select class="' + this.options.classes.selectClass + ' ' + config.code + '">' +
                  '<option value="0" option-id="0">' + chooseText + '</option>';
  
              $.each(config.options, function () {
                  var label = this.label,
                      attr = ' value="' + this.id + '" option-id="' + this.id + '"';
  
                  if (!this.hasOwnProperty('products') || this.products.length <= 0) {
                      attr += ' option-empty="true"';
                  }
  
                  html += '<option ' + attr + '>' + label + '</option>';
              });
  
              html += '</select>';
  
              return html;
          },
  
          /**
           * Input for submit form.
           * This control shouldn't have "type=hidden", "display: none" for validation work :(
           *
           * @param {Object} config
           * @private
           */
          _RenderFormInput: function (config) {
              return '<input class="' + this.options.classes.attributeInput + ' super-attribute-select" ' +
                  'name="super_attribute[' + config.id + ']" ' +
                  'type="text" ' +
                  'value="" ' +
                  'data-selector="super_attribute[' + config.id + ']" ' +
                  'data-validate="{required: true}" ' +
                  'aria-required="true" ' +
                  'aria-invalid="false">';
          },
  
          /**
           * Event listener
           *
           * @private
           */
          _EventListener: function () {
              var $widget = this,
                  options = this.options.classes,
                  target;
  
              $widget.element.on('click', '.' + options.optionClass, function () {
                  return $widget._OnClick($(this), $widget);
              });
  
              $widget.element.on('change', '.' + options.selectClass, function () {
                  return $widget._OnChange($(this), $widget);
              });
  
              $widget.element.on('click', '.' + options.moreButton, function (e) {
                  e.preventDefault();
  
                  return $widget._OnMoreClick($(this));
              });
  
              $widget.element.on('keydown', function (e) {
                  if (e.which === 13) {
                      target = $(e.target);
  
                      if (target.is('.' + options.optionClass)) {
                          return $widget._OnClick(target, $widget);
                      } else if (target.is('.' + options.selectClass)) {
                          return $widget._OnChange(target, $widget);
                      } else if (target.is('.' + options.moreButton)) {
                          e.preventDefault();
  
                          return $widget._OnMoreClick(target);
                      }
                  }
              });
          },
  
          /**
           * Load media gallery using ajax or json config.
           *
           * @private
           */
          _loadMedia: function () {
              var $main = this.inProductList ?
                      this.element.parents('.product-item-info') :
                      this.element.parents('.column.main'),
                  images;
  
              if (this.options.useAjax) {
                  this._debouncedLoadProductMedia();
              }  else {
                  images = this.options.jsonConfig.images[this.getProduct()];
  
                  if (!images) {
                      images = this.options.mediaGalleryInitial;
                  }
                  this.updateBaseImage(this._sortImages(images), $main, !this.inProductList);
              }
          },
  
          /**
           * Sorting images array
           *
           * @private
           */
          _sortImages: function (images) {
              return _.sortBy(images, function (image) {
                  return image.position;
              });
          },
  
          /**
           * Event for swatch options
           *
           * @param {Object} $this
           * @param {Object} $widget
           * @private
           */
          _OnClick: function ($this, $widget) {
              var $parent = $this.parents('.' + $widget.options.classes.attributeClass),
                  $wrapper = $this.parents('.' + $widget.options.classes.attributeOptionsWrapper),
                  $label = $parent.find('.' + $widget.options.classes.attributeSelectedOptionLabelClass),
                  attributeId = $parent.attr('attribute-id'),
                  $input = $parent.find('.' + $widget.options.classes.attributeInput);
  
              if ($widget.inProductList) {
                  $input = $widget.productForm.find(
                      '.' + $widget.options.classes.attributeInput + '[name="super_attribute[' + attributeId + ']"]'
                  );
              }
  
              if ($this.hasClass('disabled')) {
                  return;
              }
  
              if ($this.hasClass('selected')) {
                  $parent.removeAttr('option-selected').find('.selected').removeClass('selected');
                  $input.val('');
                  $label.text('');
                  $this.attr('aria-checked', false);
              } else {
                  $parent.attr('option-selected', $this.attr('option-id')).find('.selected').removeClass('selected');
                  $label.text($this.attr('option-label'));
                  $input.val($this.attr('option-id'));
                  $input.attr('data-attr-name', this._getAttributeCodeById(attributeId));
                  $this.addClass('selected');
                  $widget._toggleCheckedAttributes($this, $wrapper);
              }
  
              $widget._Rebuild();
  
              if ($widget.element.parents($widget.options.selectorProduct)
                      .find(this.options.selectorProductPrice).is(':data(mage-priceBox)')
              ) {
                  $widget._UpdatePrice();
              }
  
              $widget._loadMedia();
              $input.trigger('change');
          },
  
          /**
           * Get human readable attribute code (eg. size, color) by it ID from configuration
           *
           * @param {Number} attributeId
           * @returns {*}
           * @private
           */
          _getAttributeCodeById: function (attributeId) {
              var attribute = this.options.jsonConfig.mappedAttributes[attributeId];
  
              return attribute ? attribute.code : attributeId;
          },
  
          /**
           * Toggle accessibility attributes
           *
           * @param {Object} $this
           * @param {Object} $wrapper
           * @private
           */
          _toggleCheckedAttributes: function ($this, $wrapper) {
              $wrapper.attr('aria-activedescendant', $this.attr('id'))
                      .find('.' + this.options.classes.optionClass).attr('aria-checked', false);
              $this.attr('aria-checked', true);
          },
  
          /**
           * Event for select
           *
           * @param {Object} $this
           * @param {Object} $widget
           * @private
           */
          _OnChange: function ($this, $widget) {
              var $parent = $this.parents('.' + $widget.options.classes.attributeClass),
                  attributeId = $parent.attr('attribute-id'),
                  $input = $parent.find('.' + $widget.options.classes.attributeInput);
  
              if ($widget.productForm.length > 0) {
                  $input = $widget.productForm.find(
                      '.' + $widget.options.classes.attributeInput + '[name="super_attribute[' + attributeId + ']"]'
                  );
              }
  
              if ($this.val() > 0) {
                  $parent.attr('option-selected', $this.val());
                  $input.val($this.val());
              } else {
                  $parent.removeAttr('option-selected');
                  $input.val('');
              }
  
              $widget._Rebuild();
              $widget._UpdatePrice();
              $widget._loadMedia();
              $input.trigger('change');
          },
  
          /**
           * Event for more switcher
           *
           * @param {Object} $this
           * @private
           */
          _OnMoreClick: function ($this) {
              $this.nextAll().show();
              $this.blur().remove();
          },
  
          /**
           * Rewind options for controls
           *
           * @private
           */
          _Rewind: function (controls) {
              controls.find('div[option-id], option[option-id]').removeClass('disabled').removeAttr('disabled');
              controls.find('div[option-empty], option[option-empty]').attr('disabled', true).addClass('disabled');
          },
  
          /**
           * Rebuild container
           *
           * @private
           */
          _Rebuild: function () {
              var $widget = this,
                  controls = $widget.element.find('.' + $widget.options.classes.attributeClass + '[attribute-id]'),
                  selected = controls.filter('[option-selected]');
  
              // Enable all options
              $widget._Rewind(controls);
  
              // done if nothing selected
              if (selected.length <= 0) {
                  return;
              }
  
              // Disable not available options
              controls.each(function () {
                  var $this = $(this),
                      id = $this.attr('attribute-id'),
                      products = $widget._CalcProducts(id);
  
                  if (selected.length === 1 && selected.first().attr('attribute-id') === id) {
                      return;
                  }
  
                  $this.find('[option-id]').each(function () {
                      var $element = $(this),
                          option = $element.attr('option-id');
  
                      if (!$widget.optionsMap.hasOwnProperty(id) || !$widget.optionsMap[id].hasOwnProperty(option) ||
                          $element.hasClass('selected') ||
                          $element.is(':selected')) {
                          return;
                      }
  
                      if (_.intersection(products, $widget.optionsMap[id][option].products).length <= 0) {
                          $element.attr('disabled', true).addClass('disabled');
                      }
                  });
              });
          },
  
          /**
           * Get selected product list
           *
           * @returns {Array}
           * @private
           */
          _CalcProducts: function ($skipAttributeId) {
              var $widget = this,
                  products = [];
  
              // Generate intersection of products
              $widget.element.find('.' + $widget.options.classes.attributeClass + '[option-selected]').each(function () {
                  var id = $(this).attr('attribute-id'),
                      option = $(this).attr('option-selected');
  
                  if ($skipAttributeId !== undefined && $skipAttributeId === id) {
                      return;
                  }
  
                  if (!$widget.optionsMap.hasOwnProperty(id) || !$widget.optionsMap[id].hasOwnProperty(option)) {
                      return;
                  }
  
                  if (products.length === 0) {
                      products = $widget.optionsMap[id][option].products;
                  } else {
                      products = _.intersection(products, $widget.optionsMap[id][option].products);
                  }
              });
  
              return products;
          },
  
          /**
           * Update total price
           *
           * @private
           */
          _UpdatePrice: function () {
              var $widget = this,
                  $product = $widget.element.parents($widget.options.selectorProduct),
                  $productPrice = $product.find(this.options.selectorProductPrice),
                  options = _.object(_.keys($widget.optionsMap), {}),
                  result,
                  tierPriceHtml;
  
              $widget.element.find('.' + $widget.options.classes.attributeClass + '[option-selected]').each(function () {
                  var attributeId = $(this).attr('attribute-id');
  
                  options[attributeId] = $(this).attr('option-selected');
              });
  
              result = $widget.options.jsonConfig.optionPrices[_.findKey($widget.options.jsonConfig.index, options)];
  
              $productPrice.trigger(
                  'updatePrice',
                  {
                      'prices': $widget._getPrices(result, $productPrice.priceBox('option').prices)
                  }
              );
  
              if (typeof result != 'undefined' && result.oldPrice.amount !== result.finalPrice.amount) {
                  $(this.options.slyOldPriceSelector).show();
              } else {
                  $(this.options.slyOldPriceSelector).hide();
              }
  
              if (typeof result != 'undefined' && result.tierPrices.length) {
                  if (this.options.tierPriceTemplate) {
                      tierPriceHtml = mageTemplate(
                          this.options.tierPriceTemplate,
                          {
                              'tierPrices': result.tierPrices,
                              '$t': $t,
                              'currencyFormat': this.options.jsonConfig.currencyFormat,
                              'priceUtils': priceUtils
                          }
                      );
                      $(this.options.tierPriceBlockSelector).html(tierPriceHtml).show();
                  }
              } else {
                  $(this.options.tierPriceBlockSelector).hide();
              }
  
              $(this.options.normalPriceLabelSelector).hide();
  
              _.each($('.' + this.options.classes.attributeOptionsWrapper), function (attribute) {
                  if ($(attribute).find('.' + this.options.classes.optionClass + '.selected').length === 0) {
                      if ($(attribute).find('.' + this.options.classes.selectClass).length > 0) {
                          _.each($(attribute).find('.' + this.options.classes.selectClass), function (dropdown) {
                              if ($(dropdown).val() === '0') {
                                  $(this.options.normalPriceLabelSelector).show();
                              }
                          }.bind(this));
                      } else {
                          $(this.options.normalPriceLabelSelector).show();
                      }
                  }
              }.bind(this));
          },
  
          /**
           * Get prices
           *
           * @param {Object} newPrices
           * @param {Object} displayPrices
           * @returns {*}
           * @private
           */
          _getPrices: function (newPrices, displayPrices) {
              var $widget = this,
                  optionPriceDiff = 0,
                  allowedProduct, optionPrices, basePrice, optionFinalPrice;
  
              if (_.isEmpty(newPrices)) {
                  allowedProduct = this._getAllowedProductWithMinPrice(this._CalcProducts());
                  optionPrices = this.options.jsonConfig.optionPrices;
                  basePrice = parseFloat(this.options.jsonConfig.prices.basePrice.amount);
  
                  if (!_.isEmpty(allowedProduct)) {
                      optionFinalPrice = parseFloat(optionPrices[allowedProduct].finalPrice.amount);
                      optionPriceDiff = optionFinalPrice - basePrice;
                  }
  
                  if (optionPriceDiff !== 0) {
                      newPrices  = this.options.jsonConfig.optionPrices[allowedProduct];
                  } else {
                      newPrices = $widget.options.jsonConfig.prices;
                  }
              }
  
              _.each(displayPrices, function (price, code) {
  
                  if (newPrices[code]) {
                      displayPrices[code].amount = newPrices[code].amount - displayPrices[code].amount;
                  }
              });
  
              return displayPrices;
          },
  
          /**
           * Get product with minimum price from selected options.
           *
           * @param {Array} allowedProducts
           * @returns {String}
           * @private
           */
          _getAllowedProductWithMinPrice: function (allowedProducts) {
              var optionPrices = this.options.jsonConfig.optionPrices,
                  product = {},
                  optionFinalPrice, optionMinPrice;
  
              _.each(allowedProducts, function (allowedProduct) {
                  optionFinalPrice = parseFloat(optionPrices[allowedProduct].finalPrice.amount);
  
                  if (_.isEmpty(product)) {
                      optionMinPrice = optionFinalPrice;
                      product = allowedProduct;
                  }
  
                  if (optionFinalPrice < optionMinPrice) {
                      product = allowedProduct;
                  }
              }, this);
  
              return product;
          },
  
          /**
           * Gets all product media and change current to the needed one
           *
           * @private
           */
          _LoadProductMedia: function () {
              var $widget = this,
                  $this = $widget.element,
                  productData = this._determineProductData(),
                  mediaCallData,
                  mediaCacheKey,
  
                  /**
                   * Processes product media data
                   *
                   * @param {Object} data
                   * @returns void
                   */
                  mediaSuccessCallback = function (data) {
                      if (!(mediaCacheKey in $widget.options.mediaCache)) {
                          $widget.options.mediaCache[mediaCacheKey] = data;
                      }
                      $widget._ProductMediaCallback($this, data, productData.isInProductView);
                      setTimeout(function () {
                          $widget._DisableProductMediaLoader($this);
                      }, 300);
                  };
  
              if (!$widget.options.mediaCallback) {
                  return;
              }
  
              mediaCallData = {
                  'product_id': this.getProduct()
              };
  
              mediaCacheKey = JSON.stringify(mediaCallData);
  
              if (mediaCacheKey in $widget.options.mediaCache) {
                  $widget._XhrKiller();
                  $widget._EnableProductMediaLoader($this);
                  mediaSuccessCallback($widget.options.mediaCache[mediaCacheKey]);
              } else {
                  mediaCallData.isAjax = true;
                  $widget._XhrKiller();
                  $widget._EnableProductMediaLoader($this);
                  $widget.xhr = $.ajax({
                      url: $widget.options.mediaCallback,
                      cache: true,
                      type: 'GET',
                      dataType: 'json',
                      data: mediaCallData,
                      success: mediaSuccessCallback
                  }).done(function () {
                      $widget._XhrKiller();
                  });
              }
          },
  
          /**
           * Enable loader
           *
           * @param {Object} $this
           * @private
           */
          _EnableProductMediaLoader: function ($this) {
              var $widget = this;
  
              if ($('body.catalog-product-view').length > 0) {
                  $this.parents('.column.main').find('.photo.image')
                      .addClass($widget.options.classes.loader);
              } else {
                  //Category View
                  $this.parents('.product-item-info').find('.product-image-photo')
                      .addClass($widget.options.classes.loader);
              }
          },
  
          /**
           * Disable loader
           *
           * @param {Object} $this
           * @private
           */
          _DisableProductMediaLoader: function ($this) {
              var $widget = this;
  
              if ($('body.catalog-product-view').length > 0) {
                  $this.parents('.column.main').find('.photo.image')
                      .removeClass($widget.options.classes.loader);
              } else {
                  //Category View
                  $this.parents('.product-item-info').find('.product-image-photo')
                      .removeClass($widget.options.classes.loader);
              }
          },
  
          /**
           * Callback for product media
           *
           * @param {Object} $this
           * @param {String} response
           * @param {Boolean} isInProductView
           * @private
           */
          _ProductMediaCallback: function ($this, response, isInProductView) {
              var $main = isInProductView ? $this.parents('.column.main') : $this.parents('.product-item-info'),
                  $widget = this,
                  images = [],
  
                  /**
                   * Check whether object supported or not
                   *
                   * @param {Object} e
                   * @returns {*|Boolean}
                   */
                  support = function (e) {
                      return e.hasOwnProperty('large') && e.hasOwnProperty('medium') && e.hasOwnProperty('small');
                  };
  
              if (_.size($widget) < 1 || !support(response)) {
                  this.updateBaseImage(this.options.mediaGalleryInitial, $main, isInProductView);
  
                  return;
              }
  
              images.push({
                  full: response.large,
                  img: response.medium,
                  thumb: response.small,
                  isMain: true
              });
  
              if (response.hasOwnProperty('gallery')) {
                  $.each(response.gallery, function () {
                      if (!support(this) || response.large === this.large) {
                          return;
                      }
                      images.push({
                          full: this.large,
                          img: this.medium,
                          thumb: this.small
                      });
                  });
              }
  
              this.updateBaseImage(images, $main, isInProductView);
          },
  
          /**
           * Check if images to update are initial and set their type
           * @param {Array} images
           */
          _setImageType: function (images) {
              var initial = this.options.mediaGalleryInitial[0].img;
  
              if (images[0].img === initial) {
                  images = $.extend(true, [], this.options.mediaGalleryInitial);
              } else {
                  images.map(function (img) {
                      if (!img.type) {
                          img.type = 'image';
                      }
                  });
              }
  
              return images;
          },
  
          /**
           * Update [gallery-placeholder] or [product-image-photo]
           * @param {Array} images
           * @param {jQuery} context
           * @param {Boolean} isInProductView
           */
          updateBaseImage: function (images, context, isInProductView) {
              var justAnImage = images[0],
                  initialImages = this.options.mediaGalleryInitial,
                  gallery = context.find(this.options.mediaGallerySelector).data('gallery'),
                  imagesToUpdate,
                  isInitial;
  
              if (isInProductView) {
                  imagesToUpdate = images.length ? this._setImageType($.extend(true, [], images)) : [];
                  isInitial = _.isEqual(imagesToUpdate, initialImages);
  
                  if (this.options.gallerySwitchStrategy === 'prepend' && !isInitial) {
                      imagesToUpdate = imagesToUpdate.concat(initialImages);
                  }
  
                  imagesToUpdate = this._setImageIndex(imagesToUpdate);
                  gallery.updateData(imagesToUpdate);
  
                  if (isInitial) {
                      $(this.options.mediaGallerySelector).AddFotoramaVideoEvents();
                  } else {
                      $(this.options.mediaGallerySelector).AddFotoramaVideoEvents({
                          selectedOption: this.getProduct(),
                          dataMergeStrategy: this.options.gallerySwitchStrategy
                      });
                  }
              } else if (justAnImage && justAnImage.img) {
                  context.find('.product-image-photo').attr('src', justAnImage.img);
              }
          },
  
          /**
           * Set correct indexes for image set.
           *
           * @param {Array} images
           * @private
           */
          _setImageIndex: function (images) {
              var length = images.length,
                  i;
  
              for (i = 0; length > i; i++) {
                  images[i].i = i + 1;
              }
  
              return images;
          },
  
          /**
           * Kill doubled AJAX requests
           *
           * @private
           */
          _XhrKiller: function () {
              var $widget = this;
  
              if ($widget.xhr !== undefined && $widget.xhr !== null) {
                  $widget.xhr.abort();
                  $widget.xhr = null;
              }
          },
  
          /**
           * Emulate mouse click on all swatches that should be selected
           * @param {Object} [selectedAttributes]
           * @private
           */
          _EmulateSelected: function (selectedAttributes) {
              $.each(selectedAttributes, $.proxy(function (attributeCode, optionId) {
                  var elem = this.element.find('.' + this.options.classes.attributeClass +
                      '[attribute-code="' + attributeCode + '"] [option-id="' + optionId + '"]'),
                      parentInput = elem.parent();
  
                  if (elem.hasClass('selected')) {
                      return;
                  }
  
                  if (parentInput.hasClass(this.options.classes.selectClass)) {
                      parentInput.val(optionId);
                      parentInput.trigger('change');
                  } else {
                      elem.trigger('click');
                  }
              }, this));
          },
  
          /**
           * Emulate mouse click or selection change on all swatches that should be selected
           * @param {Object} [selectedAttributes]
           * @private
           */
          _EmulateSelectedByAttributeId: function (selectedAttributes) {
              $.each(selectedAttributes, $.proxy(function (attributeId, optionId) {
                  var elem = this.element.find('.' + this.options.classes.attributeClass +
                      '[attribute-id="' + attributeId + '"] [option-id="' + optionId + '"]'),
                      parentInput = elem.parent();
  
                  if (elem.hasClass('selected')) {
                      return;
                  }
  
                  if (parentInput.hasClass(this.options.classes.selectClass)) {
                      parentInput.val(optionId);
                      parentInput.trigger('change');
                  } else {
                      elem.trigger('click');
                  }
              }, this));
          },
  
          /**
           * Get default options values settings with either URL query parameters
           * @private
           */
          _getSelectedAttributes: function () {
              var hashIndex = window.location.href.indexOf('#'),
                  selectedAttributes = {},
                  params;
  
              if (hashIndex !== -1) {
                  params = $.parseQuery(window.location.href.substr(hashIndex + 1));
  
                  selectedAttributes = _.invert(_.mapObject(_.invert(params), function (attributeId) {
                      var attribute = this.options.jsonConfig.mappedAttributes[attributeId];
  
                      return attribute ? attribute.code : attributeId;
                  }.bind(this)));
              }
  
              return selectedAttributes;
          },
  
          /**
           * Callback which fired after gallery gets initialized.
           *
           * @param {HTMLElement} element - DOM element associated with a gallery.
           */
          _onGalleryLoaded: function (element) {
              var galleryObject = element.data('gallery');
  
              this.options.mediaGalleryInitial = galleryObject.returnCurrentImages();
          },
  
          /**
           * Sets mediaCache for cases when jsonConfig contains preSelectedGallery on layered navigation result pages
           *
           * @private
           */
          _setPreSelectedGallery: function () {
              var mediaCallData;
  
              if (this.options.jsonConfig.preSelectedGallery) {
                  mediaCallData = {
                      'product_id': this.getProduct()
                  };
  
                  this.options.mediaCache[JSON.stringify(mediaCallData)] = this.options.jsonConfig.preSelectedGallery;
              }
          }
      });
  
      return $.mage.SwatchRenderer;
  });
  