// @ts-nocheck
import copy from 'copy-to-clipboard';
import { getAbsoluteCoordinate, getInitialPoint, getActualPoint } from './annotation/utils.js';
import { isMobileDevice } from './ui_utils';

const markupType = isMobileDevice ? ['highlight', 'underline', 'squiggly', 'strikeout', 'selectText'] : ['highlight', 'underline', 'squiggly', 'strikeout', '', 'redaction', 'remove']
const markupTypeNotNull = ['highlight', 'underline', 'squiggly', 'strikeout', 'redaction', 'remove']
type Point = {
  x: number,
  y: number
}

type PagePtr = {
  doc: number
  pagePtr: number
  textPtr: number
}
export default class TextSelection {
  _selection: string | null
  #pagePtr: number | null
  #textPtr: number | null
  messageHandler: any
  container: HTMLDivElement | null
  textContainer: HTMLDivElement | null
  selected: any
  viewport: {
    width: number
    height: number
  }
  scale: number
  eventBus: any
  _tool: string | null = null
  color: string | null = null
  selecting: boolean = false
  pageViewer: any
  pageIndex: number

  constructor(options: {
    viewport: {
      width: number,
      height: number
    },
    scale: number,
    pageIndex: number,
    container: HTMLDivElement,
    pagePtr: PagePtr,
    messageHandler: any,
    eventBus: any,
    selected: any,
    tool: string,
    color: string,
    pageViewer: any
  }) {
    this._selection = null
    this.messageHandler = options.messageHandler
    this.container = options.container || null
    this.#textPtr = options.pagePtr.textPtr
    this.#pagePtr = options.pagePtr.pagePtr
    this.selected = options.selected
    this.viewport = options.viewport
    this.scale = options.scale
    this.eventBus = options.eventBus
    this.pageIndex = options.pageIndex
    this.pageViewer = options.pageViewer

    this.handleTool = this.handleTool.bind(this)
    this.handleToolMode = this.handleToolMode.bind(this)
    this.handleMouseDown = this.handleMouseDown.bind(this)
    this.handleMouseMove = this.handleMouseMove.bind(this)
    this.handleDobuleClick = this.handleDobuleClick.bind(this)
    this.handleMouseUp = this.handleMouseUp.bind(this)
    this.handleKeyDown = this.handleKeyDown.bind(this)
    this.handleTextPopup = this.handleTextPopup.bind(this)

    this.toolMode = options.toolMode
    this.tool = options.tool
    this.color = options.color
    this.eventBus._on('toolChanged', this.handleTool)
    this.eventBus._on('toolModeChanged', this.handleToolMode)
    this.eventBus._on('textPopupClicked', this.handleTextPopup)
  }

  handleToolMode(mode: string) {
    if (mode === this.toolMode) return
    this.toolMode = mode

    document.removeEventListener('keydown', this.handleKeyDown)
    document.removeEventListener('mousedown', this.handleMouseDown)
    document.removeEventListener('touchstart', this.handleMouseDown)
    document.removeEventListener('mousemove', this.handleMouseMove)
    document.removeEventListener('touchmove', this.handleMouseMove)
    document.removeEventListener('dblclick', this.handleDobuleClick)

    if (['view', 'annotation', 'security'].includes(this.toolMode)) {
      document.addEventListener('keydown', this.handleKeyDown)
      document.addEventListener('mousedown', this.handleMouseDown)
      document.addEventListener('touchstart', this.handleMouseDown)
      document.addEventListener('mousemove', this.handleMouseMove)
      document.addEventListener('touchmove', this.handleMouseMove)
      document.addEventListener('dblclick', this.handleDobuleClick)
    }
  }

  handleTool ({
    tool,
    color
  }: {
    tool: string,
    color: string
  }) {
    this.tool = tool
    this.color = color

    if (markupType.includes(this.tool)) {
      document.body.style.userSelect = 'auto'
      document.body.style.webkitUserSelect = 'auto'
    } else {
      document.body.style.userSelect = 'none'
      document.body.style.webkitUserSelect = 'none'
    }
  }

