<template>
  <div 
    class="editable-table"
    :style="{ width: totalWidth + 'px' }"
  >
    <div class="handler" v-if="editable">
      <div 
        class="drag-line" 
        v-for="(pos, index) in dragLinePosition" 
        :key="index"
        :style="{ left: pos + 'px' }"
        @mousedown="$event => handleMousedownColHandler($event, index)"
      ></div>
    </div>
    <table 
      :class="{
        'theme': theme,
        'row-header': theme?.rowHeader,
        'row-footer': theme?.rowFooter,
        'col-header': theme?.colHeader,
        'col-footer': theme?.colFooter,
      }"
      :style="`--themeColor: ${theme?.color}; --subThemeColor1: ${subThemeColor[0]}; --subThemeColor2: ${subThemeColor[1]}`"
    >
      <colgroup>
        <col span="1" v-for="(width, index) in colSizeList" :key="index" :width="width">
      </colgroup>
      <tbody>
        <tr v-for="(rowCells, rowIndex) in tableCells" :key="rowIndex" :style="{ height: cellMinHeight + 'px' }">
          <td 
            class="cell"
            :class="{
              'selected': selectedCells.includes(`${rowIndex}_${colIndex}`) && selectedCells.length > 1,
              'active': activedCell === `${rowIndex}_${colIndex}`,
            }"
            :style="{
              borderStyle: outline.style,
              borderColor: outline.color,
              borderWidth: outline.width + 'px',
              ...getTextStyle(cell.style),
            }"
            v-for="(cell, colIndex) in rowCells"
            :key="cell.id"
            :rowspan="cell.rowspan"
            :colspan="cell.colspan"
            :data-cell-index="`${rowIndex}_${colIndex}`"
            v-show="!hideCells.includes(`${rowIndex}_${colIndex}`)"
            @mousedown="$event => handleCellMousedown($event, rowIndex, colIndex)"
            @mouseenter="handleCellMouseenter(rowIndex, colIndex)"
            v-contextmenu="(el: HTMLElement) => contextmenus(el)"
          >
            <CustomTextarea 
              v-if="activedCell === `${rowIndex}_${colIndex}`"
              class="cell-text" 
              :class="{ 'active': activedCell === `${rowIndex}_${colIndex}` }"
              :style="{ minHeight: (cellMinHeight - 4) + 'px' }"
              :value="cell.text"
              @updateValue="value => handleInput(value, rowIndex, colIndex)"
              @insertExcelData="value => insertExcelData(value, rowIndex, colIndex)"
            />
            <div v-else class="cell-text" :style="{ minHeight: (cellMinHeight - 4) + 'px' }" v-html="formatText(cell.text)" />
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script lang="ts" setup>
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { debounce, isEqual } from 'lodash'
import { storeToRefs } from 'pinia'
import { nanoid } from 'nanoid'
import { useMainStore } from '@/store'
import type { PPTElementOutline, TableCell, TableTheme } from '@/types/slides'
import type { ContextmenuItem } from '@/components/Contextmenu/types'
import { KEYS } from '@/configs/hotkey'
import { getTextStyle, formatText } from './utils'
import useHideCells from './useHideCells'
import useSubThemeColor from './useSubThemeColor'

import CustomTextarea from './CustomTextarea.vue'

const props = withDefaults(defineProps<{
  data: TableCell[][]
  width: number
  cellMinHeight: number
  colWidths: number[]
  outline: PPTElementOutline
  theme?: TableTheme
  editable?: boolean
}>(), {
  editable: true,
})

const emit = defineEmits<{
  (event: 'change', payload: TableCell[][]): void
  (event: 'changeColWidths', payload: number[]): void
  (event: 'changeSelectedCells', payload: string[]): void
}>()

const { canvasScale } = storeToRefs(useMainStore())
    
const isStartSelect = ref(false)
const startCell = ref<number[]>([])
const endCell = ref<number[]>([])

const tableCells = computed<TableCell[][]>({
  get() {
    return props.data
  },
  set(newData) {
    emit('change', newData)
  },
})

// 主题辅助色
const theme = computed(() => props.theme)
const { subThemeColor } = useSubThemeColor(theme)

// 计算表格每一列的列宽和总宽度
const colSizeList = ref<number[]>([])
const totalWidth = computed(() => colSizeList.value.reduce((a, b) => a + b))
watch([
  () => props.colWidths,
  () => props.width,
], () => {
  colSizeList.value = props.colWidths.map(item => item * props.width)
}, { immediate: true })

