import { CONSTANTS } from '../../constants/constant'

//  视图操作类
interface ViewOptions {
  mindMap: any; // Replace 'any' with actual MindMap type
  viewData?: any; // Replace 'any' with actual ViewData type
}

interface TransformData {
  transform: any; // Replace 'any' with actual Transform type
  state: {
    scale: number;
    x: number;
    y: number;
    sx: number;
    sy: number;
  }
}

class View {
  private opt: ViewOptions;
  private mindMap: any; // Replace 'any' with actual MindMap type
  private scale: number;
  private sx: number;
  private sy: number;
  private x: number;
  private y: number;
  private firstDrag: boolean;

  constructor(opt: ViewOptions = {} as ViewOptions) {
    this.opt = opt;
    this.mindMap = this.opt.mindMap;
    this.scale = 1;
    this.sx = 0;
    this.sy = 0;
    this.x = 0;
    this.y = 0;
    this.firstDrag = true;
    this.setTransformData(this.mindMap.opt.viewData);
    this.bind();
  }

  private bind(): void {
    // Keyboard shortcuts
    this.mindMap.keyCommand.addShortcut('Control+=', (): void => {
      this.enlarge();
    });
    this.mindMap.keyCommand.addShortcut('Control+-', (): void => {
      this.narrow();
    });
    this.mindMap.keyCommand.addShortcut('Control+i', (): void => {
      this.fit();
    });

    // View dragging
    this.mindMap.event.on('mousedown', (e: MouseEvent): void => {
      const { isDisableDrag, mousedownEventPreventDefault } = this.mindMap.opt;
      if (isDisableDrag) return;
      if (mousedownEventPreventDefault) {
        e.preventDefault();
      }
      this.sx = this.x;
      this.sy = this.y;
    });

    this.mindMap.event.on('drag', (e: MouseEvent, event: { mousemoveOffset: { x: number; y: number } }): void => {
      if (e.ctrlKey || e.metaKey || this.mindMap.opt.isDisableDrag) {
        return;
      }
      if (this.firstDrag) {
        this.firstDrag = false;
        if (this.mindMap.renderer.activeNodeList.length > 0) {
          this.mindMap.execCommand('CLEAR_ACTIVE_NODE');
        }
      }
      this.x = this.sx + event.mousemoveOffset.x;
      this.y = this.sy + event.mousemoveOffset.y;
      this.transform();
    });

    this.mindMap.event.on('mouseup', (): void => {
      this.firstDrag = true;
    });

    // View zooming
    this.mindMap.event.on('mousewheel', (e: WheelEvent, dirs: string[], event: Event, isTouchPad: boolean): void => {
      const {
        customHandleMousewheel,
        mousewheelAction,
        mouseScaleCenterUseMousePosition,
        mousewheelMoveStep,
        mousewheelZoomActionReverse,
        disableMouseWheelZoom,
        translateRatio
      } = this.mindMap.opt;

      if (customHandleMousewheel && typeof customHandleMousewheel === 'function') {
        return customHandleMousewheel(e);
      }

      if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM || e.ctrlKey || e.metaKey) {
        if (disableMouseWheelZoom) return;

        const { x: clientX, y: clientY } = this.mindMap.toPos(e.clientX, e.clientY);
        const cx = mouseScaleCenterUseMousePosition ? clientX : undefined;
        const cy = mouseScaleCenterUseMousePosition ? clientY : undefined;

        if (isTouchPad && (dirs.includes(CONSTANTS.DIR.LEFT) || dirs.includes(CONSTANTS.DIR.RIGHT))) {
          dirs = dirs.filter(dir => ![CONSTANTS.DIR.LEFT, CONSTANTS.DIR.RIGHT].includes(dir));
        }

        switch (true) {
          case dirs.includes(CONSTANTS.DIR.UP || CONSTANTS.DIR.LEFT):
            mousewheelZoomActionReverse
              ? this.enlarge(cx, cy, isTouchPad)
              : this.narrow(cx, cy, isTouchPad);
            break;
          case dirs.includes(CONSTANTS.DIR.DOWN || CONSTANTS.DIR.RIGHT):
            mousewheelZoomActionReverse
              ? this.narrow(cx, cy, isTouchPad)
              : this.enlarge(cx, cy, isTouchPad);
            break;
        }
      } else {
        let stepX = 0;
        let stepY = 0;

        if (isTouchPad) {
          stepX = Math.abs((e as any).wheelDeltaX);
          stepY = Math.abs((e as any).wheelDeltaY);
        } else {
          stepX = stepY = mousewheelMoveStep;
        }

        let mx = 0;
        let my = 0;

        if (dirs.includes(CONSTANTS.DIR.DOWN)) my = -stepY;
        if (dirs.includes(CONSTANTS.DIR.UP)) my = stepY;
        if (dirs.includes(CONSTANTS.DIR.LEFT)) mx = stepX;
        if (dirs.includes(CONSTANTS.DIR.RIGHT)) mx = -stepX;

        this.translateXY(mx * translateRatio, my * translateRatio);
      }
    });

