/*! VILLAGE v0.0.3 | SPDX-License-Identifier: MIT | License-Filename: LICENSE.md | restricted use (see terms and conditions) */

const config = {
  prefix: 'village',
  namespace: 'village',
  version: '0.0.3'
};

const api = window[config.namespace];

const RangeSelector = {
  RANGE: api.internals.ns.selector('range'),
  RANGE_SM: api.internals.ns.selector('range--sm'),
  RANGE_STEP: api.internals.ns.selector('range--step'),
  RANGE_DOUBLE: api.internals.ns.selector('range--double'),
  RANGE_DOUBLE_STEP: api.internals.ns.selector('range--double') + api.internals.ns.selector('range--step'),
  RANGE_INPUT: api.internals.ns.selector('range input[type=range]:nth-of-type(1)'),
  RANGE_INPUT2: `${api.internals.ns.selector('range--double')} input[type=range]:nth-of-type(2)`,
  RANGE_OUTPUT: api.internals.ns.selector('range__output'),
  RANGE_MIN: api.internals.ns.selector('range__min'),
  RANGE_MAX: api.internals.ns.selector('range__max'),
  RANGE_PREFIX: api.internals.ns.attr('prefix'),
  RANGE_SUFFIX: api.internals.ns.attr('suffix')
};

const RangeEmission = {
  VALUE: api.internals.ns.emission('range', 'value'),
  VALUE2: api.internals.ns.emission('range', 'value2'),
  OUTPUT: api.internals.ns.emission('range', 'output'),
  CONSTRAINTS: api.internals.ns.emission('range', 'constraints'),
  MIN: api.internals.ns.emission('range', 'min'),
  MAX: api.internals.ns.emission('range', 'max'),
  STEP: api.internals.ns.emission('range', 'step'),
  PREFIX: api.internals.ns.emission('range', 'prefix'),
  SUFFIX: api.internals.ns.emission('range', 'suffix'),
  DISABLED: api.internals.ns.emission('range', 'disabled'),
  ENABLE_POINTER: api.internals.ns.emission('range', 'enable_pointer')
};

class RangeModel {
  constructor () {
    this._width = 0;
    this._min = 0;
    this._max = 0;
    this._value = 0;
    this._thumbSize = 24;
    this._innerWidth = 0;
    this._prefix = '';
    this._suffix = '';
    this._background = {};
  }

  configure (model) {
    if (!model) return;
    this._prefix = model._prefix;
    this._suffix = model._suffix;
    this._width = model.width;
    this.setConstraints(model._constraints);
    this.value = model.value;
    this.update();
  }

  setPrefix (value) {
    this._prefix = value !== null ? value : '';
  }

  setSuffix (value) {
    this._suffix = value !== null ? value : '';
  }

  _decorate (value) {
    return `${this._prefix}${value}${this._suffix}`;
  }

  get width () {
    return this._width;
  }

  set width (value) {
    this._width = value;
  }

  get isSm () {
    return this._isSm;
  }

  set isSm (value) {
    if (this._isSm === value) return;
    this._isSm = value;
    this.setThumbSize(value ? 16 : 24);
    this.update();
  }

  setThumbSize (value, mult = 1) {
    this._thumbSize = value;
    this._innerPadding = value * mult;
  }

  get textValue () {
    return this._decorate(this._value);
  }

  get value () {
    return this._value;
  }

  set value (value) {
    this._value = value;
  }

  get outputX () {
    return this._outputX;
  }

  setConstraints (constraints) {
    this._constraints = constraints;
    this._min = constraints.min;
    this._max = constraints.max;
    this._step = constraints.step;
    this._rangeWidth = constraints.rangeWidth;
  }

  get min () {
    return this._min;
  }

  get textMin () {
    return this._decorate(this._min);
  }

  get max () {
    return this._max;
  }

  get textMax () {
    return this._decorate(this._max);
  }

  get step () {
    return this._step;
  }

  get output () {
    return {
      text: this.textValue,
      transform: `translateX(${this._translateX}px) translateX(-${this._centerPercent}%)`
    };
  }

  _getRatio (value) {
    return (value - this._min) / this._rangeWidth;
  }

