/**
 * @file KeyCommand.ts
 * @description 思维导图快捷键命令管理模块
 * @usage 用于处理思维导图的键盘快捷键事件,支持组合键、多命令绑定等功能
 */

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

interface KeyCommandOptions {
  mindMap: any // 思维导图实例
  enableShortcutOnlyWhenMouseInSvg?: boolean
  beforeShortcutRun?: (key: string, activeNodes: any[]) => boolean
  customCheckEnableShortcut?: (e: KeyboardEvent) => boolean
}

type ShortcutFunction = () => void
type ShortcutMap = Record<string, ShortcutFunction[]>

/**
 * @class KeyCommand
 * @description 快捷键命令管理类
 * @pattern 观察者模式 - 监听键盘事件并分发到对应的命令处理函数
 * @example
 * const keyCommand = new KeyCommand({ mindMap })
 * keyCommand.addShortcut('Control+c', () => console.log('复制'))
 */
export default class KeyCommand {
  private opt: KeyCommandOptions
  private mindMap: any
  private shortcutMap: ShortcutMap
  private shortcutMapCache: ShortcutMap
  private isPause: boolean
  private isInSvg: boolean

  /**
   * @constructor
   * @param opt 配置选项
   */
  constructor(opt: KeyCommandOptions) {
    this.opt = opt
    this.mindMap = opt.mindMap
    this.shortcutMap = {}
    this.shortcutMapCache = {}
    this.isPause = false
    this.isInSvg = false
    this.bindEvent()
  }

  /**
   * @method extendKeyMap
   * @description 扩展按键映射表
   * @param key 按键名称
   * @param code 按键码
   */
  public extendKeyMap(key: string, code: number): void {
    keyMap[key] = code
  }

  /**
   * @method removeKeyMap
   * @description 从按键映射中删除指定按键
   * @param key 要删除的按键名称
   */
  public removeKeyMap(key: string): void {
    if (typeof keyMap[key] !== 'undefined') {
      delete keyMap[key]
    }
  }

  /**
   * @method pause
   * @description 暂停快捷键响应
   */
  public pause(): void {
    this.isPause = true
  }

  /**
   * @method recovery
   * @description 恢复快捷键响应
   */
  public recovery(): void {
    this.isPause = false
  }

  /**
   * @method save
   * @description 保存当前快捷键配置到缓存
   */
  public save(): void {
    if (Object.keys(this.shortcutMapCache).length > 0) {
      return
    }
    this.shortcutMapCache = { ...this.shortcutMap }
    this.shortcutMap = {}
  }

  /**
   * @method restore
   * @description 从缓存恢复快捷键配置
   */
  public restore(): void {
    if (Object.keys(this.shortcutMapCache).length <= 0) {
      return
    }
    this.shortcutMap = { ...this.shortcutMapCache }
    this.shortcutMapCache = {}
  }

  /**
   * @private
   * @method bindEvent
   * @description 绑定事件监听
   */
  private bindEvent(): void {
    this.onKeydown = this.onKeydown.bind(this)

    this.mindMap.on('svg_mouseenter', () => {
      this.isInSvg = true
    })

    this.mindMap.on('svg_mouseleave', () => {
      if (this.mindMap.renderer.textEdit.isShowTextEdit()) return
      if (
        this.mindMap.associativeLine &&
        this.mindMap.associativeLine.showTextEdit
      ) {
        return
      }
      this.isInSvg = false
    })

    window.addEventListener('keydown', this.onKeydown)

    this.mindMap.on('beforeDestroy', () => {
      this.unBindEvent()
    })
  }

  /**
   * @private
   * @method unBindEvent
   * @description 解绑事件监听
   */
  private unBindEvent(): void {
    window.removeEventListener('keydown', this.onKeydown)
  }

