class Resizable {
  handleMouseDown: (e: MouseEvent) => void;
  handleMouseUp: (e: MouseEvent) => void;
  handleMouseMove: (e: MouseEvent) => void;
  handleArray: string[];
  isResizing: boolean = false;
  data: any;
  constructor(
    public document: Document,
    public el: HTMLElement,
    public options: any
  ) {
    this.handleMouseDown = this.onMouseDown.bind(this);
    this.handleMouseUp = this.onMouseUp.bind(this);
    this.handleMouseMove = this.onMouseMove.bind(this);
    this.handleArray = options.handles
      .trim()
      .split(',')
      .map((n: string) => n.trim());
    this.init();
  }
  init() {
    const { el, document } = this;
    document.addEventListener('mousedown', this.handleMouseDown);
    document.addEventListener('mousemove', this.handleMouseMove);
  }
  onMouseDown(e: MouseEvent) {
    const { el, document } = this;
    const dir = this.getDirection(e.clientX, e.clientY);
    if (!dir) return;
    document.addEventListener('mouseup', this.handleMouseUp);
    this.startResize(dir, e.clientX, e.clientY);
  }
  onMouseUp() {
    const { document } = this;
    this.stopResize();
    document.removeEventListener('mouseup', this.handleMouseUp);
  }
  onMouseMove(e: MouseEvent) {
    const { el, isResizing } = this;
    const dir = this.getDirection(e.clientX, e.clientY);
    this.setCursor(dir);
    if (isResizing) {
      this.resize(e.clientX, e.clientY);
      this.applyResize();
    }
  }
  setCursor(dir: string | null) {
    const { el, options } = this;
    const cursor = dir ? `${dir}-resize` : '';
    el.style.cursor = cursor;
    if (cursor) {
      el.classList.add('is-cursor');
    } else {
      el.classList.remove('is-cursor');
    }
  }
  getDirection(clientX: number, clientY: number): string | null {
    const { el, handleArray, options } = this;
    const { edge } = options;
    const { top, left, width, height } = this.el.getBoundingClientRect();
    let dir = '';
    if (clientY > top - edge && clientY < top + edge) {
      dir += 'n';
    } else if (clientY < top + height + edge && clientY > top + height - edge) {
      dir += 's';
    }
    if (clientX > left - edge && clientX < left + edge) {
      dir += 'w';
    } else if (clientX < left + width + edge && clientX > left + width - edge) {
      dir += 'e';
    }
    for (let i = 0; i < handleArray.length; i++) {
      const handle = handleArray[i];
      if (handle === 'all' || handle === dir) {
        return dir;
      }
    }
    return null;
  }
  startResize(dir: string, x: number, y: number) {
    const { document, el } = this;
    const { left, top, width, height } = this.el.getBoundingClientRect();
    this.data = {
      dir: dir,
      startLeft: left,
      startTop: top,
      left: left,
      top: top,
      startX: x,
      startY: y,
      startWidth: width,
      startHeight: height,
      width: width, // outerWidth
      height: height, // outerHeight
      deltaWidth: 0,
      deltaHeight: 0
    };
    this.isResizing = true;
    document.body.classList.add('user-select-none');
    el.classList.add(`${dir}-resizing`);
  }
  stopResize() {
    const { document, el, data, options } = this;
    this.isResizing = false;
    document.body.classList.remove('user-select-none');
    el.classList.remove(`${data.dir}-resizing`);
    const { onChange } = options;
    onChange && onChange();
  }
  resize(clientX: number, clientY: number) {
    const { data, options } = this;
    if (!data.dir) return;
    if (data.dir.includes('e')) {
      let width = data.startWidth + clientX - data.startX;
      width = Math.min(Math.max(width, options.minWidth), options.maxWidth);
      data.width = width;
    }
    if (data.dir.includes('s')) {
      let height = data.startHeight + clientY - data.startY;
      height = Math.min(Math.max(height, options.minHeight), options.maxHeight);
      data.height = height;
    }
    if (data.dir.includes('w')) {
      let width = data.startWidth - clientX + data.startX;
      width = Math.min(Math.max(width, options.minWidth), options.maxWidth);
      data.width = width;
      data.left = data.startLeft + data.startWidth - data.width;
    }
    if (data.dir.includes('n')) {
      let height = data.startHeight - clientY + data.startY;
      height = Math.min(Math.max(height, options.minHeight), options.maxHeight);
      data.height = height;
      data.top = data.startTop + data.startHeight - data.height;
    }
  }
  applyResize() {
    const { data, el } = this;
    switch (data.dir) {
      case 's':
        el.style.height = `${data.height - data.deltaHeight}px`;
        break;
      case 'w':
        el.style.width = `${data.width - data.deltaWidth}px`;
        break;
      case 'e':
        el.style.width = `${data.width - data.deltaWidth}px`;
        break;
      case 'n':
        el.style.height = `${data.height - data.deltaHeight}px`;
        break;
      default:
        el.style.width = `${data.width - data.deltaWidth}px`;
        el.style.height = `${data.height - data.deltaHeight}px`;
        break;
    }
  }
  destroy() {
    document.removeEventListener('mousedown', this.handleMouseDown);
    document.removeEventListener('mousemove', this.handleMouseMove);
  }
}

export default {
  mounted(el: any, binding: any) {
    const options = {
      name: '',
      handles: 'all',
      minWidth: 50,
      minHeight: 50,
      maxWidth: 500,
      maxHeight: 1000,
      edge: 8, // 距离边缘多少时显示鼠标Cursor
      onChange: null,
      ...binding.value
    };
    el.__resizable__ = new Resizable(window.document, el, options);
  },
  beforeUnmount(el: any) {
    if (el && el.__resizable__) {
      el.__resizable__.destroy();
    }
  }
};
