import { Keybinding } from './keybinding'
import { Disposable } from '@/utils/lifecycle'
import { computed, watchEffect } from 'vue'
// import { useThemes } from '@/hooks/useThemes'
import { DesignUnitMode } from '@/configs/background'
import { PiBy180, isMobile } from '@/utils/common'
import { TAxis, Point, Rect as fabricRect, Object as FabricObject, TPointerEventInfo, TPointerEvent } from 'fabric'
import { useMainStore, useTemplatesStore } from '@/store'
import { storeToRefs } from 'pinia'
import { px2mm } from '@/utils/image'
import { ElementNames } from '@/types/elements'
import { FabricCanvas } from './fabricCanvas'
import { ReferenceLine } from '@/extension/object/ReferenceLine'
import { WorkSpaceDrawType } from '@/configs/canvas'

type Rect = { left: number; top: number; width: number; height: number }

/**
 * 配置
 */
export interface RulerOptions {
  /**
   * 标尺宽高
   * @default 10
   */
  ruleSize?: number

  /**
   * 字体大小
   * @default 10
   */
  fontSize?: number

  /**
   * 是否开启标尺
   * @default false
   */
  enabled?: boolean

  /**
   * 背景颜色
   */
  backgroundColor?: string

  /**
   * 文字颜色
   */
  textColor?: string

  /**
   * 边框颜色
   */
  borderColor?: string

  /**
   * 高亮颜色
   */
  highlightColor?: string
  /**
   * 高亮颜色
   */
  unitName: string

}

export type HighlightRect = {skip?: TAxis} & Rect

export class FabricRuler extends Disposable {
  private canvasEvents
  public lastCursor: string
  public workSpaceDraw?: fabricRect
  public options: Required<RulerOptions>
  public tempReferenceLine?: ReferenceLine
  private activeOn: string = "up"
  private objectRect: undefined | { 
    x: HighlightRect[],
    y: HighlightRect[]
  }

  constructor(private readonly canvas: FabricCanvas) {
    super()
    this.lastCursor = this.canvas.defaultCursor
    // 合并默认配置
    this.options = Object.assign({
      ruleSize: 20,
      fontSize: 8,
      enabled: isMobile() ? false : true,
    })

    // const { isDark } = useThemes()
    const isDark = false
    
    const { unitMode } = storeToRefs(useMainStore())
    watchEffect(() => {
      const unitName = DesignUnitMode.filter(ele => ele.id === unitMode.value)[0].name
      this.options = {
        ...this.options,
        ...(isDark 
          ? {
              backgroundColor: '#242424',
              borderColor: '#555',
              highlightColor: '#165dff3b',
              textColor: '#ddd',
              unitName: unitName,
            }
          : {
              backgroundColor: '#fff',
              borderColor: '#ccc',
              highlightColor: '#165dff3b',
              textColor: '#444',
              unitName: unitName,
            }),
      }
      this.render({ ctx: this.canvas.contextContainer })
    })
    
    this.canvasEvents = {
      'after:render': this.render.bind(this),
      'mouse:move': this.mouseMove.bind(this),
      'mouse:down': this.mouseDown.bind(this),
      'mouse:up': this.mouseUp.bind(this),
      'referenceline:moving': this.referenceLineMoving.bind(this),
      'referenceline:mouseup': this.referenceLineMouseup.bind(this),
    }
    this.enabled = this.options.enabled
    canvas.ruler = this
  }

  public getPointHover(point: Point): 'vertical' | 'horizontal' | '' {
    if (
      new fabricRect({
        left: 0,
        top: 0,
        width: this.options.ruleSize,
        height: this.canvas.height,
        absolutePositioned: true,
      }).containsPoint(point)
    ) {
      return 'vertical';
    } else if (
      new fabricRect({
        left: 0,
        top: 0,
        width: this.canvas.width, 
        height: this.options.ruleSize,
        absolutePositioned: true,
      }).containsPoint(point)
    ) {
      return 'horizontal';
    }
    return '';
  }

