$.widget('wpj.categoryFilter', {
   // Public interface
   options: {
      items: {},
      $items: null,
      $template: null,
      $filter: null,
      $reset: null,
      originalTitle: '',
      lastUrl: '',
      rootUrl: null,
      filterUrl: null,
      title: null,
      useLocalstorage: false
   },
   addItems: function(items) {
      $.extend(this.options.items, items);
   },
   updateItemsState: function(itemsState) {
      var item;

      for (var id in itemsState) {
         item = this.getItem(id);
         if (item) {
            item.updateState(itemsState[id]);
         }
      }
      this.updateReset();
   },
   initialize: function() {
      // Reset form to initial values - avoids damned Chrome stupid behavior
      var $element = this.getForm();

      $element.each(function() {
         this.reset();
      });

      this.prepareItems();
      this.initializeReset();
      this.initializeForm();
      this.updateReset();
   },
   getForm: function() {
      var $element = this.element;
      if (!$element.is('form')) {
         $element = $element.find('[data-filter="form"]');
      }
      return $element;
   },
   // Items management
   getItem: function(id) {
      id = id.split('|')[0];
      return this.options.items[id];
   },
   getItemInput: function(id) {
      var item = this.getItem(id),
         $input;

      if (item && item.$element) {
         return item.$element;
      }

      $input = this.element.find('[data-filter-input="' + id + '"]');
      if ($input.length) {
         return $input;
      }

      $input = this.element.find(':input[name="' + id + '"]');
      if ($input.length) {
         return $input;
      }

      //console.error('Cannot find item input', id, item);
      return $([]);
   },
   getItemsValues: function(changed_only) {
      var values = {},
         item;

      for (var id in this.options.items) {
         item = this.options.items[id];

         if (changed_only && !item.isChanged()) {
            continue;
         }
         values[id] = item.getValue();
      }

      return values;
   },
   setItemsValues: function(values) {
      var item;
      for (var id in this.options.items) {
         item = this.options.items[id];
         if (Object.prototype.hasOwnProperty.call(values, id)) {
            item.setValue(values[id]);
         } else {
            item.reset();
         }
      }
   },
   // its used from block.filterScript.tpl
   setUrlAndTitle: function(url, title) {
      this.options.filterUrl = url;
      this.options.title = title;

      return true;
   },
   getItemFromEvent: function(e) {
      if (!e) {
         return null;
      }

      var $this = $(e.target),
         $children = $this.find('[data-filter-input]');

      // pokud tam je BS4 button toggle, eventu handluje label
      if ($children.length > 0) {
         return this.getItem($children.data('filter-input'));
      }

      // tam je stary BS, eventu handluje input
      if ($this.data('filter-input')) {
         return this.getItem($this.data('filter-input'));
      }
   },
   serializeItems: function(changed_only) {
      var values = {},
         item;

      for (var id in this.options.items) {
         item = this.options.items[id];

         if (changed_only && (!item.visible_hash || !item.isChanged())) {
            continue;
         }

         $.extend(values, item.serialize());
      }

      return values;
   },
   getItemsTexts: function(changed_only) {
      var texts = [],
         item;

      for (var id in this.options.items) {
         item = this.options.items[id];

         if (changed_only && (!item.visible_hash || !item.isChanged())) {
            continue;
         }

         texts.push(item.getResetText());
      }

      return texts;
   },

   // Update DOM
   updateReset: function(ids) {
      if (ids == undefined) {
         ids = this.options.items;
      }

      // Update all items
      for (var id in ids) {
         ids[id].updateReset();
      }

      // Update empty state
      var isEmpty = this.options.$items.children().length == 0;
      this.options.$reset.toggleClass('empty', isEmpty);
   },
   updateCategory: function(write_history, delay, isNext) {
      var me = this;
      wpj.domUtils.resetTimer('filter-category-update', delay, function() {
         // Collect current form values
         var values = me.getItemsValues(true),
            form_values = wpj.serializer.serializeUrl(me.serializeItems()),
            loc = window.location;

         if (form_values === me.options.lastUrl) {
            return false;
         }

         var changedSerializeItems = me.serializeItems(true);

         var queryUrl = wpj.serializer.serializeUrl(changedSerializeItems);

         me.options.lastUrl = form_values;

         if (me.options.useLocalstorage) {
            // Store form values for use in next/prev products
            wpj.storage.setValue(loc.pathname, form_values);
         }

         // Load new category products and pager
         var $productList = $('[data-filter=wrapper]')
               .find('[data-reload]')
               .not('[data-reload=cart-info]'),
            dataLoaded = $.Deferred();

         if (isNext) {
            dataLoaded.done(function($html) {
               var $new = $html.find('[data-products-row], [data-loader-wrapper]');
               var $parentRow = $('[data-loader-content]');
               var $pagerTop = $('[data-pager-top]');

               wpj.domUtils.crossFade($('[data-reload="filter-script"]'), $html.find('[data-reload="filter-script"]'));
               wpj.domUtils.crossFade($parentRow, $new);

               if ($pagerTop.length) {
                  wpj.domUtils.crossFade($pagerTop, $html.find('[data-pager-top]'));
               }
            });
         } else {
            wpj.domUtils.reloadParts($productList, dataLoaded);
         }

         $.get(
            me.options.rootUrl + (me.options.rootUrl.indexOf('?') < 0 ? '?' : '') + '&xhr=1&' + form_values,
            null,
            function(data) {
               dataLoaded.resolve(wpj.domUtils.parseHtml(data));
               let titleSeparator = ' - ';
               if (me.options.originalTitle.indexOf(titleSeparator) < 0) {
                  titleSeparator = ' | ';
               }
               document.title = me.options.title + titleSeparator + me.options.originalTitle.split(titleSeparator)[1];
               // Write history
               if (write_history && window.history && window.history.pushState) {
                  window.history.pushState(
                     values,
                     me.options.title,
                     (me.options.filterUrl ? me.options.filterUrl : '') +
                     (queryUrl ? (me.options.filterUrl?.indexOf('?') > -1 ? '&' : '?') + queryUrl : '')
                  );
               }

               if (typeof window.wpj !== "undefined" && typeof window.wpj.tracking !== "undefined") {
                  window.wpj.tracking.handleImpressions();
               }
            }
         );
      });
   },

   // Events
   onResetItem: function(e) {
      var id = $(e.target)
            .closest('[data-filter-reset-item]')
            .data('filter-reset-item'),
         item = this.getItem(id);

      item.reset(id);

      this.element.change();

      return false;
   },
   onResetAll: function(e) {
      var me = this;
      // Reset all items
      $.each(this.options.items, function(index, item) {
         item.reset();
      });

      this.element.change();

      return false;
   },
   onFormInputChanged: function(e) {
      if (e && ($(e.target).data('filter') === 'noreload' || $(e.target).closest('[data-filter="noreload"]').length)) {
         return true;
      }

      this._trigger('change', e);
      this.updateCategory(true, 500);
      this.updateReset();
   },
   onHistoryChanged: function(e) {
      var values = e.originalEvent ? e.originalEvent.state : e.state;

      $.fx.off = true;

      if (values) {
         this.setItemsValues(values);
      }
      this.updateReset();
      this.updateCategory(false, 1);
      this.element.trigger('prefilled');

      for (var id in this.options.items) {
         this.options.items[id].showIfChanged();
      }

      $.fx.off = false;
   },

   // Initialization
   prepareItems: function() {
      var item, $input, type;
      for (var id in this.options.items) {
         item = this.options.items[id];
         $input = this.getItemInput(id);

         if (item && item.type) {
            type = item.type;
         } else {
            if ($input.is(':radio')) {
               type = jQuery.fn.categoryFilter.ItemRadio;
            } else if ($input.is('select')) {
               type = jQuery.fn.categoryFilter.ItemSelect;
            } else if ($input.is(':checkbox')) {
               type = jQuery.fn.categoryFilter.ItemCheckbox;
            } else {
               type = jQuery.fn.categoryFilter.Item;
            }
         }

         if (!type) {
            console.error('Unknown type of item', id, ':', type);
         }

         item = new type();

         // Skip item if initialization failed
         if (item.init(this, id, this.options.items[id]) === false) {
            delete this.options.items[id];
            continue;
         }

         this.options.items[id] = item;
      }
   },
   initializeReset: function() {
      // init reset
      this.options.$reset = this.element.find('[data-filter-reset=wrap]');
      this.options.$items = this.options.$reset.find('[data-filter-reset=items]');
      this.options.$template = this.options.$reset
         .find('[data-filter-reset=template]')
         .remove()
         .attr('data-filter-reset-item', '');

      if (!this.options.rootUrl) {
         this.options.rootUrl = window.location.pathname + window.location.search;
      }

      this._on(this.options.$reset, {
         'click [data-filter-reset=reset-item]': this.onResetItem,
         'click [data-filter-reset=reset]': this.onResetAll
      });
   },
   initializeForm: function() {
      // Init form
      this._on(this.element, {
         change: this.onFormInputChanged,
         'change :input': this.onFormInputChanged,
         'ifChanged :input': this.onFormInputChanged,
         'submit form': function(e) {
            // Disable form submission using enter on input (ie. Koza brand search),
            // allow submit of product buy_form
            var $element = this.getForm();
            var isFilter = false;

            $element.each(function(i) {
               if (e.currentTarget === $element[i]) {
                  isFilter = true;
               }
            });

            return !isFilter;
         },
         'click label a': function(e) {
            e.preventDefault();
            e.currentTarget.closest('label').click();
         }
      });

      this._on(this.window, {
         popstate: this.onHistoryChanged
      });

      this.options.originalTitle = document.title;
   },
   _create: function() {
   }
});

