/*
 * https://github.com/morethanwords/tweb
 * Copyright (C) 2019-2021 Eduard Kuzmenko
 * https://github.com/morethanwords/tweb/blob/master/LICENSE
 */

import attachGrabListeners, { GrabEvent } from "../helpers/dom/attachGrabListeners";
import clamp from "../helpers/number/clamp";
import safeAssign from "../helpers/object/safeAssign";

export default class RangeSelector {
  public container: HTMLDivElement;
  protected filled: HTMLDivElement;
  protected seek: HTMLInputElement;

  public mousedown = false;
  protected rect: DOMRect;
  protected _removeListeners: () => void;

  private events: Partial<{
    //onMouseMove: ProgressLine['onMouseMove'],
    onMouseDown: RangeSelector['onMouseDown'],
    onMouseUp: RangeSelector['onMouseUp'],
    onScrub: (value: number) => void
  }> = {};

  protected decimals: number;

  protected step: number;
  protected min: number;
  protected max: number;
  protected withTransition = false;
  protected useTransform = false;
  protected vertical = false;

  constructor(
    options: {
      step: RangeSelector['step'],
      min: RangeSelector['min'],
      max: RangeSelector['max'],
      withTransition?: RangeSelector['withTransition'],
      useTransform?: RangeSelector['useTransform'],
      vertical?: RangeSelector['vertical']
    }, 
    value = 0
  ) {
    safeAssign(this, options);

    this.container = document.createElement('div');
    this.container.classList.add('progress-line');

    // there is no sense in using transition with transform, because it is updating every frame
    if(this.useTransform) {
      this.container.classList.add('use-transform');
    } else if(this.withTransition) {
      this.container.classList.add('with-transition');
    }

    this.filled = document.createElement('div');
    this.filled.classList.add('progress-line__filled');

    const seek = this.seek = document.createElement('input');
    seek.classList.add('progress-line__seek');
    //seek.setAttribute('max', '0');
    seek.type = 'range';
    seek.step = '' + this.step;
    seek.min = '' + this.min;
    seek.max = '' + this.max;
    seek.value = '' + value;

    if(value) {
      this.setProgress(value);
    }

    const stepStr = '' + this.step;
    const index = stepStr.indexOf('.');
    this.decimals = index === -1 ? 0 : stepStr.length - index - 1;

    //this.setListeners();

    this.container.append(this.filled, seek);
  }

  get value() {
    return +this.seek.value;
  }

  public setHandlers(events: RangeSelector['events']) {
    this.events = events;
  }

  protected onMouseMove = (event: GrabEvent) => {
    this.scrub(event);
  };

  protected onMouseDown = (event: GrabEvent) => {
    this.rect = this.container.getBoundingClientRect();
    this.mousedown = true;
    this.scrub(event);
    this.container.classList.add('is-focused');
    this.events?.onMouseDown && this.events.onMouseDown(event);
  };

  protected onMouseUp = (event: GrabEvent) => {
    this.mousedown = false;
    this.container.classList.remove('is-focused');
    this.events?.onMouseUp && this.events.onMouseUp(event);
  };

  public setListeners() {
    this.seek.addEventListener('input', this.onInput);
    this._removeListeners = attachGrabListeners(this.container, this.onMouseDown, this.onMouseMove, this.onMouseUp);
  }

  public onInput = () => {
    const value = +this.seek.value;
    this.setFilled(value);
    this.events?.onScrub && this.events.onScrub(value);
  };

  public setProgress(value: number) {
    this.seek.value = '' + value;
    this.setFilled(+this.seek.value); // clamp
  }

  public addProgress(value: number) {
    this.seek.value = '' + (+this.seek.value + value);
    this.setFilled(+this.seek.value); // clamp
  }

  public setFilled(value: number) {
    let percents = (value - this.min) / (this.max - this.min);
    percents = clamp(percents, 0, 1);
    
    // using scaleX and width even with vertical because it will be rotated
    if(this.useTransform) {
      this.filled.style.transform = `scaleX(${percents})`;
    } else {
      this.filled.style.width = (percents * 100) + '%';
    }
  }

  protected scrub(event: GrabEvent) {
    const rectMax = this.vertical ? this.rect.height : this.rect.width;
    const offsetAxisValue = clamp(this.vertical ? -(event.y - this.rect.bottom) : event.x - this.rect.left, 0, rectMax);

    let value = this.min + (offsetAxisValue / rectMax * (this.max - this.min));

    if((value - this.min) < ((this.max - this.min) / 2)) {
      value -= this.step / 10;
    }
    
    value = +value.toFixed(this.decimals);
    value = clamp(value, this.min, this.max);

    //this.seek.value = '' + value;
    //this.onInput();

    this.setProgress(value);
    this.events?.onScrub && this.events.onScrub(value);

    return value;
  }

  public removeListeners() {
    if(this._removeListeners) {
      this._removeListeners();
      this._removeListeners = null;
    }

    this.seek.removeEventListener('input', this.onInput);
    
    this.events = {};
  }
}