  private mouseMove(e: TPointerEventInfo<TPointerEvent>) {
    if (!e.viewportPoint) return
    if (this.tempReferenceLine && e.scenePoint) {
      const pos: Partial<ReferenceLine> = {};
      if (this.tempReferenceLine.axis === 'horizontal') {
        pos.top = e.scenePoint.y;
      } 
      else {
        pos.left = e.scenePoint.x;
      }
      this.tempReferenceLine.set({ ...pos, visible: true });
      this.canvas.renderAll();
      const event = this.getCommonEventInfo(e) as any;
      this.canvas.fire('object:moving', event);
      this.tempReferenceLine.fire('moving', event);
    }
    const status = this.getPointHover(e.viewportPoint)
    this.canvas.defaultCursor = this.lastCursor
    if (!status) return
    this.lastCursor = this.canvas.defaultCursor
    this.canvas.defaultCursor = status === 'horizontal' ? 'ns-resize' : 'ew-resize';
  }

  private mouseDown(e: TPointerEventInfo<TPointerEvent>) {
    const pointHover = this.getPointHover(e.viewportPoint)
    if (!pointHover) return
    if (this.activeOn === 'up') {
      this.canvas.selection = false
      this.activeOn = 'down'
      const point = pointHover === 'horizontal' ? e.viewportPoint.y : e.viewportPoint.x
      this.tempReferenceLine = new ReferenceLine(
        point,
        {
          type: 'ReferenceLine',
          axis: pointHover,
          visible: false,
          name: 'ReferenceLine',
          hasControls: false,
          hasBorders: false,
          stroke: 'pink',
          fill: 'pink',
          originX: 'center',
          originY: 'center',
          padding: 4,
          globalCompositeOperation: 'difference',
        }
      );
      this.canvas.add(this.tempReferenceLine)
      const templatesStore = useTemplatesStore()
      templatesStore.addElement(this.tempReferenceLine)
      this.canvas.setActiveObject(this.tempReferenceLine)
      this.canvas._setupCurrentTransform(e.e, this.tempReferenceLine, true)
      this.tempReferenceLine.fire('down', this.getCommonEventInfo(e));
    }
  }

  private getCommonEventInfo(e: TPointerEventInfo<TPointerEvent>) {
    if (!this.tempReferenceLine || !e.scenePoint) return;
    return {
      e: e.e,
      transform: this.tempReferenceLine.get('transform'),
      pointer: {
        x: e.scenePoint.x,
        y: e.scenePoint.y,
      },
      target: this.tempReferenceLine,
    };
  }

  private mouseUp(e: TPointerEventInfo<TPointerEvent>) {
    if (this.activeOn !== 'down') return;
    this.canvas.selection = true
    this.tempReferenceLine!.selectable = false
    this.canvas.renderAll()
    this.activeOn = 'up';
    // @ts-ignore
    this.tempReferenceLine?.fire('up', this.getCommonEventInfo(e));
    this.tempReferenceLine = undefined;
  }

  public setWorkSpaceDraw() {
    this.workSpaceDraw = this.canvas.getObjects().filter(item => item.id === WorkSpaceDrawType)[0] as fabricRect
  }

  public isRectOut(object: FabricObject, target: ReferenceLine): boolean {
    // const { top, height, left, width } = object;

    // if (top === undefined || height === undefined || left === undefined || width === undefined) {
    //   return false;
    // }

    // const targetRect = target.getBoundingRect(true, true);
    // const {
    //   top: targetTop,
    //   height: targetHeight,
    //   left: targetLeft,
    //   width: targetWidth,
    // } = targetRect;

    // if (target.isHorizontal() && (top > targetTop + 1 || top + height < targetTop + targetHeight - 1)) {
    //   return true;
    // } 
    // else if (!target.isHorizontal() && (left > targetLeft + 1 || left + width < targetLeft + targetWidth - 1)) {
    //   return true;
    // }

    return false;
  };

  referenceLineMoving(e: any) {
    if (!this.workSpaceDraw) {
      this.setWorkSpaceDraw();
      return;
    }
    const { target } = e;
    if (this.isRectOut(this.workSpaceDraw, target)) {
      target.moveCursor = 'not-allowed';
    }
  } 

  referenceLineMouseup(e: any) {
    if (!this.workSpaceDraw) {
      this.setWorkSpaceDraw();
      return;
    }
    const { target } = e;
    if (this.isRectOut(this.workSpaceDraw, target)) {
      this.canvas.remove(target);
      this.canvas.setCursor(this.canvas.defaultCursor ?? '');
    }
  }

  public get enabled() {
    return this.options.enabled
  }