  get progress () {
    return this._progress;
  }

  update () {
    this._update();
  }

  _update () {
    this._innerWidth = this._width - this._innerPadding;
    const ratio = this._getRatio(this._value);
    this._translateX = ratio * this._width;
    this._centerPercent = ratio * 100;
    this._progress = {
      right: `${(this._innerWidth * ratio + this._innerPadding * 0.5).toFixed(2)}px`
    };
  }
}

class RangeModelStep extends RangeModel {
  get stepWidth () {
    return `${this._stepWidth.toFixed(3)}px`;
  }

  _update () {
    super._update();
    const steps = this._rangeWidth / this._step;
    this._stepWidth = this._innerWidth / steps;
    while (this._stepWidth < 4) this._stepWidth *= 2;
  }
}

class RangeModelDouble extends RangeModel {
  get value2 () {
    return this._value;
  }

  set value2 (value) {
    if (this._value2 === value) return;
    this._value2 = value;
    this.update();
  }

  get textValue () {
    return `${this._decorate(this._value)} - ${this._decorate(this._value2)}`;
  }

  setThumbSize (value) {
    super.setThumbSize(value, 2);
  }

  _update () {
    super._update();
    const ratio = this._getRatio((this._value + this._value2) * 0.5);
    this._translateX = ratio * this._width;
    this._centerPercent = ratio * 100;
    const ratio1 = this._getRatio(this._value);
    const ratio2 = this._getRatio(this._value2);
    this._progress = {
      left: `${(this._innerWidth * ratio1 + this._innerPadding * 0.25).toFixed(2)}px`,
      right: `${(this._innerWidth * ratio2 + this._innerPadding * 0.75).toFixed(2)}px`
    };
  }
}

class RangeModelDoubleStep extends RangeModelDouble {
  get stepWidth () {
    return `${this._stepWidth.toFixed(3)}px`;
  }

  _update () {
    super._update();
    const steps = this._rangeWidth / this._step;
    this._stepWidth = this._innerWidth / steps;
    if (this._stepWidth < 4) this._stepWidth *= Math.ceil(4 / this._stepWidth);
  }
}

const RangeTypes = {
  STEP: 'step',
  DOUBLE: 'double',
  DOUBLE_STEP: 'double-step',
  DEFAULT: 'default'
};

class Range extends api.core.Instance {
  static get instanceClassName () {
    return 'Range';
  }

  init () {
    this._retrieveType();
    this._retrieveSize();
    if (this.isLegacy) {
      this.isResizing = true;
      this.isMouseMoving = true;
    } else {
      this._observer = new ResizeObserver(this.resize.bind(this));
      this._observer.observe(this.node);
    }

    this.addAscent(RangeEmission.CONSTRAINTS, this.setConstraints.bind(this));
    this.addAscent(RangeEmission.VALUE, this.setValue.bind(this));
    this.addAscent(RangeEmission.VALUE2, this.setValue2.bind(this));
    if (this.getAttribute(RangeSelector.RANGE_PREFIX)) this.setPrefix(this.getAttribute(RangeSelector.RANGE_PREFIX));
    if (this.getAttribute(RangeSelector.RANGE_SUFFIX)) this.setSuffix(this.getAttribute(RangeSelector.RANGE_SUFFIX));
    this.update();
  }

  _retrieveType () {
    switch (true) {
      case this.matches(RangeSelector.RANGE_DOUBLE_STEP):
        this.type = RangeTypes.DOUBLE;
        break;

      case this.matches(RangeSelector.RANGE_DOUBLE):
        this.type = RangeTypes.DOUBLE;
        break;

      case this.matches(RangeSelector.RANGE_STEP):
        this.type = RangeTypes.STEP;
        break;

      default:
        this.type = RangeTypes.DEFAULT;
    }
  }

  set type (value) {
    if (this._type === value) return;
    this._type = value;

    const oldModel = this._model;

    switch (this._type) {
      case RangeTypes.DOUBLE_STEP:
        this._model = new RangeModelDoubleStep();
        break;

      case RangeTypes.DOUBLE:
        this._model = new RangeModelDouble();
        break;

      case RangeTypes.STEP:
        this._model = new RangeModelStep();
        break;

      default:
        this._model = new RangeModel();
    }

    this._model.configure(oldModel);
  }