(function(filter) {
   filter.Item = wpj.Class.extend({
      id: null,
      $element: null,
      owner: null,
      defaultValue: null,
      visible: true,
      visible_hash: false,
      $inputs: null,
      title: null,
      init: function(owner, id, options) {
         this.id = id;
         this.owner = owner;
         this.applyMembers(options, this.getPrototypeOf(this));

         this.$element = this.owner.getItemInput(id);

         if (this.create() === false) {
            return false;
         }

         //if (this.defaultValue == null)
         //   this.defaultValue = this.getValue();

         return true;
      },
      updateCount: function(state) {
         if (state.count) {
            var $count = this.$element.closest('label').find('[data-filter-count]');
            $count.text(state.count);
         }
      },
      updateState: function(state) {
         this.updateCount(state);
      },
      updateResetElement: function(id, text) {
         var $element = this.owner.options.$items.find('[data-filter-reset-item="' + id + '"]');

         if (text && !$element.length) {
            // Create new element
            $element = this.owner.options.$template.clone().attr('data-filter-reset-item', id);
            $element.appendTo(this.owner.options.$items);
         } else if (!text && $element.length) {
            // Delete element
            $element.remove();
         }

         if (text) {
            $element.find('[data-filter-reset=item-label]').html(text);
         }
      },
      updateReset: function() {
         var show = this.isChanged() && this.isVisible();
         this.updateResetElement(this.id, show ? this.getResetText() : false);
      },
      showIfChanged: function() {
         if (this.isChanged()) {
            var $opener = $('[data-opener=".' + this.id + '-box-wrapper"]');
            wpj.domUtils.expandOpener($opener[0]);
         }
      },
      isChanged: function() {
         return this.defaultValue != this.getValue();
      },
      isVisible: function() {
         return this.visible;
      },
      getResetText: function() {
         return (this.title ? this.title + ': ' : '') + this.getText();
      },
      getText: function(element) {
         var $element = element || this.$element;

         var id = $element.attr('id'),
            label,
            prepareText = function(value) {
               if (!(value && typeof value === 'string')) return null;

               return value.trim().replace(/\([0-9]+\)/, '');
            };

         // Try to get from label
         if (id) {
            var title = $element.closest('[data-filter-label]').data('filter-label');

            if (title) {
               return title;
            }

            label = this.owner.element.find('label[for="' + id + '"]');

            if (label) {
               var value = prepareText(label.text());
               if (value) {
                  return value;
               }

               value = prepareText(label.attr('title'));
               if (value) {
                  return value;
               }
            }
         }

         // Try to get from wrapped label
         label = prepareText($element.closest('label').text());
         if (label) {
            return label;
         }

         // Fallback to input value
         return $element.val();
      },
      getValue: function() {
         return this.$element.val();
      },
      setValue: function(value) {
         this.$element.val(value);
      },
      reset: function() {
         this.setValue(this.defaultValue);
      },
      serialize: function() {
         var result = {};

         if (this.isChanged()) {
            $.extend(result, wpj.serializer.arrayToDict(this.getInputs().serializeArray()));
         }

         return result;
      },
      create: function() {
      },
      getInputs: function() {
         return this.$inputs || this.$element;
      }
   });

   filter.MultiItem = filter.Item.extend({
      defaultValue: [],
      updateCount: function(state) {

         if (state && state.values) {
            // Multiple counts for item - has subitems
            this.$element.each(function() {
               var $element = $(this),
                  $label = $element.closest('label'),
                  $count = $label.find('[data-filter-count]'),
                  id = $count.data('filter-count'),
                  count;

               if (!id) {
                  id = $element.val();
               }

               count = state.values[id] ? state.values[id].count : 0;

               if ($count.length > 0) {
                  $count.text(count);
               }

               $label.toggleClass('disabled', count <= 0);
            });
         }
      },
      isChanged: function() {
         return JSON.stringify(this.defaultValue) != JSON.stringify(this.getValue());
      }
   });

   /* Radion buttons */
   filter.ItemRadio = filter.Item.extend({
      isChanged: function() {
         return JSON.stringify(this.defaultValue) != JSON.stringify(this.getValue());
      },
      getText: function() {
         return arguments.callee._super.getText.call(this, this.$element.filter(':checked'));
      },
      getValue: function() {
         return [this.$element.filter(':checked').val()];
      },
      setValue: function(value) {
         if (!$.isArray(value)) {
            arguments[0] = [value];
         }
         this._super(arguments);
      },
      updateCount: function(state) {
         if (state && state.values) {
            // Multiple counts for item - has subitems
            this.$element.each(function() {
               var $element = $(this),
                  $label = $element.closest('label'),
                  $count = $label.find('[data-filter-count]'),
                  id = $count.data('filter-count'),
                  count = state.values[id] ? state.values[id].count : 0;

               $count.text(count);

               $label.toggleClass('disabled', count <= 0);
            });
         }
      }
   });

   /* Checkbox */
   filter.ItemCheckbox = filter.Item.extend({
      getValue: function() {
         return this.$element.prop('checked');
      },
      setValue: function(value) {
         this.$element.prop('checked', value);
      }
   });

   /* Multiple Checkboxes */
   filter.ItemCheckboxMultiple = filter.MultiItem.extend({
      getText: function() {
         var me = this,
            _super = arguments.callee._super;
         return this.$element
            .filter(':checked')
            .map(function() {
               return _super.getText.call(me, $(this));
            })
            .get()
            .join(', ');
      },
      getValue: function() {
         return this.$element
            .filter(':checked')
            .map(function(_, el) {
               return $(el).val();
            })
            .get();
      },
      setValue: function(value) {
         var $this;
         this.$element.each(function() {
            $this = $(this);
            $this.prop('checked', jQuery.inArray($this.val(), value) > -1);
         });
      }
   });

   filter.ItemCheckboxMultipleLazy = filter.ItemCheckboxMultiple.extend({
      $wrapper: null,
      $itemsWrapper: null,
      template: null,
      endpoint: null,
      create: function () {
         this.$wrapper = $('[data-filter-lazy="' + this.id + '"]');
         this.endpoint = this.$wrapper.data('filterLazyUrl');
         this.template = this.$wrapper.find('[data-filter-lazy="template"]').html();

         this.$itemsWrapper = this.$wrapper.find('[data-filter-lazy="items"]');

         var me = this;
         this.$wrapper.on('input', '[data-filter-lazy="input"] input', function (e) {
            var term = $(this).val();
            wpj.domUtils.resetTimer('filter_' + me.id + '_autocomplete', 300, function () {
               me._doSearch(term);
            });
            e.preventDefault();
         });

         // initialize selected items
         var initItems = this.$wrapper.data('filterLazySelected');
         if (initItems) {
            this._update(initItems);
         }

         // initialize first X values by calling _doSearch with empty term
         this._doSearch('');
      },
      getInputs: function () {
         return this.$itemsWrapper.find('input');
      },
      updateCount: function(state) {},
      _getSelectedItems: function () {
         var selected = [];

         this.getInputs().filter(':checked').each(function () {
            var $label = $(this).parent().find('[data-filter-lazy-label]');
            selected.push({
               value: parseInt($(this).val()),
               name: $label.text(),
               position: parseInt($label.data('filterLazyPosition')),
            })
         });

         return selected;
      },
      _doSearch: function (term) {
         if (!this.endpoint) {
            this._update([]);
         }

         var me = this;
         $.getJSON(this.endpoint + '&term=' + term, function (data) {
            var dataMap = new Map(data.map(item => [item.value, item]));
            var selectedItems = me._getSelectedItems();

            // merge selectedItems, marking them as selected and overriding existing entries
            selectedItems.forEach(selectedItem => {
                if (dataMap.has(selectedItem.value)) {
                    dataMap.get(selectedItem.value).selected = true;
                } else {
                    selectedItem.selected = true;
                    dataMap.set(selectedItem.value, selectedItem);
                }
            });

            var items = Array.from(dataMap.values());
            // sort by positions so selected items that are added to end of array are sorted by its position
            items.sort(function(a, b) {
               return a.position - b.position;
            });

            me._update(items);
         });
      },
      _update: function (items) {
         this.$itemsWrapper.empty();

         var template = this._getTemplate(),
          html = template({ items: items });

         this.$itemsWrapper.html(html);
         this.$element = this.getInputs();
      },
      _getTemplate: function() {
         return wpj.template.compile(this.template);
      }
   });

   /* Multiple Checkboxes with separated entries in reset */
   filter.ItemCheckboxMultipleSeparate = filter.ItemCheckboxMultiple.extend({
      updateReset: function() {
         var me = this,
            show = this.isChanged() && this.isVisible(),
            value = this.getValue(),
            $this,
            showElement;

         this.$element.each(function() {
            $this = $(this);
            showElement = show && jQuery.inArray($this.val(), value) > -1;
            me.updateResetElement(
               me.id + '|' + $this.val(),
               showElement ? me.getText._super.getText.call(me, $this) : false
            );
         });
      },
      reset: function(id) {
         if (id && id.split('|').length >= 2) {
            id = id.split('|')[1];

            this.setValue(
               $.grep(this.getValue(), function(value) {
                  return value != id;
               })
            );
         } else {
            return this._super(arguments);
         }
      }
   });

   /* Range of numeric values */
   filter.ItemFloatRange = filter.Item.extend({
      viewMin: undefined,
      min: undefined,
      max: undefined,
      unit: '',
      steps: 50,
      step: 1,
      margin: 1,
      precision: 2,
      maxPrecision: 2,
      holdUpdate: false,
      valuesSetByHand: [null, null],
      initValues: [null, null],
      rawValues: [null, null],
      create: function() {
         var me = this;
         if (this.unit) {
            this.unit = ' ' + this.unit;
         }

         if (!this.$inputs) {
            this.$inputs = this.$element;
            this.$element = $('[data-filter-range="' + this.id + '"]');
            this.$enter_inputs = $('[data-filter-enter="' + this.id + '"]');
         }

         // Get range from inputs
         if (this.min == undefined && this.max == undefined) {
            this.min = parseFloat(this.$inputs.eq(0).val());
            this.viewMin = this.min;
            this.max = parseFloat(this.$inputs.eq(1).val());
         }

         this.$enter_inputs.on('blur keypress', function(e) {
            if (e.type === 'blur' || e.which === 13) {
               e.preventDefault();
               const minValueInput = me.$enter_inputs.filter('[name$="[minValue]"]');
               const maxValueInput = me.$enter_inputs.filter('[name$="[maxValue]"]');
               me.setValue([minValueInput.val(), maxValueInput.val()]);
            }
         });

         this.updateRange();

         if (!this.isRangeValid()) {
            return false;
         }

         var slider = this.$element[0];
         if (!slider) {
            return false;
         }

         var value = this.$element.data('filter-value');
         if ($.isArray(value)) {
            if (!value[0]) {
               value[0] = this.min;
            }
            if (!value[1]) {
               value[1] = this.max;
            }
            me.valuesSetByHand = value;
         } else {
            value = [this.min, this.max];
         }

         if (value[0] % this.step !== 0) {
            value[0] = this.getMinimum(value[0]);
         }

         noUiSlider.create(slider, {
            start: value,
            range: {
               min: this.min,
               max: this.max
            },
            margin: this.margin,
            step: this.step,
            connect: true
         });

         slider.noUiSlider.on('update', function(values, handle) {
            me.setInputValue(me.fromRangeArray(values));
         });
         slider.noUiSlider.on('set', function(values, handle) {
            if (!me.holdUpdate) {
               $(slider).trigger('setByHand', { range: values });
               me.$inputs.change();
            }
            me.setInputValue(me.fromRangeArray(values));
         });

         this.initValues = [this.min, this.max];

         $(this.$element[0]).on('setByHand', function(e, params) {
            me.valuesSetByHand = me.fromRangeArray(params.range).slice();
            if (JSON.stringify(me.valuesSetByHand) === JSON.stringify([me.min, me.max])) {
               me.valuesSetByHand = [null, null];
            }
         });

         return true;
      },
      isChanged: function() {
         var value = this.getValue();
         if (value) {
            return value[0] > this.initValues[0] || value[1] < this.initValues[1];
         }
         return false;
      },
      reset: function() {
         this.setValue(this.initValues.slice());
         this.valuesSetByHand = [null, null];
         this.updateCount({ min: this.initValues[0], max: this.initValues[1] });
      },
      getText: function() {
         var values = this.getValue();

         if (values[0] <= this.viewMin) {
            values[0] = this.viewMin;
         }
         let val1 = parseFloat(values[0].toFixed(this.precision)).toLocaleString();
         let val2 = parseFloat(values[1].toFixed(this.precision)).toLocaleString();

         return this._super(arguments) + ' ' + val1 + '—' + val2 + this.unit;
      },
      getValue: function() {
         if (!this.$element[0]) {
            return null;
         }

         if (this.valuesSetByHand[0] != null) {
            return this.valuesSetByHand.slice();
         }
         return this.initValues.slice();
      },
      setValue: function(value) {
         if (!value) {
            return;
         }
         this.setInputValue(value);
         return this.$element[0].noUiSlider.set(value);
      },
      setInputValue: function(value) {
         if (!value) {
            return;
         }

         var $inputs = this.getInputs(),
            text = (value[0] < this.viewMin ? this.viewMin : value[0]) + ' - ' + value[1] + this.unit;

         $inputs.filter('[name$="[value]"]').val(text);
         $inputs.filter('[name$="[minValue]"]').val(value[0]);
         $inputs.filter('[name$="[maxValue]"]').val(value[1]);

         this.rawValues = value.slice();

         if (value[0] <= this.initValues[0]) {
            this.rawValues[0] = '*';
         }
         if (value[1] >= this.initValues[1]) {
            this.rawValues[1] = '*';
         }
      },
      serialize: function() {
         var result = {};

         if (this.isChanged()) {
            $.extend(result, wpj.serializer.arrayToDict(this.getInputs().serializeArray()));

            var value_name = this.getInputs().filter('[name$="[value]"]').attr('name');
            result[value_name] = this.rawValues[0] + ' - ' + this.rawValues[1]
         }
         return result;
      },
      updateRange: function() {
         var stepRange = (this.max - this.min) / this.steps,
            stepLog = this.log10(stepRange),
            precision = -Math.min(Math.floor(stepLog), 0);

         this.step = Math.pow(10, Math.floor(stepLog));

         if (stepLog - Math.floor(stepLog) >= 0.5) {
            this.step /= 2;
         }

         precision = Math.min(precision, this.maxPrecision);

         // Update minimum and maximum according to precision
         var roundCoeff = Math.pow(10, precision);
         this.min = this.getMinimum(this.min);
         this.max = Math.ceil(this.max * roundCoeff) / roundCoeff;
         if (this.min === this.max) {
            this.min -= 0.00001;
         }

         this.precision = precision;
      },
      log10: function(value) {
         return Math.log(value) / Math.LN10;
      },
      fromRange: function(value) {
         if (value < this.min) {
            return this.min;
         }
         if (value > this.max) {
            return this.max;
         }
         return value;
      },
      fromRangeArray: function(range) {
         return [this.fromRange(parseFloat(range[0])), this.fromRange(parseFloat(range[1]))];
      },
      isRangeValid: function() {
         return this.min < this.max;
      },
      updateCount: function(state) {
         // price's state min max are NOT nested under "values" property,
         // but other params has min max nested
         var stateValues = state.values !== undefined ? state.values : state;
         if (stateValues !== null && stateValues.min !== undefined) {
            // Update new edge values
            this.min = this.getMinimum(parseFloat(stateValues.min));
            this.max = parseFloat(stateValues.max);

            this.initValues = [this.min, this.max];

            // Recalculate steps
            this.updateRange();

            var values = this.getValue();

            var valid = this.isRangeValid();

            if (!valid) {
               this.min = 0;
               this.max = 1;
               this.precision = 0;
               this.step = 1;
            }

            this.holdUpdate = true;
            this.$element[0].noUiSlider.updateOptions({
               range: {
                  min: this.min,
                  max: this.max
               },
               step: this.step
            });

            this.setValue(values);
            this.holdUpdate = false;
            if (this.isChanged()) {
               $('body').trigger('priceFilterChange');
            }
         }
      },
      getMinimum: function(min) {
         return Math.round((Math.floor(parseFloat(min)/this.step)*this.step)*100)/100;
      }
   });

   /* Price range */
   filter.ItemPrice = filter.ItemFloatRange.extend({
      unit: 'Kč',
      updateCount: function(state) {
         if (state.min !== undefined) {
            this._super(arguments);

            // Update min/max in form
            this.$inputs.eq(0).val(this.min < this.viewMin ? this.viewMin : this.min);
            this.$inputs.eq(1).val(this.max);
         }
      },
      serialize: function() {
         // Call grand-parent
         return arguments.callee._super.serialize._super.serialize.apply(this, [arguments])
      }
   });

   /* Select */
   filter.ItemSelect = filter.Item.extend({
      create: function() {
         if (!this.$inputs) {
            this.$inputs = this.$element;
            this.$element = this.$element.filter('select');
         }
      },
      isChanged: function() {
         return JSON.stringify(this.defaultValue) != JSON.stringify(this.getValue());
      },
      getText: function() {
         var $selected = this.$element.find('option:selected');
         return $selected.data('label') || $selected.text().trim();
      },
      setValue: function(value) {
         this._super(arguments);
         this.$element.trigger('chosen:updated');
      },
      getValue: function() {
         var value = this._super(arguments);
         if (value == null) {
            value = '';
         }
         return value;
      },
      updateCount: function(state) {
         if (state.values) {
            var template = wpj.template.compile(this.$element.filter('[data-template]').data('template'));

            // Multiple counts for item - has subitems
            this.$element.find('option').each(function() {
               var $element = $(this),
                  id = $element.val(),
                  value = state.values[id],
                  count = value ? value.count : 0;

               if (id == '') {
                  return;
               }

               $element.text(template({ value: value, element: $element }));

               $element.prop('disabled', count <= 0);
            });
         }
      }
   });

   /* Select that uses focus */
   filter.FocusSelect = filter.Item.extend({
      $itemTemplate: null,
      $itemsWrapper: null,
      messageKey: 'filter_focusSelect',
      defaultValue: [],
      create: function() {
         const me = this;

         this.$itemsWrapper = this.$element.find('[data-focus-select="items"]');

         // ulozim si templatu aktivniho itemu
         this.$itemTemplate = this.$itemsWrapper.find('[data-focus-select="item-template"]').clone();
         // smazu HTML pro templatu, protoze ho mam uz ulozeny v promenny
         this.$element.find('[data-focus-select="item-template"]').remove();

         // zaregistruju eventu pro handlovani odebrani prodejny z filtru
         this.$itemsWrapper.on('click', '[data-filter-reset]', function(e) {
            const id = $(this).data('filter-reset');

            me.reset(id);
            e.preventDefault();

            return false;
         });

         // pridam listener na message eventu, pres kterou mi focus posle data
         window.addEventListener('message', (event) => {
            // pokud je message odeslana z focusu pro selectnuti skladu
            if (event.data?.key !== me.messageKey) {
               return;
            }

            // nastavim hodnotu z focusu
            me.setValue(event.data.value);
         });
      },
      setValue: function(values) {
         const me = this;

         // smazu aktualni itemy
         this.$itemsWrapper.empty();

         values.forEach(({ id, name }) => {
            if (!id || !name) {
               return;
            }

            // pridam zaselectovane hodnoty, aby bylo videt, ze jsou vybrane
            this._createItem({ id, name }).appendTo(me.$itemsWrapper);
         });

         // provedu update filtru
         this.owner._trigger('change');
         this.owner.updateCategory(true, 500);
      },
      getValue: function() {
         const values = [];

         this.getInputs().each(function() {
            values.push($(this).val());
         });

         return values;
      },
      getInputs: function() {
         return this.$itemsWrapper.find('[data-focus-select="value"]');
      },
      isChanged: function() {
         return JSON.stringify(this.defaultValue) !== JSON.stringify(this.getValue());
      },
      reset: function(resetId) {
         window.postMessage({
            key: 'filter_focusReset',
            value: resetId
         });
         // odeberu itemu podle id, ktere chci resetovat
         this.setValue(this._getItems().filter(({ id, name }) => id != resetId));
      },
      _createItem: function({ id, name }) {
         const item = this.$itemTemplate.clone();
         // odeberu display: none
         item.removeAttr('style');
         // zmenim "item-template" na "item"
         item.attr('data-focus-select', 'item');
         item.attr('data-focus-select-item', 'item-' + id);
         // vyplnim nazev
         item.find('[data-focus-select="name"]').text(name);
         // vyplnim hodnotu do inputu
         item.find('[data-focus-select="value"]').val(id);
         // nastavim reset button
         item.find('[data-filter-reset]').attr('data-filter-reset', id);

         return item;
      },
      _getItems: function() {
         const values = [];

         // z aktualnich itemu vytvorim pole [{ id, name }]
         this.$itemsWrapper.find('[data-focus-select="item"]').each(function() {
            const id = $(this).find('[data-focus-select="value"]').val();
            const name = $(this).find('[data-focus-select="name"]').text();

            if (id && name) {
               values.push({ id, name })
            }
         });

         return values;
      }
   });

   /* Pagination */
   filter.ItemPaging = filter.Item.extend({
      value: 1,
      defaultValue: 1,
      visible: false,
      $element: true,
      visible_hash: true,
      create: function() {
         var me = this;
         $(document).on('click', '[data-filter-page]', function(e) {
            e.preventDefault();
            return me.onPageLoad(e);
         }).on('click', '[data-filter-loader]', function(e) {
            $(this).append('<span class="loading-indicator"></span>');
            return me.onPageLoad(e, true);
         });

         this.owner.element.bind('categoryfilterchange', function(e) {
            var item = me.owner.getItemFromEvent(e.originalEvent);

            // Skip page number reset if changing display type
            // todo: brat v potaz preklady
            if (item && item.id == 'zobrazeni') {
               return;
            }

            me.value = 1;
         });
         this._super(arguments);
      },
      onPageLoad: function(e, isNext) {
         var page = isNext ? this.getValue() + 1 : $(e.currentTarget).data('filter-page');

         this.setValue(page);

         this.owner.updateCategory(true, 1, isNext);
         this.owner.updateReset();

         return false;
      },
      getText: function() {
         return 'Strana ' + this.value;
      },
      setValue: function(value) {
         if (value > 0) {
            this.value = value;
         }
      },
      getValue: function() {
         return this.value;
      },
      serialize: function() {
         if (this.isChanged()) {
            return { page: this.value };
         }

         return {};
      }
   });

   /* Sorting */
   filter.ItemSorting = {
      visible_hash: true,
      fromValue: function(value) {
         var order_by = Math.abs(parseInt(value)),
            order_dir = value >= 0 ? 1 : 2;

         if (value == '') {
            order_by = -1;
            order_dir = '';
         }

         return [order_by, order_dir];
      },
      toValue: function(order_by, order_dir) {
         var value = '';

         if (order_by > 0) {
            value = (parseInt(order_dir) == 2 ? '-' : '') + order_by;
         }

         return value;
      }
   };

   /* Sorting using links */
   filter.ItemSortingLinks = filter.Item.extend({
      visible: false,
      visible_hash: true,
      getValue: function() {
         if (this.$element.is('select')) {
            return this.$element.find('option:selected').val();
         }

         return this.$element.filter(':checked').val();
      },
      setValue: function(value) {
         if (!$.isArray(value)) {
            arguments[0] = [value];
         }
         this._super(arguments);
      },
      serialize: function() {
         if (this.getValue() === '') {
            return {};
         }

         return this._super(arguments);
      }
   });
})(jQuery.fn.categoryFilter);