// 清除全部单元格的选中状态
// 表格处于不可编辑状态时也需要清除
const removeSelectedCells = () => {
  startCell.value = []
  endCell.value = []
}

watch(() => props.editable, () => {
  if (!props.editable) removeSelectedCells()
})

// 用于拖拽列宽的操作节点位置
const dragLinePosition = computed(() => {
  const dragLinePosition: number[] = []
  for (let i = 1; i < colSizeList.value.length + 1; i++) {
    const pos = colSizeList.value.slice(0, i).reduce((a, b) => (a + b))
    dragLinePosition.push(pos)
  }
  return dragLinePosition
})

// 无效的单元格位置（被合并的单元格位置）集合
const cells = computed(() => props.data)
const { hideCells } = useHideCells(cells)

// 当前选中的单元格集合
const selectedCells = computed(() => {
  if (!startCell.value.length) return []
  const [startX, startY] = startCell.value

  if (!endCell.value.length) return [`${startX}_${startY}`]
  const [endX, endY] = endCell.value

  if (startX === endX && startY === endY) return [`${startX}_${startY}`]

  const selectedCells = []

  const minX = Math.min(startX, endX)
  const minY = Math.min(startY, endY)
  const maxX = Math.max(startX, endX)
  const maxY = Math.max(startY, endY)

  for (let i = 0; i < tableCells.value.length; i++) {
    const rowCells = tableCells.value[i]
    for (let j = 0; j < rowCells.length; j++) {
      if (i >= minX && i <= maxX && j >= minY && j <= maxY) selectedCells.push(`${i}_${j}`)
    }
  }
  return selectedCells
})

watch(selectedCells, (value, oldValue) => {
  if (isEqual(value, oldValue)) return
  emit('changeSelectedCells', selectedCells.value)
})

// 当前激活的单元格：当且仅当只有一个选中单元格时，该单元格为激活的单元格
const activedCell = computed(() => {
  if (selectedCells.value.length > 1) return null
  return selectedCells.value[0]
})

// 设置选中单元格状态（鼠标点击或拖选）
const handleMouseup = () => isStartSelect.value = false

const handleCellMousedown = (e: MouseEvent, rowIndex: number, colIndex: number) => {
  if (e.button === 0) {
    endCell.value = []
    isStartSelect.value = true
    startCell.value = [rowIndex, colIndex]
  }
}

const handleCellMouseenter = (rowIndex: number, colIndex: number) => {
  if (!isStartSelect.value) return
  endCell.value = [rowIndex, colIndex]
}

onMounted(() => {
  document.addEventListener('mouseup', handleMouseup)
})
onUnmounted(() => {
  document.removeEventListener('mouseup', handleMouseup)
})

// 判断某位置是否为无效单元格（被合并掉的位置）
const isHideCell = (rowIndex: number, colIndex: number) => hideCells.value.includes(`${rowIndex}_${colIndex}`)

// 选中指定的列
const selectCol = (index: number) => {
  const maxRow = tableCells.value.length - 1
  startCell.value = [0, index]
  endCell.value = [maxRow, index]
}

// 选中指定的行
const selectRow = (index: number) => {
  const maxCol = tableCells.value[index].length - 1
  startCell.value = [index, 0]
  endCell.value = [index, maxCol]
}

// 选中全部单元格
const selectAll = () => {
  const maxRow = tableCells.value.length - 1
  const maxCol = tableCells.value[maxRow].length - 1
  startCell.value = [0, 0]
  endCell.value = [maxRow, maxCol]
}

// 删除一行
const deleteRow = (rowIndex: number) => {
  const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))

  const targetCells = tableCells.value[rowIndex]
  const hideCellsPos = []
  for (let i = 0; i < targetCells.length; i++) {
    if (isHideCell(rowIndex, i)) hideCellsPos.push(i)
  }
  
  for (const pos of hideCellsPos) {
    for (let i = rowIndex; i >= 0; i--) {
      if (!isHideCell(i, pos)) {
        _tableCells[i][pos].rowspan = _tableCells[i][pos].rowspan - 1
        break
      }
    }
  }

  _tableCells.splice(rowIndex, 1)
  tableCells.value = _tableCells
}