  get type () {
    return this._type;
  }

  _retrieveSize () {
    this._model.isSm = this.matches(RangeSelector.RANGE_SM);
  }

  resize () {
    this._retrieveWidth();
    this.update();
  }

  _retrieveWidth () {
    this._model.width = this.getRect().width;
  }

  setValue (value) {
    this._model.value = value;
    switch (this._type) {
      case RangeTypes.DOUBLE_STEP:
      case RangeTypes.DOUBLE:
        this.descend(RangeEmission.VALUE, value);
        break;
    }
    this.update();
  }

  setValue2 (value) {
    this._model.value2 = value;
    this.descend(RangeEmission.VALUE2, value);
    this.update();
  }

  setConstraints (constraints) {
    this._model.setConstraints(constraints);
    this.update();
    this.descend(RangeEmission.CONSTRAINTS, constraints);
  }

  setPrefix (value) {
    this._model.setPrefix(value);
    this.update();
  }

  setSuffix (value) {
    this._model.setSuffix(value);
    this.update();
  }

  mutate (attributesNames) {
    switch (true) {
      case attributesNames.includes('class'):
        this._retrieveType();
        this._retrieveSize();
        break;

      case attributesNames.includes(RangeSelector.RANGE_PREFIX):
      case attributesNames.includes(RangeSelector.RANGE_SUFFIX):
        this._model.setPrefix(this.getAttribute(RangeSelector.RANGE_PREFIX));
        this._model.setSuffix(this.getAttribute(RangeSelector.RANGE_SUFFIX));
        this.update();
        break;
    }
  }

  update () {
    this._model.update();
    this.descend(RangeEmission.OUTPUT, this._model.output);
    this.descend(RangeEmission.MIN, this._model.textMin);
    this.descend(RangeEmission.MAX, this._model.textMax);
    const progress = this._model.progress;
    if (progress.left) {
      this.style.setProperty('--progress-left', progress.left);
    } else {
      this.style.removeProperty('--progress-left');
    }
    if (progress.right) {
      this.style.setProperty('--progress-right', progress.right);
      if (this.isLegacy) {
        if (progress.left) {
          this.style.setProperty('background-position-x', progress.left);
          this.style.setProperty('background-size', `${parseFloat(progress.right) - parseFloat(progress.left)}px ${this._model.isSm ? '8px' : '12px'}`);
        }
      }
    } else {
      this.style.removeProperty('--progress-right');
      if (this.isLegacy) {
        this.style.removeProperty('background-size');
        this.style.removeProperty('background-position-x');
      }
    }
    if (this._model.stepWidth) this.style.setProperty('--step-width', this._model.stepWidth);
    else this.style.removeProperty('--step-width');
  }

  mouseMove (point) {
    if (this._type !== RangeTypes.DOUBLE && this._type !== RangeTypes.DOUBLE_STEP) return;
    const x = point.x - this.getRect().left;
    this.descend(RangeEmission.ENABLE_POINTER, (parseFloat(this._model.progress.right) - parseFloat(this._model.progress.left)) / 2 + parseFloat(this._model.progress.left) < x ? 2 : 1);
  }

  dispose () {
    this._observer.disconnect();
  }
}

class RangeConstraints {
  constructor (node) {
    this._min = isNaN(node.min) ? 0 : node.min;
    this._max = isNaN(node.max) ? 100 : node.max;
    this._step = isNaN(node.step) ? 1 : node.step;
    this._rangeWidth = this._max - this._min;
  }

  get min () {
    return this._min;
  }

  get max () {
    return this._max;
  }

  get step () {
    return this._step;
  }

  get rangeWidth () {
    return this._rangeWidth;
  }

  test (min, max, step) {
    return this._min === min && this._max === max && this._step === step;
  }
}

class RangeInput extends api.core.Instance {
  static get instanceClassName () {
    return 'RangeInput';
  }

