<template>
  <div
    class="canvas"
    ref="canvasRef"
    @wheel="$event => handleMousewheelCanvas($event)"
    @mousedown="$event => handleClickBlankArea($event)"
    @dblclick="$event => handleDblClick($event)"
    v-contextmenu="contextmenus"
    v-click-outside="removeEditorAreaFocus"
  >
    <ElementCreateSelection v-if="creatingElement" @created="data => insertElementFromCreateSelection(data)" />
    <ShapeCreateCanvas v-if="creatingCustomShape" @created="data => insertCustomShape(data)" />
    <div
      class="viewport-wrapper"
      :style="{
        width: viewportStyles.width * canvasScale + 'px',
        height: viewportStyles.height * canvasScale + 'px',
        left: viewportStyles.left + 'px',
        top: viewportStyles.top + 'px'
      }"
    >
      <div class="operates">
        <AlignmentLine
          v-for="(line, index) in alignmentLines"
          :key="index"
          :type="line.type"
          :axis="line.axis"
          :length="line.length"
          :canvasScale="canvasScale"
        />
        <MultiSelectOperate v-if="activeElementIdList.length > 1" :elementList="elementList" :scaleMultiElement="scaleMultiElement" />
        <Operate
          v-for="element in elementList"
          :key="element.id"
          :elementInfo="element"
          :isSelected="activeElementIdList.includes(element.id)"
          :isActive="handleElementId === element.id"
          :isActiveGroupElement="activeGroupElementId === element.id"
          :isMultiSelect="activeElementIdList.length > 1"
          :rotateElement="rotateElement"
          :scaleElement="scaleElement"
          :openLinkDialog="openLinkDialog"
          :dragLineElement="dragLineElement"
          :moveShapeKeypoint="moveShapeKeypoint"
          v-show="!hiddenElementIdList.includes(element.id)"
        />
        <ViewportBackground />
      </div>

      <div class="viewport" ref="viewportRef" :style="{ transform: `scale(${canvasScale})` }">
        <MouseSelection
          v-if="mouseSelectionVisible"
          :top="mouseSelection.top"
          :left="mouseSelection.left"
          :width="mouseSelection.width"
          :height="mouseSelection.height"
          :quadrant="mouseSelectionQuadrant"
        />
        <EditableElement
          v-for="(element, index) in elementList"
          :key="element.id"
          :elementInfo="element"
          :elementIndex="index + 1"
          :isMultiSelect="activeElementIdList.length > 1"
          :selectElement="selectElement"
          :openLinkDialog="openLinkDialog"
          v-show="!hiddenElementIdList.includes(element.id)"
        />
      </div>
    </div>

    <div class="drag-mask" v-if="spaceKeyState"></div>

    <Ruler :viewportStyles="viewportStyles" :elementList="elementList" v-if="showRuler" />

    <Modal v-model:visible="linkDialogVisible" :width="540">
      <LinkDialog @close="linkDialogVisible = false" />
    </Modal>
  </div>
</template>

<script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, provide, ref, watch, watchEffect } from "vue"
import { throttle } from "lodash"
import { storeToRefs } from "pinia"
import { useMainStore, useSlidesStore, useKeyboardStore } from "@/store"
import type { ContextmenuItem } from "@/components/Contextmenu/types"
import type { PPTElement, PPTShapeElement } from "@/types/slides"
import type { AlignmentLineProps, CreateCustomShapeData } from "@/types/edit"
import { injectKeySlideScale } from "@/types/injectKey"
import { removeAllRanges } from "@/utils/selection"
import { KEYS } from "@/configs/hotkey"

import useViewportSize from "./hooks/useViewportSize"
import useMouseSelection from "./hooks/useMouseSelection"
import useDropImageOrText from "./hooks/useDropImageOrText"
import useRotateElement from "./hooks/useRotateElement"
import useScaleElement from "./hooks/useScaleElement"
import useSelectAndMoveElement from "./hooks/useSelectElement"
import useDragElement from "./hooks/useDragElement"
import useDragLineElement from "./hooks/useDragLineElement"
import useMoveShapeKeypoint from "./hooks/useMoveShapeKeypoint"
import useInsertFromCreateSelection from "./hooks/useInsertFromCreateSelection"