  /**
   * @private
   * @method defaultEnableCheck
   * @description 默认的快捷键启用检查
   * @param e 键盘事件对象
   */
  private defaultEnableCheck(e: KeyboardEvent): boolean {
    const target = e.target as HTMLElement
    return (
      target === document.body ||
      target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.SMM_NODE_EDIT_WRAP) ||
      target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP) ||
      target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.ASSOCIATIVE_LINE_TEXT_EDIT_WRAP)
    )
  }

  /**
   * @private
   * @method onKeydown
   * @description 按键事件处理
   * @param e 键盘事件对象
   */
  private onKeydown(e: KeyboardEvent): void {
    const {
      enableShortcutOnlyWhenMouseInSvg,
      beforeShortcutRun,
      customCheckEnableShortcut
    } = this.mindMap.opt

    const checkFn = typeof customCheckEnableShortcut === 'function'
      ? customCheckEnableShortcut
      : this.defaultEnableCheck

    if (!checkFn(e)) return

    if (this.isPause || (enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)) {
      return
    }

    Object.keys(this.shortcutMap).forEach(key => {
      if (this.checkKey(e, key)) {
        if (!this.checkKey(e, 'Control+v')) {
          e.stopPropagation()
          e.preventDefault()
        }

        if (typeof beforeShortcutRun === 'function') {
          const isStop = beforeShortcutRun(key, [
            ...this.mindMap.renderer.activeNodeList
          ])
          if (isStop) return
        }

        this.shortcutMap[key].forEach(fn => {
          fn()
        })
      }
    })
  }

  /**
   * @private
   * @method checkKey
   * @description 检查按键是否匹配
   * @param e 键盘事件对象
   * @param key 快捷键字符串
   */
  private checkKey(e: KeyboardEvent, key: string): boolean {
    const originCodes = this.getOriginEventCodeArr(e)
    let keyCodes = this.getKeyCodeArr(key)

    if (originCodes.length !== keyCodes.length) {
      return false
    }

    for (let i = 0; i < originCodes.length; i++) {
      const index = keyCodes.findIndex(item => item === originCodes[i])
      if (index === -1) {
        return false
      } else {
        keyCodes.splice(index, 1)
      }
    }
    return true
  }

  /**
   * @private
   * @method getOriginEventCodeArr
   * @description 获取事件对象中的按键码数组
   * @param e 键盘事件对象
   */
  private getOriginEventCodeArr(e: KeyboardEvent): number[] {
    const arr: number[] = []

    if (e.ctrlKey || e.metaKey) {
      arr.push(keyMap['Control'])
    }
    if (e.altKey) {
      arr.push(keyMap['Alt'])
    }
    if (e.shiftKey) {
      arr.push(keyMap['Shift'])
    }
    if (!arr.includes(e.keyCode)) {
      arr.push(e.keyCode)
    }

    return arr
  }

  /**
   * @method hasCombinationKey
   * @description 检查是否按下了组合键
   * @param e 键盘事件对象
   */
  public hasCombinationKey(e: KeyboardEvent): boolean {
    return e.ctrlKey || e.metaKey || e.altKey || e.shiftKey
  }

  /**
   * @private
   * @method getKeyCodeArr
   * @description 获取快捷键字符串对应的按键码数组
   * @param key 快捷键字符串
   */
  private getKeyCodeArr(key: string): number[] {
    const keyArr = key.split(/\s*\+\s*/)
    return keyArr.map(item => keyMap[item])
  }

  /**
   * @method addShortcut
   * @description 添加快捷键命令
   * @param key 快捷键字符串,支持 | 分隔的多个快捷键
   * @param fn 快捷键处理函数
   * @example
   * addShortcut('Enter', () => {})
   * addShortcut('Tab | Insert', () => {})
   * addShortcut('Shift + a', () => {})
   */
  public addShortcut(key: string, fn: ShortcutFunction): void {
    key.split(/\s*\|\s*/).forEach(item => {
      if (this.shortcutMap[item]) {
        this.shortcutMap[item].push(fn)
      } else {
        this.shortcutMap[item] = [fn]
      }
    })
  }

  /**
   * @method removeShortcut
   * @description 移除快捷键命令
   * @param key 快捷键字符串
   * @param fn 可选,指定要移除的处理函数,不指定则移除该快捷键的所有处理函数
   */
  public removeShortcut(key: string, fn?: ShortcutFunction): void {
    key.split(/\s*\|\s*/).forEach(item => {
      if (this.shortcutMap[item]) {
        if (fn) {
          const index = this.shortcutMap[item].findIndex(f => f === fn)
          if (index !== -1) {
            this.shortcutMap[item].splice(index, 1)
          }
        } else {
          delete this.shortcutMap[item]
        }
      }
    })
  }

  /**
   * @method getShortcutFn
   * @description 获取指定快捷键的所有处理函数
   * @param key 快捷键字符串
   */
  public getShortcutFn(key: string): ShortcutFunction[] {
    let res: ShortcutFunction[] = []
    key.split(/\s*\|\s*/).forEach(item => {
      res = this.shortcutMap[item] || []
    })
    return res
  }
}