// 删除一列
const deleteCol = (colIndex: number) => {
  const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))

  const hideCellsPos = []
  for (let i = 0; i < tableCells.value.length; i++) {
    if (isHideCell(i, colIndex)) hideCellsPos.push(i)
  }

  for (const pos of hideCellsPos) {
    for (let i = colIndex; i >= 0; i--) {
      if (!isHideCell(pos, i)) {
        _tableCells[pos][i].colspan = _tableCells[pos][i].colspan - 1
        break
      }
    }
  }

  tableCells.value = _tableCells.map(item => {
    item.splice(colIndex, 1)
    return item
  })
  colSizeList.value.splice(colIndex, 1)
  emit('changeColWidths', colSizeList.value)
}

// 插入一行
const insertRow = (rowIndex: number) => {
  const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))

  const rowCells: TableCell[] = []
  for (let i = 0; i < _tableCells[0].length; i++) {
    rowCells.push({
      colspan: 1,
      rowspan: 1,
      text: '',
      id: nanoid(10),
    })
  }

  _tableCells.splice(rowIndex, 0, rowCells)
  tableCells.value = _tableCells
}

// 插入一列
const insertCol = (colIndex: number) => {
  tableCells.value = tableCells.value.map(item => {
    const cell = {
      colspan: 1,
      rowspan: 1,
      text: '',
      id: nanoid(10),
    }
    item.splice(colIndex, 0, cell)
    return item
  })
  colSizeList.value.splice(colIndex, 0, 100)
  emit('changeColWidths', colSizeList.value)
}

// 填充指定的行/列数
const fillTable = (rowCount: number, colCount: number) => {
  let _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))
  const defaultCell = { colspan: 1, rowspan: 1, text: '' }
  
  if (rowCount) {
    const newRows = []
    for (let i = 0; i < rowCount; i++) {
      const rowCells: TableCell[] = []
      for (let j = 0; j < _tableCells[0].length; j++) {
        rowCells.push({
          ...defaultCell,
          id: nanoid(10),
        })
      }
      newRows.push(rowCells)
    }
    _tableCells = [..._tableCells, ...newRows]
  }
  if (colCount) {
    _tableCells = _tableCells.map(item => {
      const cells: TableCell[] = []
      for (let i = 0; i < colCount; i++) {
        const cell = {
          ...defaultCell,
          id: nanoid(10),
        }
        cells.push(cell)
      }
      return [...item, ...cells]
    })
    colSizeList.value = [...colSizeList.value, ...new Array(colCount).fill(100)]
    emit('changeColWidths', colSizeList.value)
  }

  tableCells.value = _tableCells
}

// 合并单元格
const mergeCells = () => {
  const [startX, startY] = startCell.value
  const [endX, endY] = endCell.value

  const minX = Math.min(startX, endX)
  const minY = Math.min(startY, endY)
  const maxX = Math.max(startX, endX)
  const maxY = Math.max(startY, endY)

  const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))
  
  _tableCells[minX][minY].rowspan = maxX - minX + 1
  _tableCells[minX][minY].colspan = maxY - minY + 1

  tableCells.value = _tableCells
  removeSelectedCells()
}

// 拆分单元格
const splitCells = (rowIndex: number, colIndex: number) => {
  const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))
  _tableCells[rowIndex][colIndex].rowspan = 1
  _tableCells[rowIndex][colIndex].colspan = 1

  tableCells.value = _tableCells
  removeSelectedCells()
}

// 鼠标拖拽调整列宽
const handleMousedownColHandler = (e: MouseEvent, colIndex: number) => {
  removeSelectedCells()
  let isMouseDown = true

  const originWidth = colSizeList.value[colIndex]
  const startPageX = e.pageX

  const minWidth = 50

  document.onmousemove = e => {
    if (!isMouseDown) return
    
    const moveX = (e.pageX - startPageX) / canvasScale.value
    const width = originWidth + moveX < minWidth ? minWidth : Math.round(originWidth + moveX)

    colSizeList.value[colIndex] = width
  }
  document.onmouseup = () => {
    isMouseDown = false
    document.onmousemove = null
    document.onmouseup = null

    emit('changeColWidths', colSizeList.value)
  }
}

// 清空选中单元格内的文字
const clearSelectedCellText = () => {
  const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))

  for (let i = 0; i < _tableCells.length; i++) {
    for (let j = 0; j < _tableCells[i].length; j++) {
      if (selectedCells.value.includes(`${i}_${j}`)) {
        _tableCells[i][j].text = ''
      }
    }
  }
  tableCells.value = _tableCells
}