    this.mindMap.on('resize', (): void => {
      if (!this.checkNeedMindMapInCanvas()) return;
      this.transform();
    });
  }

  public getTransformData(): TransformData {
    return {
      transform: this.mindMap.draw.transform(),
      state: {
        scale: this.scale,
        x: this.x,
        y: this.y,
        sx: this.sx,
        sy: this.sy
      }
    };
  }


  /**
  * 动态设置变换状态数据
  */
  setTransformData(viewData: {
    state: Record<string, any>;
    transform: Record<string, any>;
  }): void {
    if (viewData) {
      Object.keys(viewData.state).forEach((prop: string) => {
        (this as any)[prop] = viewData.state[prop];
      });
      this.mindMap.draw.transform({
        ...viewData.transform
      });
      this.mindMap.emit('view_data_change', this.getTransformData());
      this.emitEvent('scale');
      this.emitEvent('translate');
    }
  }

  /**
   * 平移x,y方向
   */
  translateXY(x: number, y: number): void {
    if (x === 0 && y === 0) return;
    this.x += x;
    this.y += y;
    this.transform();
    this.emitEvent('translate');
  }

  /**
   * 平移x方向
   */
  translateX(step: number): void {
    if (step === 0) return;
    this.x += step;
    this.transform();
    this.emitEvent('translate');
  }

  /**
   * 平移x方向到
   */
  translateXTo(x: number): void {
    this.x = x;
    this.transform();
    this.emitEvent('translate');
  }

  /**
   * 平移y方向
   */
  translateY(step: number): void {
    if (step === 0) return;
    this.y += step;
    this.transform();
    this.emitEvent('translate');
  }

  /**
   * 平移y方向到
   */
  translateYTo(y: number): void {
    this.y = y;
    this.transform();
    this.emitEvent('translate');
  }

  /**
   * 应用变换
   */
  transform(): void {
    try {
      this.limitMindMapInCanvas();
    } catch (error) { }
    this.mindMap.draw.transform({
      origin: [0, 0],
      scale: this.scale,
      translate: [this.x, this.y]
    });
    this.mindMap.emit('view_data_change', this.getTransformData());
  }

  /**
   * 恢复
   */
  reset(): void {
    const scaleChange: boolean = this.scale !== 1;
    const translateChange: boolean = this.x !== 0 || this.y !== 0;
    this.scale = 1;
    this.x = 0;
    this.y = 0;
    this.transform();
    if (scaleChange) {
      this.emitEvent('scale');
    }
    if (translateChange) {
      this.emitEvent('translate');
    }
  }

  /**
 * Narrow/zoom out the view
 * @param cx - Center x coordinate
 * @param cy - Center y coordinate 
 * @param isTouchPad - Whether triggered by touchpad
 */
  narrow(cx?: number, cy?: number, isTouchPad?: boolean): void {
    let { scaleRatio, minZoomRatio } = this.mindMap.opt;
    scaleRatio = scaleRatio / (isTouchPad ? 5 : 1);
    const scale = Math.max(this.scale - scaleRatio, minZoomRatio / 100);
    this.scaleInCenter(scale, cx, cy);
    this.transform();
    this.emitEvent('scale');
  }

  /**
   * Enlarge/zoom in the view
   * @param cx - Center x coordinate
   * @param cy - Center y coordinate
   * @param isTouchPad - Whether triggered by touchpad
   */
  enlarge(cx?: number, cy?: number, isTouchPad?: boolean): void {
    let { scaleRatio, maxZoomRatio } = this.mindMap.opt;
    scaleRatio = scaleRatio / (isTouchPad ? 5 : 1);
    let scale = 0;
    if (maxZoomRatio === -1) {
      scale = this.scale + scaleRatio;
    } else {
      scale = Math.min(this.scale + scaleRatio, maxZoomRatio / 100);
    }
    this.scaleInCenter(scale, cx, cy);
    this.transform();
    this.emitEvent('scale');
  }

  /**
   * Scale view based on specified center point
   * @param scale - Scale ratio
   * @param cx - Center x coordinate
   * @param cy - Center y coordinate
   */
  scaleInCenter(scale: number, cx?: number, cy?: number): void {
    if (cx === undefined || cy === undefined) {
      cx = this.mindMap.width / 2;
      cy = this.mindMap.height / 2;
    }
    const prevScale = this.scale;
    const ratio = 1 - scale / prevScale;
    const dx = (cx - this.x) * ratio;
    const dy = (cy - this.y) * ratio;
    this.x += dx;
    this.y += dy;
    this.scale = scale;
  }

  /**
   * Set scale directly
   * @param scale - Scale ratio
   * @param cx - Center x coordinate
   * @param cy - Center y coordinate
   */
  setScale(scale: number, cx?: number, cy?: number): void {
    if (cx !== undefined && cy !== undefined) {
      this.scaleInCenter(scale, cx, cy);
    } else {
      this.scale = scale;
    }
    this.transform();
    this.emitEvent('scale');
  }

  /**
   * Fit view to canvas size
   * @param getRbox - Function to get bounding box
   * @param enlarge - Whether to allow enlarging
   * @param fitPadding - Padding for fit calculation
   */
  fit(getRbox: () => any = () => { }, enlarge: boolean = false, fitPadding?: number): void {
    fitPadding =
      fitPadding === undefined ? this.mindMap.opt.fitPadding : fitPadding;
    const draw = this.mindMap.draw;
    const origTransform = draw.transform();
    const rect = getRbox() || draw.rbox();
    const drawWidth = rect.width / origTransform.scaleX;
    const drawHeight = rect.height / origTransform.scaleY;
    const drawRatio = drawWidth / drawHeight;
    let { width: elWidth, height: elHeight } = this.mindMap.elRect;
    elWidth = elWidth - fitPadding * 2;
    elHeight = elHeight - fitPadding * 2;
    const elRatio = elWidth / elHeight;
    let newScale = 0;
    let flag: string | number = '';
    if (drawWidth <= elWidth && drawHeight <= elHeight && !enlarge) {
      newScale = 1;
      flag = 1;
    } else {
      let newWidth = 0;
      let newHeight = 0;
      if (drawRatio > elRatio) {
        newWidth = elWidth;
        newHeight = elWidth / drawRatio;
        flag = 2;
      } else {
        newHeight = elHeight;
        newWidth = elHeight * drawRatio;
        flag = 3;
      }
      newScale = newWidth / drawWidth;
    }
    this.setScale(newScale);
    const newRect = getRbox() || draw.rbox();
    newRect.x -= this.mindMap.elRect.left;
    newRect.y -= this.mindMap.elRect.top;
    let newX = 0;
    let newY = 0;
    if (flag === 1) {
      newX = -newRect.x + fitPadding + (elWidth - newRect.width) / 2;
      newY = -newRect.y + fitPadding + (elHeight - newRect.height) / 2;
    } else if (flag === 2) {
      newX = -newRect.x + fitPadding;
      newY = -newRect.y + fitPadding + (elHeight - newRect.height) / 2;
    } else if (flag === 3) {
      newX = -newRect.x + fitPadding + (elWidth - newRect.width) / 2;
      newY = -newRect.y + fitPadding;
    }
    this.translateXY(newX, newY);
  }

  /**
  * 判断是否需要将思维导图限制在画布内
  */
  private checkNeedMindMapInCanvas(): boolean {
    // 如果当前在演示模式，那么不需要限制
    if (this.mindMap.demonstrate?.isInDemonstrate) {
      return false
    }
    const { isLimitMindMapInCanvasWhenHasScrollbar, isLimitMindMapInCanvas } =
      this.mindMap.opt
    // 如果注册了滚动条插件，那么使用isLimitMindMapInCanvasWhenHasScrollbar配置
    if (this.mindMap.scrollbar) {
      return isLimitMindMapInCanvasWhenHasScrollbar
    } else {
      // 否则使用isLimitMindMapInCanvas配置
      return isLimitMindMapInCanvas
    }
  }

  /**
   * 将思维导图限制在画布内
   */
  private limitMindMapInCanvas(): void {
    if (!this.checkNeedMindMapInCanvas()) return

    let { scale, left, top, right, bottom } = this.getPositionLimit()

    // 画布宽高改变了，但是思维导图元素变换的中心点依旧是原有位置，所以需要加上中心点变化量
    const centerXChange: number =
      ((this.mindMap.width - this.mindMap.initWidth) / 2) * scale
    const centerYChange: number =
      ((this.mindMap.height - this.mindMap.initHeight) / 2) * scale

    // 如果缩放值改变了
    const scaleRatio: number = this.scale / scale
    left *= scaleRatio
    right *= scaleRatio
    top *= scaleRatio
    bottom *= scaleRatio

    // 加上画布中心点距离
    const centerX: number = this.mindMap.width / 2
    const centerY: number = this.mindMap.height / 2
    const scaleOffset: number = this.scale - 1
    left -= scaleOffset * centerX - centerXChange
    right -= scaleOffset * centerX - centerXChange
    top -= scaleOffset * centerY - centerYChange
    bottom -= scaleOffset * centerY - centerYChange

    // 判断是否超出边界
    if (this.x > left) {
      this.x = left
    }
    if (this.x < right) {
      this.x = right
    }
    if (this.y > top) {
      this.y = top
    }
    if (this.y < bottom) {
      this.y = bottom
    }
  }

  /**
   * 计算图形四个方向的位置边界值
   */
  private getPositionLimit(): {
    scale: number;
    left: number;
    right: number;
    top: number;
    bottom: number;
  } {
    const { scaleX, scaleY } = this.mindMap.draw.transform()
    const drawRect = this.mindMap.draw.rbox()
    const rootRect = this.mindMap.renderer.root.group.rbox()
    const rootCenterOffset = this.mindMap.renderer.layout.getRootCenterOffset(
      rootRect.width,
      rootRect.height
    )
    const left: number = rootRect.x - drawRect.x - rootCenterOffset.x * scaleX
    const right: number = rootRect.x - drawRect.x2 - rootCenterOffset.x * scaleX
    const top: number = rootRect.y - drawRect.y - rootCenterOffset.y * scaleY
    const bottom: number = rootRect.y - drawRect.y2 - rootCenterOffset.y * scaleY
    return {
      scale: scaleX,
      left,
      right,
      top,
      bottom
    }
  }

  /**
   * 派发事件
   */
  private emitEvent(type: 'scale' | 'translate'): void {
    switch (type) {
      case 'scale':
        this.mindMap.emit('scale', this.scale)
        break
      case 'translate':
        this.mindMap.emit('translate', this.x, this.y)
        break
    }
  }
}

export default View