  public set enabled(value) {
    this.options.enabled = value
    if (value) {
      this.canvas.on(this.canvasEvents)
      this.render({ ctx: this.canvas.contextContainer })
    } 
    else {
      this.canvas.off(this.canvasEvents)
      this.canvas.requestRenderAll()
    }
  }

  /**
   * 获取画板尺寸
   */
  private getSize() {
    return {
      width: this.canvas.width,
      height: this.canvas.height,
    }
  }

  private render({ ctx }: { ctx: CanvasRenderingContext2D }) {
    if (ctx !== this.canvas.contextContainer) return

    const { viewportTransform: vpt } = this.canvas

    // 计算元素矩形
    this.calcObjectRect()

    // 绘制尺子
    this.draw({
      ctx,
      isHorizontal: true,
      rulerLength: this.getSize().width,
      startCalibration: -(vpt[4] / vpt[0]),
    })
    this.draw({
      ctx,
      isHorizontal: false,
      rulerLength: this.getSize().height,
      startCalibration: -(vpt[5] / vpt[3]),
    })

    const { borderColor, backgroundColor, ruleSize, textColor } = this.options

    this.darwRect(ctx, {
      left: 0,
      top: 0,
      width: ruleSize,
      height: ruleSize,
      fill: backgroundColor,
      stroke: borderColor,
    })

    this.darwText(ctx, {
      text: this.options.unitName,
      left: ruleSize / 2,
      top: ruleSize / 2,
      align: 'center',
      baseline: 'middle',
      fill: textColor,
    })
  }

  private draw(opt: {ctx: CanvasRenderingContext2D, isHorizontal: boolean, rulerLength: number, startCalibration: number}) {
    const { ctx, isHorizontal, rulerLength, startCalibration } = opt
    const zoom = this.canvas.getZoom()

    const gap = this.getGap(zoom)
    const unitLength = Math.ceil(rulerLength / zoom)
    const startValue = Math.floor(startCalibration / gap) * gap
    const startOffset = startValue - startCalibration

    const canvasSize = this.getSize()

    const { textColor, borderColor, ruleSize, highlightColor } = this.options

    // 文字顶部偏移
    const padding = 2.5

    // 背景
    this.darwRect(ctx, {
      left: 0,
      top: 0,
      width: isHorizontal ? canvasSize.width : ruleSize,
      height: isHorizontal ? ruleSize : canvasSize.height,
      fill: this.options.backgroundColor,
      stroke: this.options.borderColor,
    })

    // 标尺刻度线显示
    for (let pos = 0; pos + startOffset <= unitLength; pos += gap) {
      for (let index = 0; index < 10; index++) {
        const position = Math.round((startOffset + pos + (gap * index) / 10) * zoom)
        const isMajorLine = index === 0
        const [left, top] = isHorizontal ? [position, isMajorLine ? 0 : ruleSize - 8] : [isMajorLine ? 0 : ruleSize - 8, position]
        const [width, height] = isHorizontal ? [0, ruleSize - top] : [ruleSize - left, 0]
        this.darwLine(ctx, {
          left,
          top,
          width,
          height,
          stroke: borderColor,
        })
      }
    }

    // 标尺蓝色遮罩
    if (this.objectRect) {
      const axis = isHorizontal ? 'x' : 'y'
      this.objectRect[axis].forEach((rect) => {
        // 跳过指定矩形
        if (rect.skip === axis) return

        const [left, top, width, height] = isHorizontal ? [(rect.left - startCalibration) * zoom, 0, rect.width * zoom, ruleSize] : [0, (rect.top - startCalibration) * zoom, ruleSize, rect.height * zoom]

        // 高亮遮罩
        // ctx.save()
        this.darwRect(ctx, {
          left,
          top,
          width,
          height,
          fill: highlightColor,
        })
        // ctx.restore()
      })
    }

    // 标尺文字显示
    for (let pos = 0; pos + startOffset <= unitLength; pos += gap) {
      const position = (startOffset + pos) * zoom
      let textValue = (startValue + pos).toString()
      if (this.options.unitName === 'mm') {
        textValue = px2mm(startValue + pos).toFixed(0)
      }
      const [left, top, angle] = isHorizontal ? [position + 6, padding, 0] : [padding, position - 6, -90]

      this.darwText(ctx, {
        text: textValue,
        left,
        top,
        fill: textColor,
        angle,
      })
    }
    // draw end
  }