// 将焦点移动到下一个单元格
// 当前行右边有单元格时，焦点右移
// 当前行右边无单元格（已处在行末），且存在下一行时，焦点移动至下一行行首
// 当前行右边无单元格（已处在行末），且不存在下一行（已处在最后一行）时，新建一行并将焦点移动至下一行行首
const tabActiveCell = () => {
  const getNextCell = (i: number, j: number): [number, number] | null => {
    if (!tableCells.value[i]) return null
    if (!tableCells.value[i][j]) return getNextCell(i + 1, 0)
    if (isHideCell(i, j)) return getNextCell(i, j + 1)
    return [i, j]
  }

  endCell.value = []

  const nextRow = startCell.value[0]
  const nextCol = startCell.value[1] + 1

  const nextCell = getNextCell(nextRow, nextCol)
  if (!nextCell) {
    insertRow(nextRow + 1)
    startCell.value = [nextRow + 1, 0]
  }
  else startCell.value = nextCell

  // 移动焦点后自动聚焦文本
  nextTick(() => {
    const textRef = document.querySelector('.cell-text.active') as HTMLInputElement
    if (textRef) textRef.focus()
  })
}

// 表格快捷键监听
const keydownListener = (e: KeyboardEvent) => {
  if (!props.editable || !selectedCells.value.length) return

  const key = e.key.toUpperCase()
  if (selectedCells.value.length < 2) {
    if (key === KEYS.TAB) {
      e.preventDefault()
      tabActiveCell()
    }
    if (e.ctrlKey && key === KEYS.UP) {
      e.preventDefault()
      const rowIndex = +selectedCells.value[0].split('_')[0]
      insertRow(rowIndex)
    }
    if (e.ctrlKey && key === KEYS.DOWN) {
      e.preventDefault()
      const rowIndex = +selectedCells.value[0].split('_')[0]
      insertRow(rowIndex + 1)
    }
    if (e.ctrlKey && key === KEYS.LEFT) {
      e.preventDefault()
      const colIndex = +selectedCells.value[0].split('_')[1]
      insertCol(colIndex)
    }
    if (e.ctrlKey && key === KEYS.RIGHT) {
      e.preventDefault()
      const colIndex = +selectedCells.value[0].split('_')[1]
      insertCol(colIndex + 1)
    }
  }
  else if (key === KEYS.DELETE) {
    clearSelectedCellText()
  }
}

onMounted(() => {
  document.addEventListener('keydown', keydownListener)
})
onUnmounted(() => {
  document.removeEventListener('keydown', keydownListener)
})

// 单元格文字输入时更新表格数据
const handleInput = debounce(function(value, rowIndex, colIndex) {
  tableCells.value[rowIndex][colIndex].text = value
  emit('change', tableCells.value)
}, 300, { trailing: true })

// 插入来自Excel的数据，表格的行/列数不够时自动补足
const insertExcelData = (data: string[][], rowIndex: number, colIndex: number) => {
  const maxRow = data.length
  const maxCol = data[0].length

  let fillRowCount = 0
  let fillColCount = 0
  if (rowIndex + maxRow > tableCells.value.length) fillRowCount = rowIndex + maxRow - tableCells.value.length
  if (colIndex + maxCol > tableCells.value[0].length) fillColCount = colIndex + maxCol - tableCells.value[0].length
  if (fillRowCount || fillColCount) fillTable(fillRowCount, fillColCount)

  nextTick(() => {
    for (let i = 0; i < maxRow; i++) {
      for (let j = 0; j < maxCol; j++) {
        if (tableCells.value[rowIndex + i][colIndex + j]) {
          tableCells.value[rowIndex + i][colIndex + j].text = data[i][j]
        }
      }
    }
    emit('change', tableCells.value)
  })
}

// 获取有效的单元格（排除掉被合并的单元格）
const getEffectiveTableCells = () => {
  const effectiveTableCells = []

  for (let i = 0; i < tableCells.value.length; i++) {
    const rowCells = tableCells.value[i]
    const _rowCells = []
    for (let j = 0; j < rowCells.length; j++) {
      if (!isHideCell(i, j)) _rowCells.push(rowCells[j])
    }
    if (_rowCells.length) effectiveTableCells.push(_rowCells)
  }

  return effectiveTableCells
}

