'use strict';

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}

var defaults = {
  classNames: {
    active: 'gl-active',
    base: 'gl-star-rating',
    selected: 'gl-selected'
  },
  clearable: true,
  maxStars: 10,
  prebuilt: false,
  stars: null,
  tooltip: 'Select a Rating'
};

var addRemoveClass = function addRemoveClass(el, bool, className) {
  el.classList[bool ? 'add' : 'remove'](className);
};
var createSpanEl = function createSpanEl(attributes) {
  var el = document.createElement('span');
  attributes = attributes || {};

  for (var key in attributes) {
    el.setAttribute(key, attributes[key]);
  }

  return el;
};
var inRange = function inRange(value, min, max) {
  return /^\d+$/.test(value) && min <= value && value <= max;
};
var insertSpanEl = function insertSpanEl(el, after, attributes) {
  var newEl = createSpanEl(attributes);
  el.parentNode.insertBefore(newEl, after ? el.nextSibling : el);
  return newEl;
};
var merge = function merge() {
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
    args[_key] = arguments[_key];
  }

  // adapted from https://github.com/firstandthird/aug
  var results = {};
  args.forEach(function (prop) {
    Object.keys(prop || {}).forEach(function (propName) {
      if (args[0][propName] === undefined) return; // restrict keys to the defaults

      var propValue = prop[propName];

      if (type(propValue) === 'Object' && type(results[propName]) === 'Object') {
        results[propName] = merge(results[propName], propValue);
        return;
      }

      results[propName] = propValue;
    });
  });
  return results;
};
var type = function type(value) {
  return {}.toString.call(value).slice(8, -1);
};
var values = function values(selectEl) {
  var values = [];
  [].forEach.call(selectEl.options, function (el) {
    var value = parseInt(el.value, 10) || 0;

    if (value > 0) {
      values.push({
        index: el.index,
        text: el.text,
        value: value
      });
    }
  });
  return values.sort(function (a, b) {
    return a.value - b.value;
  });
};