  get tool () {
    return this._tool
  }

  set tool (toolType: string) {
    if ((toolType === this._tool && toolType !== '')) return
    
    if (!markupType.includes(toolType) || this.toolMode === 'editor') {
      document.removeEventListener('keydown', this.handleKeyDown)
      document.removeEventListener('mousedown', this.handleMouseDown)
      document.removeEventListener('touchstart', this.handleMouseDown)
      document.removeEventListener('mousemove', this.handleMouseMove)
      document.removeEventListener('touchmove', this.handleMouseMove)
      document.removeEventListener('dblclick', this.handleDobuleClick)
      this._tool = toolType
      return
    }
    if (!(markupType.includes(toolType) && markupType.includes(this.tool))) {
      document.addEventListener('keydown', this.handleKeyDown)
      document.addEventListener('mousedown', this.handleMouseDown)
      document.addEventListener('touchstart', this.handleMouseDown)
      document.addEventListener('mousemove', this.handleMouseMove)
      document.addEventListener('touchmove', this.handleMouseMove)
      document.addEventListener('dblclick', this.handleDobuleClick)
    }
    this._tool = toolType
  }

  testPoint(event: MouseEvent) {
    const { x, y, width, height } = getAbsoluteCoordinate(this.container, event)
    if (x < 0 || y < 0 || x > width || y > height) return false
    return {
      x,
      y
    }
  }

  handleKeyDown(event: KeyboardEvent) {
    if (event.key.toLocaleLowerCase() === 'c' && (event.ctrlKey || event.metaKey)) {
      const text = this._selection?.textContent
      if (text) {
        copy(text)
      }
    }
  }

  async handleMouseDown(event: MouseEvent) {
    // return的几种情况：
    // 文本悬浮菜单下，且不清空选中文本
    if (this.isTargetInElement(event.target, '.text-popup')) return
    
    this.cleanSelection()
    const tool = this.tool

    // dialog弹窗显示时
    const dialogs = document.querySelectorAll('.dialog')
    if (dialogs.length) {
      for (let i = 0; i < dialogs.length; i++) {
        if (dialogs[i].contains(event.target)) return
      }
    }

    // 选中注释层（annotationContainer）下除Markup的元素时
    if (event.target.className !== 'annotationContainer' && this.isTargetInElement(event.target, '.annotationContainer') && !this.isTargetInElement(event.target, '.markup')) return

    // 选中text注释的编辑框时
    if (this.isTargetInElement(event.target, '.text-editor-container')) return

    // 不在PDF区域内（document-container）
    if (!this.isTargetInElement(event.target, '.document-container') && !this.isTargetInElement(event.target, '.compare-document-container')) return
    
    // 添加markup时
    if (!markupType.includes(tool)) return

    // 不在页面内
    const inPage = this.testPoint(event)
    if (!inPage) return

    const { x, y } = inPage
    const point = getInitialPoint({ x, y }, this.viewport, this.scale)
    this.startPoint = point
    const isText = await this.isTextAtPoint(point)
    this.selecting = !!isText

    document.addEventListener('mouseup', this.handleMouseUp)
    document.addEventListener('touchend', this.handleMouseUp)
  }

  async handleMouseMove(event: MouseEvent) {
    if (this.isTargetInElement(event.target, '.redaction-confirm-dialog') || this.isTargetInElement(event.target, '.mask')) return

    const inPage = this.testPoint(event)
    if (!inPage || document.querySelector('.annotationContainer .outline-container')) {
      this.container?.classList.remove('text')
      return
    }

    const { x, y } = inPage
    const point = getInitialPoint({ x, y }, this.viewport, this.scale)
    const isText = await this.isTextAtPoint(point)
    const startPoint = this.startPoint
    if (startPoint && (startPoint.x === point.x || startPoint.y === point.y)) return
    if (isText) {
      this.container?.classList.add('text')
      this.endPoint = point
    } else {
      this.container?.classList.remove('text')
      return
    }
    if (!this.selecting) return
    this.container?.classList.add('selecting')

    const selection = await this.messageHandler.sendWithPromise('GetCharsRangeAtPos', {
      pagePtr: this.#pagePtr,
      textPtr: this.#textPtr,
      start: this.startPoint,
      end: this.endPoint
    })
    this._selection = selection
    this.updateSelection()
  }