import useDeleteElement from "@/hooks/useDeleteElement"
import useCopyAndPasteElement from "@/hooks/useCopyAndPasteElement"
import useSelectElement from "@/hooks/useSelectElement"
import useScaleCanvas from "@/hooks/useScaleCanvas"
import useScreening from "@/hooks/useScreening"
import useSlideHandler from "@/hooks/useSlideHandler"
import useCreateElement from "@/hooks/useCreateElement"

import EditableElement from "./EditableElement.vue"
import MouseSelection from "./MouseSelection.vue"
import ViewportBackground from "./ViewportBackground.vue"
import AlignmentLine from "./AlignmentLine.vue"
import Ruler from "./Ruler.vue"
import ElementCreateSelection from "./ElementCreateSelection.vue"
import ShapeCreateCanvas from "./ShapeCreateCanvas.vue"
import MultiSelectOperate from "./Operate/MultiSelectOperate.vue"
import Operate from "./Operate/index.vue"
import LinkDialog from "./LinkDialog.vue"
import Modal from "@/components/Modal.vue"

const mainStore = useMainStore()
const {
  activeElementIdList,
  activeGroupElementId,
  handleElementId,
  hiddenElementIdList,
  editorAreaFocus,
  gridLineSize,
  showRuler,
  creatingElement,
  creatingCustomShape,
  canvasScale,
  textFormatPainter,
  isPPTWheelPage
} = storeToRefs(mainStore)
const { currentSlide } = storeToRefs(useSlidesStore())
const { ctrlKeyState, spaceKeyState } = storeToRefs(useKeyboardStore())

const viewportRef = ref<HTMLElement>()
const alignmentLines = ref<AlignmentLineProps[]>([])

const linkDialogVisible = ref(false)
const openLinkDialog = () => (linkDialogVisible.value = true)

watch(handleElementId, () => {
  mainStore.setActiveGroupElementId("")
})

const elementList = ref<PPTElement[]>([])
const setLocalElementList = () => {
  elementList.value = currentSlide.value ? JSON.parse(JSON.stringify(currentSlide.value.elements)) : []
}
watchEffect(setLocalElementList)

const canvasRef = ref<HTMLElement>()
const { dragViewport, viewportStyles } = useViewportSize(canvasRef)

/* 这个功能和 enjoyPlayer 冲突，所以禁用掉 */
//useDropImageOrText(canvasRef)

const { mouseSelection, mouseSelectionVisible, mouseSelectionQuadrant, updateMouseSelection } = useMouseSelection(elementList, viewportRef)

const { dragElement } = useDragElement(elementList, alignmentLines, canvasScale)
const { dragLineElement } = useDragLineElement(elementList)
const { selectElement } = useSelectAndMoveElement(elementList, dragElement)
const { scaleElement, scaleMultiElement } = useScaleElement(elementList, alignmentLines, canvasScale)
const { rotateElement } = useRotateElement(elementList, viewportRef, canvasScale)
const { moveShapeKeypoint } = useMoveShapeKeypoint(elementList, canvasScale)

const { selectAllElements } = useSelectElement()
const { deleteAllElements } = useDeleteElement()
const { pasteElement } = useCopyAndPasteElement()
const { enterScreeningFromStart } = useScreening()
const { updateSlideIndex } = useSlideHandler()
const { createTextElement, createShapeElement } = useCreateElement()

// 组件渲染时，如果存在元素焦点，需要清除
// 这种情况存在于：有焦点元素的情况下进入了放映模式，再退出时，需要清除原先的焦点（因为可能已经切换了页面）
onMounted(() => {
  if (activeElementIdList.value.length) {
    nextTick(() => mainStore.setActiveElementIdList([]))
  }
})

// 点击画布的空白区域：清空焦点元素、设置画布焦点、清除文字选区、清空格式刷状态
const handleClickBlankArea = (e: MouseEvent) => {
  if (activeElementIdList.value.length) mainStore.setActiveElementIdList([])

  if (!spaceKeyState.value) updateMouseSelection(e)
  else dragViewport(e)

  if (!editorAreaFocus.value) mainStore.setEditorareaFocus(true)
  if (textFormatPainter.value) mainStore.setTextFormatPainter(null)
  removeAllRanges()
}