  private getGap(zoom: number) {
    const zooms = [0.02, 0.03, 0.05, 0.1, 0.2, 0.5, 1, 2, 5]
    const gaps = [5000, 2500, 1000, 500, 200, 100, 50, 20, 10]

    let i = 0
    while (i < zooms.length && zooms[i] < zoom) {
      i++
    }

    return gaps[i - 1] || 10000
  }

  private darwRect(
    ctx: CanvasRenderingContext2D,
    {
      left,
      top,
      width,
      height,
      fill,
      stroke,
      strokeWidth,
    }: {
      left: number
      top: number
      width: number
      height: number
      fill?: string | CanvasGradient | CanvasPattern
      stroke?: string
      strokeWidth?: number
    },
  ) {
    ctx.save()
    ctx.beginPath()
    fill && (ctx.fillStyle = fill)
    ctx.rect(left, top, width, height)
    ctx.fill()
    if (stroke) {
      ctx.strokeStyle = stroke
      ctx.lineWidth = strokeWidth ?? 1
      ctx.stroke()
    }
    ctx.restore()
  }

  private darwText(
    ctx: CanvasRenderingContext2D,
    {
      left,
      top,
      text,
      fill,
      align,
      angle,
      fontSize,
      baseline,
    }: {
      left: number
      top: number
      text: string
      fill?: string | CanvasGradient | CanvasPattern
      align?: CanvasTextAlign
      baseline?: CanvasTextBaseline
      angle?: number
      fontSize?: number
    },
  ) {
    ctx.save()
    fill && (ctx.fillStyle = fill)
    ctx.textAlign = align ?? 'left'
    ctx.textBaseline = baseline ?? 'top'
    ctx.font = `${fontSize ?? 12}px Helvetica`
    if (angle) {
      ctx.translate(left, top)
      ctx.rotate(PiBy180 * angle)
      ctx.translate(-left, -top)
    }
    ctx.fillText(text, left, top)
    ctx.restore()
  }

  private darwLine(
    ctx: CanvasRenderingContext2D,
    {
      left,
      top,
      width,
      height,
      stroke,
      lineWidth,
    }: {
      left: number
      top: number
      width: number
      height: number
      stroke?: string | CanvasGradient | CanvasPattern
      lineWidth?: number
    },
  ) {
    ctx.save()
    ctx.beginPath()
    stroke && (ctx.strokeStyle = stroke)
    ctx.lineWidth = lineWidth ?? 1
    ctx.moveTo(left, top)
    ctx.lineTo(left + width, top + height)
    ctx.stroke()
    ctx.restore()
  }

  private calcObjectRect() {
    const activeObjects = this.canvas.getActiveObjects()
    if (activeObjects.length === 0) {
      this.objectRect = undefined
      return
    }
    if (activeObjects[0].name.toLowerCase() === ElementNames.REFERENCELINE) {
      this.objectRect = undefined
      return
    }
    const allRect = activeObjects.reduce((rects, obj) => {
      const rect: HighlightRect = obj.getBoundingRect()
      rects.push(rect)
      return rects
    }, [] as HighlightRect[])
    if (allRect.length === 0) return
    this.objectRect = {
      x: this.mergeLines(allRect, true),
      y: this.mergeLines(allRect, false),
    }
  }

  private mergeLines(rect: Rect[], isHorizontal: boolean) {
    const axis = isHorizontal ? 'left' : 'top'
    const length = isHorizontal ? 'width' : 'height'
    // 先按照 axis 的大小排序
    rect.sort((a, b) => a[axis] - b[axis])
    const mergedLines = []
    let currentLine = Object.assign({}, rect[0])
    for (let i = 1; i < rect.length; i++) {
      const line = Object.assign({}, rect[i])
      if (currentLine[axis] + currentLine[length] >= line[axis]) {
        // 当前线段和下一个线段相交，合并宽度
        currentLine[length] =
          Math.max(currentLine[axis] + currentLine[length], line[axis] + line[length]) -
          currentLine[axis]
      } else {
        // 当前线段和下一个线段不相交，将当前线段加入结果数组中，并更新当前线段为下一个线段
        mergedLines.push(currentLine)
        currentLine = Object.assign({}, line)
      }
    }
    // 加入数组
    mergedLines.push(currentLine)
    return mergedLines
  }

  public dispose(): void {
    super.dispose()
    this.enabled = false
  }
}
