import Strings from '@/common/utils/strings.ts'

class MoveHandle {
  private static MoveHandles = new Map<string, MoveHandle>()

  private readonly id: string
  private disabled: boolean
  private container: {
    el?: HTMLElement;
    width: number;
    height: number;
    x: number;
    y: number;
  }
  private widget: {
    el?: HTMLElement;
    width: number;
    height: number;
    x: number;
    y: number;
  }
  private triggerArea?: HTMLElement
  private startPoint: {
    x: number;
    y: number;
  }
  private offset: {
    x: number;
    y: number;
  }
  private dragging: boolean

  constructor(id: string, container?: HTMLElement, widget?: HTMLElement, triggerArea?: HTMLElement) {
    this.id = id
    this.container = {width: 0, height: 0, x: 0, y: 0}
    this.widget = {width: 0, height: 0, x: 0, y: 0}
    this.startPoint = {x: 0, y: 0}
    this.offset = {x: 0, y: 0}
    this.dragging = false
    this.disabled = true
    this.setContainer(container)
      .setWidget(widget)
      .setTriggerArea(triggerArea)
    MoveHandle.MoveHandles.set(this.id, this)
  }

  public static of(id: string, container?: HTMLElement, widget?: HTMLElement, triggerArea?: HTMLElement) {
    if (Strings.isBlank(id)) throw new Error('Id 不能为空')
    return MoveHandle.MoveHandles.get(id) ?? new MoveHandle(id, container, widget, triggerArea)
  }

  public handleEvent(e: Event) {
    if (this.disabled) return
    if (e.type === 'mousedown') {
      this.handlerMousedown(e)
    } else if (e.type === 'mouseup') {
      this.handlerMouseup()
    } else if (e.type === 'mousemove') {
      this.handlerMove(e)
    }
  }

  public setContainer(el?: HTMLElement) {
    if (!this.disabled) throw new Error('不能在启用状态下设置容器')
    this.container.el = el
    if (el != null) {
      const containerRect = el.getBoundingClientRect()
      this.container.width = containerRect.width
      this.container.height = containerRect.height
      this.container.x = containerRect.x
      this.container.y = containerRect.y
    }
    return this
  }

  public setWidget(el?: HTMLElement) {
    if (!this.disabled) throw new Error('不能在启用状态下设置组件')
    this.widget.el = el
    if (el != null) {
      const widgetRect = el.getBoundingClientRect()
      this.widget.width = widgetRect.width
      this.widget.height = widgetRect.height
      this.widget.x = widgetRect.x
      this.widget.y = widgetRect.y
    }
    return this
  }

  public setTriggerArea(el?: HTMLElement) {
    if (!this.disabled) throw new Error('不能在启用状态下设置触发区域')
    this.triggerArea?.removeEventListener('mousedown', this)
    el?.addEventListener('mousedown', this)
    this.triggerArea = el
    return this
  }

  public destroy() {
    MoveHandle.MoveHandles.delete(this.id)
    this.disable()
    this.setContainer()
      .setWidget()
      .setTriggerArea()
  }

  public enable() {
    if (this.container.el == null
      || this.widget.el == null
      || this.triggerArea == null) {
      return
    }
    this.disabled = false
  }

  public disable() {
    this.disabled = true
    return this
  }

  private handlerMove(e: Event) {
    if (!this.dragging
      || this.widget.el == null
    ) return
    const mouseEvent = e as MouseEvent

    let dx = mouseEvent.clientX - this.startPoint.x
    let dy = mouseEvent.clientY - this.startPoint.y
    // 左右边界
    if (dx < 0) {
      const min_dx = this.container.x - this.widget.x
      dx = Math.max(dx, min_dx)
    } else {
      const max_dx = (this.container.x + this.container.width) - (this.widget.x + this.widget.width)
      dx = Math.min(dx, max_dx)
    }
    // 上下边界
    if (dy < 0) {
      const min_dy = this.container.y - this.widget.y
      dy = Math.max(dy, min_dy)
    } else {
      const max_dy = (this.container.y + this.container.height) - (this.widget.y + this.widget.height)
      dy = Math.min(dy, max_dy)
    }
    let newOffsetX = this.offset.x + dx
    let newOffsetY = this.offset.y + dy
    this.widget.el.style.transform = `translate(${newOffsetX}px, ${newOffsetY}px)`
  }

  private handlerMouseup() {
    this.dragging = false
    document.removeEventListener('mousemove', this)
    document.removeEventListener('mouseup', this)
  }

  private handlerMousedown(e: Event) {
    if (this.container.el == null
      || this.widget.el == null
    ) return
    const mouseEvent = e as MouseEvent
    mouseEvent.preventDefault()
    mouseEvent.stopPropagation()

    this.dragging = true
    this.startPoint.x = mouseEvent.clientX
    this.startPoint.y = mouseEvent.clientY

    const transform = window.getComputedStyle(this.widget.el).transform
    let translateX = 0
    let translateY = 0

    if (transform !== 'none') {
      const matrix = new DOMMatrix(transform)
      translateX = matrix.e
      translateY = matrix.f
    } else {
      this.widget.el.style.transform = 'translate(0, 0)'
    }

    this.offset.x = translateX
    this.offset.y = translateY

    const containerRect = this.container.el.getBoundingClientRect()
    const widgetRect = this.widget.el.getBoundingClientRect()
    this.container.width = containerRect.width
    this.container.height = containerRect.height
    this.container.x = containerRect.x
    this.container.y = containerRect.y

    this.widget.width = widgetRect.width
    this.widget.height = widgetRect.height
    this.widget.x = widgetRect.x
    this.widget.y = widgetRect.y
    document.addEventListener('mouseup', this)
    document.addEventListener('mousemove', this)
  }
}

export default MoveHandle.of