// 双击空白处插入文本
const handleDblClick = (e: MouseEvent) => {
  if (activeElementIdList.value.length || creatingElement.value || creatingCustomShape.value) return
  if (!viewportRef.value) return

  const viewportRect = viewportRef.value.getBoundingClientRect()
  const left = (e.pageX - viewportRect.x) / canvasScale.value
  const top = (e.pageY - viewportRect.y) / canvasScale.value

  createTextElement({
    left,
    top,
    width: 200 / canvasScale.value, // 除以 canvasScale 是为了与点击选区创建的形式保持相同的宽度
    height: 0
  })
}

// 画布注销时清空格式刷状态
onUnmounted(() => {
  if (textFormatPainter.value) mainStore.setTextFormatPainter(null)
})

// 移除画布编辑区域焦点
const removeEditorAreaFocus = () => {
  if (editorAreaFocus.value) mainStore.setEditorareaFocus(false)
}

// 滚动鼠标
const { scaleCanvas } = useScaleCanvas()
const throttleScaleCanvas = throttle(scaleCanvas, 100, { leading: true, trailing: false })
const throttleUpdateSlideIndex = throttle(updateSlideIndex, 300, { leading: true, trailing: false })

const handleMousewheelCanvas = (e: WheelEvent) => {
  // 按住Ctrl键时：缩放画布
  if (ctrlKeyState.value) {
    e.preventDefault()
    if (e.deltaY > 0) throttleScaleCanvas("-")
    else if (e.deltaY < 0) throttleScaleCanvas("+")
  }
  // 上下翻页
  else {
    // 控制能不能翻页
    if (!isPPTWheelPage.value) return
    e.preventDefault()
    if (e.deltaY > 0) throttleUpdateSlideIndex(KEYS.DOWN)
    else if (e.deltaY < 0) throttleUpdateSlideIndex(KEYS.UP)
  }
}

// 开关标尺
const toggleRuler = () => {
  mainStore.setRulerState(!showRuler.value)
}

// 在鼠标绘制的范围插入元素
const { insertElementFromCreateSelection, formatCreateSelection } = useInsertFromCreateSelection(viewportRef)

// 插入自定义任意多边形
const insertCustomShape = (data: CreateCustomShapeData) => {
  const { start, end, path, viewBox } = data
  const position = formatCreateSelection({ start, end })
  if (position) {
    const supplement: Partial<PPTShapeElement> = {}
    if (data.fill) supplement.fill = data.fill
    if (data.outline) supplement.outline = data.outline
    createShapeElement(position, { path, viewBox }, supplement)
  }

  mainStore.setCreatingCustomShapeState(false)
}

const contextmenus = (): ContextmenuItem[] => {
  return [
    {
      text: "粘贴",
      subText: "Ctrl + V",
      handler: pasteElement
    },
    {
      text: "全选",
      subText: "Ctrl + A",
      handler: selectAllElements
    },
    {
      text: "标尺",
      subText: showRuler.value ? "√" : "",
      handler: toggleRuler
    },
    {
      text: "网格线",
      handler: () => mainStore.setGridLineSize(gridLineSize.value ? 0 : 50),
      children: [
        {
          text: "无",
          subText: gridLineSize.value === 0 ? "√" : "",
          handler: () => mainStore.setGridLineSize(0)
        },
        {
          text: "小",
          subText: gridLineSize.value === 25 ? "√" : "",
          handler: () => mainStore.setGridLineSize(25)
        },
        {
          text: "中",
          subText: gridLineSize.value === 50 ? "√" : "",
          handler: () => mainStore.setGridLineSize(50)
        },
        {
          text: "大",
          subText: gridLineSize.value === 100 ? "√" : "",
          handler: () => mainStore.setGridLineSize(100)
        }
      ]
    },
    {
      text: "重置当前页",
      handler: deleteAllElements
    },
    { divider: true },
    {
      text: "幻灯片放映",
      subText: "F5",
      handler: enterScreeningFromStart
    }
  ]
}

provide(injectKeySlideScale, canvasScale)
</script>

<style lang="scss" scoped>
.canvas {
  height: 100%;
  user-select: none;
  overflow: hidden;
  background-color: $lightGray;
  position: relative;
}
.drag-mask {
  cursor: grab;
  @include absolute-0();
}
.viewport-wrapper {
  position: absolute;
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.01),
    0 0 12px 0 rgba(0, 0, 0, 0.1);
}
.viewport {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
}
</style>