  init () {
    this._init();
    this.node.value = this.getAttribute('value');
    this._changing = this.change.bind(this);
    this._listenerType = this.isLegacy ? 'change' : 'input';
    this.listen(this._listenerType, this._changing);
    if (this.isLegacy) this.addDescent(RangeEmission.ENABLE_POINTER, this._enablePointer.bind(this));
    this.change();
  }

  _init () {
    this._pointerId = 1;
    this.request(() => {
      if (!this.hasAttribute('min')) this.setAttribute('min', 0);
      this.ascend(RangeEmission.CONSTRAINTS, new RangeConstraints(this.node));
      this.ascend(RangeEmission.DISABLED, this.node.disabled);
    });

    this.addDescent(RangeEmission.VALUE2, this.setValue.bind(this));
  }

  _enablePointer (pointerId) {
    const isEnabled = pointerId === this._pointerId;
    if (this._isPointerEnabled === isEnabled) return;
    this._isPointerEnabled = isEnabled;
    if (isEnabled) this.style.removeProperty('pointer-events');
    else this.style.setProperty('pointer-events', 'none');
  }

  setValue (value) {
    if (parseFloat(this.node.value) > value) {
      this.node.value = value;
      this.change();
    }
  }

  change () {
    this.ascend(RangeEmission.VALUE, parseFloat(this.node.value));
  }

  mutate (attributesNames) {
    if (attributesNames.includes('disabled')) this.ascend(RangeEmission.DISABLED, this.node.disabled);
    if (attributesNames.includes('min') || attributesNames.includes('max') || attributesNames.includes('step')) {
      this.ascend(RangeEmission.CONSTRAINTS, new RangeConstraints(this.node));
      this.change();
    }
  }

  dispose () {
    if (this._listenerType) this.unlisten(this._listenerType, this._changing);
  }
}

class RangeInput2 extends RangeInput {
  static get instanceClassName () {
    return 'RangeInput2';
  }

  _init () {
    this._pointerId = 2;
    this.addDescent(RangeEmission.CONSTRAINTS, this.setConstraints.bind(this));
    this.addDescent(RangeEmission.VALUE, this.setValue.bind(this));
  }

  setValue (value) {
    if (parseFloat(this.node.value) < value) {
      this.node.value = value;
      this.change();
    }
  }

  change () {
    this.ascend(RangeEmission.VALUE2, parseFloat(this.node.value));
  }

  setConstraints (constraints) {
    this.node.min = constraints.min;
    this.node.max = constraints.max;
    this.node.step = constraints.step;
    this.change();
  }

  mutate (attributesNames) {}
}

class RangeOutput extends api.core.Instance {
  static get instanceClassName () {
    return 'RangeOutput';
  }

  init () {
    this.addDescent(RangeEmission.OUTPUT, this.change.bind(this));
  }

  change (data) {
    this.node.innerText = data.text;
    this.node.style.transform = data.transform;
  }
}

class RangeLimit extends api.core.Instance {
  static get instanceClassName () {
    return 'RangeLimit';
  }

  init () {
    switch (true) {
      case this.matches(RangeSelector.RANGE_MIN):
        this.addDescent(RangeEmission.MIN, this.change.bind(this));
        break;

      case this.matches(RangeSelector.RANGE_MAX):
        this.addDescent(RangeEmission.MAX, this.change.bind(this));
        break;
    }
  }

  change (text) {
    this.node.innerText = text;
  }
}

api.range = {
  Range: Range,
  RangeInput: RangeInput,
  RangeInput2: RangeInput2,
  RangeOutput: RangeOutput,
  RangeLimit: RangeLimit,
  RangeEmission: RangeEmission,
  RangeSelector: RangeSelector
};

api.internals.register(api.range.RangeSelector.RANGE, api.range.Range);
api.internals.register(api.range.RangeSelector.RANGE_INPUT, api.range.RangeInput);
api.internals.register(api.range.RangeSelector.RANGE_INPUT2, api.range.RangeInput2);
api.internals.register(api.range.RangeSelector.RANGE_OUTPUT, api.range.RangeOutput);
api.internals.register(api.range.RangeSelector.RANGE_MIN, api.range.RangeLimit);
api.internals.register(api.range.RangeSelector.RANGE_MAX, api.range.RangeLimit);
//# sourceMappingURL=range.module.js.map