// 检查是否可以删除行和列：有效的行/列数大于1
const checkCanDeleteRowOrCol = () => {
  const effectiveTableCells = getEffectiveTableCells()
  const canDeleteRow = effectiveTableCells.length > 1
  const canDeleteCol = effectiveTableCells[0].length > 1

  return { canDeleteRow, canDeleteCol }
}

// 检查是否可以合并或拆分
// 必须多选才可以合并
// 必须单选且所选单元格为合并单元格才可以拆分
const checkCanMergeOrSplit = (rowIndex: number, colIndex: number) => {
  const isMultiSelected = selectedCells.value.length > 1
  const targetCell = tableCells.value[rowIndex][colIndex]

  const canMerge = isMultiSelected
  const canSplit = !isMultiSelected && (targetCell.rowspan > 1 || targetCell.colspan > 1)

  return { canMerge, canSplit }
}

const contextmenus = (el: HTMLElement): ContextmenuItem[] => {
  const cellIndex = el.dataset.cellIndex as string
  const rowIndex = +cellIndex.split('_')[0]
  const colIndex = +cellIndex.split('_')[1]

  if (!selectedCells.value.includes(`${rowIndex}_${colIndex}`)) {
    startCell.value = [rowIndex, colIndex]
    endCell.value = []
  }

  const { canMerge, canSplit } = checkCanMergeOrSplit(rowIndex, colIndex)
  const { canDeleteRow, canDeleteCol } = checkCanDeleteRowOrCol()

  return [
    {
      text: '插入列',
      children: [
        { text: '到左侧', handler: () => insertCol(colIndex) },
        { text: '到右侧', handler: () => insertCol(colIndex + 1) },
      ],
    },
    {
      text: '插入行',
      children: [
        { text: '到上方', handler: () => insertRow(rowIndex) },
        { text: '到下方', handler: () => insertRow(rowIndex + 1) },
      ],
    },
    {
      text: '删除列',
      disable: !canDeleteCol,
      handler: () => deleteCol(colIndex),
    },
    {
      text: '删除行',
      disable: !canDeleteRow,
      handler: () => deleteRow(rowIndex),
    },
    { divider: true },
    {
      text: '合并单元格',
      disable: !canMerge,
      handler: mergeCells,
    },
    {
      text: '取消合并单元格',
      disable: !canSplit,
      handler: () => splitCells(rowIndex, colIndex),
    },
    { divider: true },
    {
      text: '选中当前列',
      handler: () => selectCol(colIndex),
    },
    {
      text: '选中当前行',
      handler: () => selectRow(rowIndex),
    },
    {
      text: '选中全部单元格',
      handler: selectAll,
    },
  ]
}
</script>

<style lang="scss" scoped>
.editable-table {
  position: relative;
  user-select: none;
}
table {
  width: 100%;
  position: relative;
  table-layout: fixed;
  border-collapse: collapse;
  border-spacing: 0;
  border: 0;
  word-wrap: break-word;
  user-select: none;

  --themeColor: $themeColor;
  --subThemeColor1: $themeColor;
  --subThemeColor2: $themeColor;

  &.theme {
    background-color: #fff;

    tr:nth-child(2n) .cell {
      background-color: var(--subThemeColor1);
    }
    tr:nth-child(2n + 1) .cell {
      background-color: var(--subThemeColor2);
    }

    &.row-header {
      tr:first-child .cell {
        background-color: var(--themeColor);
      }
    }
    &.row-footer {
      tr:last-child .cell {
        background-color: var(--themeColor);
      }
    }
    &.col-header {
      tr .cell:first-child {
        background-color: var(--themeColor);
      }
    }
    &.col-footer {
      tr .cell:last-child {
        background-color: var(--themeColor);
      }
    }
  }

  .cell {
    position: relative;
    white-space: normal;
    word-wrap: break-word;
    vertical-align: middle;
    font-size: 14px;
    background-clip: padding-box;
    cursor: default;

    &.selected::after {
      content: '';
      width: 100%;
      height: 100%;
      position: absolute;
      top: 0;
      left: 0;
      background-color: rgba($color: #666, $alpha: .4);
    }
  }

  .cell-text {
    padding: 5px;
    line-height: 1.5;
    user-select: none;
    cursor: text;

    &.active {
      user-select: text;
    }
  }
}

.drag-line {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 3px;
  background-color: $themeColor;
  margin-left: -1px;
  opacity: 0;
  z-index: 2;
  cursor: col-resize;
}
</style>