  async handleDobuleClick(event: MouseEvent) {
    event.stopPropagation()
    event.preventDefault()
    const inPage = this.testPoint(event)
    if (!inPage || document.querySelector('.annotationContainer .outline-container')) {
      this.container?.classList.remove('text')
      return
    }
    const { x, y } = inPage
    const point = getInitialPoint({ x, y }, this.viewport, this.scale)
    const isText = await this.isTextAtPoint(point)
    if (!isText) return

    const selection = await this.messageHandler.sendWithPromise('GetSelectionForWordAtPos', {
      pagePtr: this.#pagePtr,
      textPtr: this.#textPtr,
      start: point,
      end: point
    })
    this._selection = selection
    this.updateSelection()

    if (this._selection && this.textContainer && ['redaction', 'remove'].includes(this.tool)) {
      const redactData = {
        operate: 'add-annot',
        type: 'redact',
        pageIndex: this.pageIndex,
        date: new Date(),
        rect: this.rect,
        rects: this.rects,
        color: this.color,
        erasure: this.tool === 'remove',
        quadPoints: this.quadPoints,
        contents: this._selection.textContent || undefined
      }
      this.tool === 'redaction' && (redactData.fillColor = 'rgb(0, 0, 0)')

      this.eventBus.dispatch('annotationChange', {
        type: 'add',
        annotation: redactData
      })
      this.cleanSelection()

    } else if (this._selection && this.textContainer) {
      this.showTextPopup()
    }
  }

  handleMouseUp() {
    if (this.selecting && this._selection && markupTypeNotNull.includes(this.tool)) {
      const annotationData = {
        operate: 'add-annot',
        type: this.tool,
        pageIndex: this.pageIndex,
        date: new Date(),
        opacity: 0.5,
        quadPoints: this.quadPoints,
        rect: this.rect,
        color: this.color,
        contents: this._selection.textContent || undefined
      }

      if (this.tool === 'redaction' || this.tool === 'remove') {
        annotationData.type = 'redact'
        annotationData.rects = this.rects
        annotationData.erasure = this.tool === 'remove'
        this.tool === 'redaction' && (annotationData.fillColor = 'rgb(0, 0, 0)')
        delete annotationData.opacity
      }

      this.eventBus.dispatch('annotationChange', {
        type: 'add',
        annotation: annotationData
      })
      this.cleanSelection()
    }
    this.startPoint = null
    this.endPoint = null
    this.selecting = false
    this.container?.classList.remove('selecting')

    document.removeEventListener('mouseup', this.handleMouseUp)
    document.removeEventListener('touchend', this.handleMouseUp)

    if (this._selection && this.textContainer) {
      this.showTextPopup()
    }
  }

  updateSelection() {
    const textRects = this._selection.textRects
    this.textContainer?.textContent = ''
    const rects = []
    const quadPoints = []
    const topArray = []
    const bottomArray = []
    const leftArray = []
    const rightArray = []
    for (let i = 0; i < textRects.length; i++) {
      const textRect = textRects[i]
      const { Left: left, Top: top, Right: right, Bottom: bottom } = textRect
      const rect = {
        left: left * this.scale,
        top: top * this.scale,
        right: right * this.scale,
        bottom: bottom * this.scale
      }
      topArray.push(top)
      bottomArray.push(bottom)
      leftArray.push(left)
      rightArray.push(right)
      const leftTop = {
        PointX: left,
        PointY: top
      }
      const rightTop = {
        PointX: right,
        PointY: top
      }
      const leftBottom = {
        PointX: left,
        PointY: bottom
      }
      const rightBottom = {
        PointX: right,
        PointY: bottom
      }
      quadPoints.push(leftTop, rightTop, leftBottom, rightBottom)
      rects.push({ top, left, right, bottom })
      this.drawSelection(rect)
    }
    this.quadPoints = quadPoints
    this.rects = rects
    const top = Math.min(...topArray)
    const bottom = Math.max(...bottomArray)
    const left = Math.min(...leftArray)
    const right = Math.max(...rightArray)
    this.rect = {
      left,
      top,
      right,
      bottom
    }
  }

