interface Options {
  transformX?: number;
  transformY?: number;
  zoom?: number;
  rotate?: number;
}
interface HTMLElementPlus extends HTMLElement {
  onmousewheel?: (...args: unknown[]) => void;
}

export default class Transform {
  private el: HTMLElementPlus;

  private oTransformX = 0;
  private oTransformY = 0;
  private transformX: number;
  private transformY: number;
  private zoom: number;
  private rotate: number;

  private STEP = 0.25;
  private MIN_SCALE = 0.2;
  private MAX_SCALE = 2.5;
  private TRANSFORMX = 0;
  private TRANSFORMY = 0;
  private ZOOM = 1;
  private ROTATE = 0;

  constructor(el: HTMLElementPlus, options: Options = {}) {
    this.el = el;
    this.transformX = options.transformX || this.TRANSFORMX;
    this.transformY = options.transformY || this.TRANSFORMY;
    this.zoom = options.zoom || this.ZOOM;
    this.rotate = options.rotate || this.ROTATE;

    this.handleDefaultDraggable();
    this.onDraggable();
    this.onMouseWheel();
  }
  handleDefaultDraggable(): void {
    document.body.ondragstart = () => {
      window.event.returnValue = false;
      return false;
    };
  }
  onDraggable(): void {
    this.el.onmousedown = (e: MouseEvent) => {
      const ox = e.clientX;
      const oy = e.clientY;
      document.onmousemove = (e1: MouseEvent) => {
        const disX = e1.clientX - ox;
        const disY = e1.clientY - oy;
        this.transformX = this.oTransformX + disX;
        this.transformY = this.oTransformY + disY;
        this.el.style.cursor = 'grabbing';
        this.setPosition();
      };
    };
    document.onmouseup = () => {
      document.onmousemove = null;
      this.oTransformX = this.transformX;
      this.oTransformY = this.transformY;
      this.el.style.cursor = 'grab';
    };
  }
  onMouseWheel(): void {
    const handleWheel = this.throttle(this.setMouseWheel, 100);
    this.el.onmousewheel = (e) => {
      const value: number = -e.wheelDelta || e.deltaY || e.detail;
      handleWheel(value);
    };
  }
  throttle(fn: (...args: unknown[]) => void, t: number): void {
    let timer = null;
    return (...args) => {
      if (!timer) {
        setTimeout(() => {
          timer = null;
          fn.apply(this, args);
        }, t);
      }
    };
  }
  setMouseWheel(value: number): void {
    if (value < 0) {
      if (this.zoom >= this.MAX_SCALE) {
        this.el.style.cursor = 'not-allowed';
        return;
      }
      this.el.style.cursor = 'zoom-in';
      this.setZoomIn(this.STEP);
    } else {
      if (this.zoom <= this.MIN_SCALE) {
        this.el.style.cursor = 'not-allowed';
        return;
      }
      this.el.style.cursor = 'zoom-out';
      this.setZoomOut(this.STEP);
    }
    this.setPosition();
  }
  setZoomIn(step = this.STEP): void {
    this.zoom = Math.min(this.MAX_SCALE, this.zoom + step);
    this.setPosition();
  }
  setZoomOut(step = this.STEP): void {
    this.zoom = Math.max(this.MIN_SCALE, this.zoom - step);
    this.setPosition();
  }
  setZoomBest(): void {
    this.reset();
    this.setPosition();
  }
  setZoomOriginal(): void {
    this.reset();
    this.setPosition();
  }
  setRotate(): void {
    this.rotate += 0.25;
    this.setPosition();
  }
  reset(): void {
    this.transformX = this.TRANSFORMX;
    this.transformY = this.TRANSFORMY;
    this.oTransformX = this.transformX;
    this.oTransformY = this.transformY;
    this.zoom = this.ZOOM;
  }
  setPosition(): void {
    this.el.style.transform = `translate(${this.transformX}px, ${this.transformY}px) scale(${this.zoom}) rotate(${this.rotate}turn)`;
  }
}