var Widget = /*#__PURE__*/function () {
  function Widget(el, props) {
    _classCallCheck(this, Widget);

    // (HTMLElement, object):void
    this.direction = window.getComputedStyle(el, null).getPropertyValue('direction');
    this.el = el;
    this.events = {
      change: this.onChange.bind(this),
      keydown: this.onKeyDown.bind(this),
      mousedown: this.onPointerDown.bind(this),
      mouseleave: this.onPointerLeave.bind(this),
      mousemove: this.onPointerMove.bind(this),
      reset: this.onReset.bind(this),
      touchend: this.onPointerDown.bind(this),
      touchmove: this.onPointerMove.bind(this)
    };
    this.indexActive = null; // the active span index

    this.indexSelected = null; // the selected span index

    this.props = props;
    this.tick = null;
    this.ticking = false;
    this.values = values(el);
    this.widgetEl = null;

    if (this.el.widget) {
      this.el.widget.destroy(); // remove any stale event listeners
    }

    if (inRange(this.values.length, 1, this.props.maxStars)) {
      this.build();
    } else {
      this.destroy();
    }
  }

  _createClass(Widget, [{
    key: "build",
    value: function build() {
      // ():void
      this.destroy();
      this.buildWidget();
      this.selectValue(this.indexSelected = this.selected(), false); // set the initial value but do not trigger change event

      this.handleEvents('add');
      this.el.widget = this; // store a reference to this widget on the SELECT so that we can remove stale event listeners
    }
  }, {
    key: "buildWidget",
    value: function buildWidget() {
      var _this = this;

      // ():void
      var parentEl, widgetEl;

      if (this.props.prebuilt) {
        parentEl = this.el.parentNode;
        widgetEl = parentEl.querySelector('.' + this.props.classNames.base + '--stars');
      } else {
        parentEl = insertSpanEl(this.el, false, {
          "class": this.props.classNames.base
        });
        parentEl.appendChild(this.el);
        widgetEl = insertSpanEl(this.el, true, {
          "class": this.props.classNames.base + '--stars'
        });
        this.values.forEach(function (item, index) {
          var el = createSpanEl({
            'data-index': index,
            'data-value': item.value
          });

          if ('function' === typeof _this.props.stars) {
            _this.props.stars.call(_this, el, item, index);
          }

          [].forEach.call(el.children, function (el) {
            return el.style.pointerEvents = 'none';
          });
          widgetEl.innerHTML += el.outerHTML;
        });
      }

      parentEl.dataset.starRating = '';
      parentEl.classList.add(this.props.classNames.base + '--' + this.direction);

      if (this.props.tooltip) {
        widgetEl.setAttribute('role', 'tooltip');
      }

      this.widgetEl = widgetEl;
    }
  }, {
    key: "changeIndexTo",
    value: function changeIndexTo(index, force) {
      var _this2 = this;

      // (int):void
      if (this.indexActive !== index || force) {
        [].forEach.call(this.widgetEl.children, function (el, i) {
          // i starts at zero
          addRemoveClass(el, i <= index, _this2.props.classNames.active);
          addRemoveClass(el, i === _this2.indexSelected, _this2.props.classNames.selected);
        });
        this.widgetEl.setAttribute('data-rating', index + 1);

        if ('function' !== typeof this.props.stars && !this.props.prebuilt) {
          // @v3 compat
          this.widgetEl.classList.remove('s' + 10 * (this.indexActive + 1));
          this.widgetEl.classList.add('s' + 10 * (index + 1));
        }

        if (this.props.tooltip) {
          var _this$values$index;

          var label = index < 0 ? this.props.tooltip : (_this$values$index = this.values[index]) === null || _this$values$index === void 0 ? void 0 : _this$values$index.text;
          this.widgetEl.setAttribute('aria-label', label);
        }

        this.indexActive = index;
      }

      this.ticking = false;
    }
  }, {
    key: "destroy",
    value: function destroy() {
      // ():void
      this.indexActive = null; // the active span index

      this.indexSelected = this.selected(); // the selected span index

      var parentEl = this.el.parentNode;

      if (parentEl.classList.contains(this.props.classNames.base)) {
        if (this.props.prebuilt) {
          this.widgetEl = parentEl.querySelector('.' + this.props.classNames.base + '--stars');
          parentEl.classList.remove(this.props.classNames.base + '--' + this.direction);
          delete parentEl.dataset.starRating;
        } else {
          parentEl.parentNode.replaceChild(this.el, parentEl);
        }

        this.handleEvents('remove');
      }

      delete this.el.widget; // remove the widget reference
    }
  }, {
    key: "eventListener",
    value: function eventListener(el, action, events, items) {
      var _this3 = this;

      // (HTMLElement, string, array, object):void
      events.forEach(function (ev) {
        return el[action + 'EventListener'](ev, _this3.events[ev], items || false);
      });
    }
  }, {
    key: "handleEvents",
    value: function handleEvents(action) {
      // (string):void
      var formEl = this.el.closest('form');

      if (formEl && formEl.tagName === 'FORM') {
        this.eventListener(formEl, action, ['reset']);
      }

      this.eventListener(this.el, action, ['change']); // always trigger the change event, even when SELECT is disabled

      if ('add' === action && this.el.disabled) return;
      this.eventListener(this.el, action, ['keydown']);
      this.eventListener(this.widgetEl, action, ['mousedown', 'mouseleave', 'mousemove', 'touchend', 'touchmove'],  false);
    }
  }, {
    key: "indexFromEvent",
    value: function indexFromEvent(ev) {
      var _ev$touches, _ev$changedTouches;

      // (MouseEvent|TouchEvent):void
      var origin = ((_ev$touches = ev.touches) === null || _ev$touches === void 0 ? void 0 : _ev$touches[0]) || ((_ev$changedTouches = ev.changedTouches) === null || _ev$changedTouches === void 0 ? void 0 : _ev$changedTouches[0]) || ev;
      var el = document.elementFromPoint(origin.clientX, origin.clientY);

      if (el.parentNode === this.widgetEl) {
        return [].slice.call(el.parentNode.children).indexOf(el);
      }

      return this.indexActive;
    }
  }, {
    key: "onChange",
    value: function onChange() {
      // ():void
      this.changeIndexTo(this.selected(), true);
    }
  }, {
    key: "onKeyDown",
    value: function onKeyDown(ev) {
      // (KeyboardEvent):void
      var key = ev.key.slice(5);
      if (!~['Left', 'Right'].indexOf(key)) return;
      ev.preventDefault();
      var increment = key === 'Left' ? -1 : 1;

      if (this.direction === 'rtl') {
        increment *= -1;
      }

      var maxIndex = this.values.length - 1;
      var minIndex = -1;
      var index = Math.min(Math.max(this.selected() + increment, minIndex), maxIndex);
      this.selectValue(index, true); // trigger change event
    }
  }, {
    key: "onPointerDown",
    value: function onPointerDown(ev) {
      // (MouseEvent|TouchEvent):void
      ev.preventDefault(); // this.el.focus(); // highlight the rating field

      var index = this.indexFromEvent(ev);

      if (this.props.clearable && index === this.indexSelected) {
        index = -1; // remove the value
      }

      this.selectValue(index, true); // trigger change event
    }
  }, {
    key: "onPointerLeave",
    value: function onPointerLeave(ev) {
      var _this4 = this;

      // (MouseEvent):void
      ev.preventDefault();
      cancelAnimationFrame(this.tick);
      requestAnimationFrame(function () {
        return _this4.changeIndexTo(_this4.indexSelected);
      });
    }
  }, {
    key: "onPointerMove",
    value: function onPointerMove(ev) {
      var _this5 = this;

      // (MouseEvent|TouchEvent):void
      ev.preventDefault();

      if (!this.ticking) {
        this.tick = requestAnimationFrame(function () {
          return _this5.changeIndexTo(_this5.indexFromEvent(ev));
        });
        this.ticking = true;
      }
    }
  }, {
    key: "onReset",
    value: function onReset() {
      var _this$el$querySelecto;

      // ():void
      var index = this.valueIndex((_this$el$querySelecto = this.el.querySelector('[selected]')) === null || _this$el$querySelecto === void 0 ? void 0 : _this$el$querySelecto.value);
      this.selectValue(index || -1, false); // do not trigger change event
    }
  }, {
    key: "selected",
    value: function selected() {
      // ():int
      return this.valueIndex(this.el.value); // get the selected span index
    }
  }, {
    key: "selectValue",
    value: function selectValue(index, triggerChangeEvent) {
      var _this$values$index2;

      // (int, bool):void
      this.el.value = ((_this$values$index2 = this.values[index]) === null || _this$values$index2 === void 0 ? void 0 : _this$values$index2.value) || ''; // first set the new value

      this.indexSelected = this.selected(); // get the actual index from the selected value

      if (false === triggerChangeEvent) {
        this.changeIndexTo(this.selected(), true);
      } else {
        this.el.dispatchEvent(new Event('change'));
      }
    }
  }, {
    key: "valueIndex",
    value: function valueIndex(value) {
      return this.values.findIndex(function (val) {
        return val.value === +value;
      });
    }
  }]);

  return Widget;
}();