  drawSelection(rect: {
    left: number,
    top: number,
    right: number,
    bottom: number
  }) {
    if (!this.textContainer) {
      this.textContainer = document.createElement('div')
      this.textContainer.classList.add('text-container')
      this.container?.appendChild(this.textContainer)
    }
    const { left, top, right, bottom } = rect
    const selection = document.createElement('div')
    selection.classList.add('text-selection')
    selection.style.left = `${left}px`
    selection.style.top = `${top}px`
    selection.style.width = `${right - left}px`
    selection.style.height = `${bottom - top}px`
    this.textContainer?.appendChild(selection)
  }

  cleanSelection() {
    this.textContainer?.remove()
    this.textContainer = null
    this._selection = null

    this.eventBus.dispatch('showTextPopup', { show: false })

    const data = {
      selectedText: null,
      textRects: null
    }
  }

  destroy() {
    this.cleanSelection()

    document.removeEventListener('keydown', this.handleKeyDown)
    document.removeEventListener('mousedown', this.handleMouseDown)
    document.removeEventListener('touchstart', this.handleMouseDown)
    document.removeEventListener('mousemove', this.handleMouseMove)
    document.removeEventListener('touchmove', this.handleMouseMove)
    document.removeEventListener('mouseup', this.handleMouseUp)
    document.removeEventListener('touchend', this.handleMouseUp)
    document.removeEventListener('dblclick', this.handleDobuleClick)

    this.eventBus._off('toolChanged', this.handleTool)
    this.eventBus._off('toolModeChanged', this.handleToolMode)
    this.eventBus._off('textPopupClicked', this.handleTextPopup)
  }

  // 计算坐标并显示文本悬浮窗
  showTextPopup() {
    const pageRect = this.container.getBoundingClientRect()
    const rect = {
      left: this.rect.left * this.scale + pageRect.left,
      top: this.rect.top * this.scale + pageRect.top,
      right: this.rect.right * this.scale + pageRect.left,
      bottom: this.rect.bottom * this.scale + pageRect.top
    }
    this.eventBus.dispatch('showTextPopup', { show: true, rect, text: this._selection?.textContent })

    const textRects = []
    for (let i = 0; i < this._selection.textRects.length; i++) {
      const textRect = this._selection.textRects[i]
      const { Left: left, Top: top, Right: right, Bottom: bottom } = textRect
      textRects.push({ left, top, right, bottom })
    }
    const data = {
      selectedText: this._selection?.textContent,
      pageNumber: this.pageIndex + 1,
      textRects
    }
    this.eventBus.dispatch('textSelected', data)
  }

  handleTextPopup(data) {
    if (!this._selection) return

    const { tool, color } = data
    const text = this._selection?.textContent
    
    if (markupTypeNotNull.includes(tool)) {
      const annotationData = {
        operate: 'add-annot',
        type: tool,
        pageIndex: this.pageIndex,
        date: new Date(),
        opacity: 0.5,
        quadPoints: this.quadPoints,
        rect: this.rect,
        color: color || '#ff0000',
        contents: text
      }

      this.eventBus.dispatch('annotationChange', {
        type: 'add',
        annotation: annotationData
      })
      this.cleanSelection()
      this.endPoint = null
    }
  }

  isTargetInElement(target: HTMLElement, query: string): boolean {
    const el = document.querySelector(query)
    if (el && el.contains(target)) return true
    else return false
  }

  async isTextAtPoint(point: Point): boolean {
    return await this.messageHandler.sendWithPromise('GetCharIndexAtPos', {
      pagePtr: this.#pagePtr,
      textPtr: this.#textPtr,
      point
    })
  }
}