var StarRating = /*#__PURE__*/function () {
  function StarRating(selector, props) {
    _classCallCheck(this, StarRating);

    // (HTMLSelectElement|NodeList|string, object):void
    this.destroy = this.destroy.bind(this);
    this.props = props;
    this.rebuild = this.rebuild.bind(this);
    this.selector = selector;
    this.widgets = [];
    this.build();
  }

  _createClass(StarRating, [{
    key: "build",
    value: function build() {
      var _this = this;

      // (HTMLSelectElement|NodeList|string, object):void
      this.queryElements(this.selector).forEach(function (el) {
        var options = merge(defaults, _this.props, JSON.parse(el.getAttribute('data-options')));

        if ('SELECT' === el.tagName && !el.widget) {
          // check for an existing Widget reference
          if (!options.prebuilt && el.parentNode.classList.contains(options.classNames.base)) {
            _this.unwrap(el);
          }

          _this.widgets.push(new Widget(el, options));
        }
      });
    }
  }, {
    key: "destroy",
    value: function destroy() {
      // ():void
      this.widgets.forEach(function (widget) {
        return widget.destroy();
      });
      this.widgets = [];
    }
  }, {
    key: "queryElements",
    value: function queryElements(selector) {
      // (HTMLSelectElement|NodeList|string):array
      if ('HTMLSelectElement' === type(selector)) {
        return [selector];
      }

      if ('NodeList' === type(selector)) {
        return [].slice.call(selector);
      }

      if ('String' === type(selector)) {
        return [].slice.call(document.querySelectorAll(selector));
      }

      return [];
    }
  }, {
    key: "rebuild",
    value: function rebuild() {
      // ():void
      this.destroy();
      this.build();
    }
  }, {
    key: "unwrap",
    value: function unwrap(el) {
      var removeEl = el.parentNode;
      var parentEl = removeEl.parentNode;
      parentEl.insertBefore(el, removeEl);
      parentEl.removeChild(removeEl);
    }
  }]);

  return StarRating;
}();

module.exports = StarRating;
