<template>
  <div class="stage-wrapper" ref="stageWrapperRef" :style="getWrapperStyle" @wheel="handleWheel">
    <div class="stage" ref="stageRef" :style="getStyles.stageStyle">
      <div ref="tipRef" class="tip-txt" :style="getStyles.tipStyle">
        {{ screen?.name }}:{{ page?.name }}
      </div>
      <!-- 格子背景 -->
      <div class="absolute transparent-bg" :style="getStyles.transpartBg"></div>
      <!-- 画布区域 -->
      <div
        ref="canvasRef"
        class="absolute"
        :style="{
          ...getStyles.canvasStyle,
          outline: 'none',
          background: 'transparent'
        }"
      >
        <!-- 内容节点 -->
        <Nodes
          :schema="page!"
          :root-container="canvasRef!"
          :style="{
            ...getStyles.canvasStyle,
            transform: '',
            border: 'none',
            position: 'static'
          }"
        />
        <Moveable :root-container="canvasRef!" :page-id="page?.id" />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import type { StageState } from './type'
import type { Page } from '@/types/page'
import type { Screen } from '@/types/screen'

import { ref, onMounted, computed, nextTick } from 'vue'
import { useScroll, useElementSize } from '@vueuse/core'
import { useProjectStore } from '@/store/modules/project'
import { useCanvasMove } from './useCanvasMove'
import { throttle } from 'lodash-es'

import Nodes from './Node.vue'
import Moveable from './Moveable.vue'

const props = defineProps<{
  state: StageState
  page?: Page
  screen?: Screen
}>()
const emit = defineEmits<{
  changeState: [Partial<StageState>]
}>()
const stageWrapperRef = ref<HTMLDivElement>()
const canvasRef = ref<HTMLDivElement>()
const { width: clientWidth, height: clientHeight } = useElementSize(stageWrapperRef)
const projectStore = useProjectStore()

useCanvasMove(stageWrapperRef)

const getWrapperStyle = computed(() => {
  const { showRuler } = props.state
  return {
    width: showRuler ? 'calc(100% - 20px)' : '100%',
    height: showRuler ? 'calc(100% - 20px)' : '100%',
    left: showRuler ? '20px' : '0',
    top: showRuler ? '20px' : '0'
  }
})

const STAGE_SCALE = 3
const getStyles = computed(() => {
  const { width = 1280, height = 720, scale } = props.state
  // const { page } = props

  // 可滚动距离 至少2倍窗口大小
  const scrollWidth =
    width * STAGE_SCALE > clientWidth.value * 2 ? width * STAGE_SCALE : clientWidth.value * 2
  const scrollHeight =
    height * STAGE_SCALE > clientHeight.value * 2 ? height * STAGE_SCALE : clientHeight.value * 2

  // 计算居中偏移量
  const canvasOffsetX = -(width - width * scale) / 2 + (clientWidth.value - width * scale) / 2
  const canvasOffsetY = -(height - height * scale) / 2 + (clientHeight.value - height * scale) / 2
  // 距离左边 = (可滚动距离 - 视窗宽度) / 2 + 缩放实际偏移量
  const canvasLeft = (scrollWidth - clientWidth.value) / 2 + canvasOffsetX
  // 距离顶边 = (可滚动距离 - 视窗高度) / 2 + 缩放实际偏移量
  const canvasTop = (scrollHeight - clientHeight.value) / 2 + canvasOffsetY

  const tipOffsetX = (clientWidth.value - width * scale) / 2
  const tipOffsetY = (clientHeight.value - height * scale) / 2 - 20
  const tipLeft = (scrollWidth - clientWidth.value) / 2 + tipOffsetX
  const tipTop = (scrollHeight - clientHeight.value) / 2 + tipOffsetY

  emit('changeState', {
    originX: tipLeft,
    originY: tipTop + 20,
    wrapperWidth: scrollWidth,
    wrapperHeight: scrollHeight,
    viewportWidth: clientWidth.value,
    viewportHeight: clientHeight.value
  })

  const startY = tipTop + 20
  const endX = tipLeft + width * scale
  const endY = startY + height * scale

  const offset = 0
  const clipPath = `rect(${startY + offset}px ${endX + offset}px ${endY + offset}px ${tipLeft + offset}px)`
  const outline = props.page?.id === projectStore.activePageId ? '2px solid #1890ff' : ''

  return {
    // 舞台样式
    stageStyle: {
      width: `${width * STAGE_SCALE}px`,
      height: `${height * STAGE_SCALE}px`
    },
    // 画布样式
    canvasStyle: {
      width: `${width}px`,
      height: `${height}px`,
      'transform-origin': '50% 50%',
      transform: `scale(${scale})`,
      left: `${canvasLeft}px`,
      top: `${canvasTop}px`,
      outline
    },
    // 提示样式
    tipStyle: {
      left: `${tipLeft}px`,
      top: `${tipTop}px`
    },
    // 画布背景
    transpartBg: {
      'clip-path': clipPath
    }
  }
})

/**
 * 监听滚动 更新标尺
 */
useScroll(stageWrapperRef, {
  throttle: 0,
  onScroll: () => {
    const scrollTop = stageWrapperRef.value!.scrollTop
    const scrollLeft = stageWrapperRef.value!.scrollLeft
    emit('changeState', {
      scrollX: scrollLeft,
      scrollY: scrollTop
    })
  }
})

/* 设置缩放倍数 */
const initScale = async () => {
  if (!clientWidth.value || !clientHeight.value) return
  // 4为滚动条宽度 40为左右上下间隔20px
  const windowWidth = clientWidth.value - 40
  const windowHeight = clientHeight.value - 40
  const { width = 1280, height = 720 } = props.state
  let scale
  let maxScale
  if (windowHeight > windowWidth) {
    scale = (windowWidth / width).toFixed(2) as unknown as number
    maxScale = (windowHeight / height).toFixed(2) as unknown as number
  } else {
    scale = (windowHeight / height).toFixed(2) as unknown as number
    maxScale = (windowWidth / width).toFixed(2) as unknown as number
  }

  const result = scale > maxScale ? maxScale : scale

  emit('changeState', {
    scale: result > 0.1 ? Number(result) : 0.1
  })
}

/* 设置舞台位置-默认居中 */
const initStagePosition = async () => {
  await nextTick()
  const scrollWidth = stageWrapperRef.value!.scrollWidth
  const scrollHeight = stageWrapperRef.value!.scrollHeight

  const centerX = (scrollWidth - clientWidth.value) / 2
  const centerY = (scrollHeight - clientHeight.value) / 2

  emit('changeState', {
    centerX,
    centerY,
    scrollX: centerX,
    scrollY: centerY
  })

  stageWrapperRef.value!.scrollTo(centerX, centerY)
}

/**
 * 滚动缩放处理
 * Ctrl + 鼠标滚轮缩放
 */
const handleWheel = throttle((e: WheelEvent) => {
  if (e.ctrlKey) {
    const { state } = props
    //滚轮方向
    const direction = e.deltaY > 0 ? 'down' : 'up'
    const scale = direction === 'up' ? state.scale + 0.05 : state.scale - 0.05
    emit('changeState', {
      scale: scale < 0.1 ? 0.1 : scale > 4 ? 4 : scale
    })
  }
}, 16)

defineExpose({
  getPosition: () => canvasRef.value?.getBoundingClientRect(),
  initScale,
  initStagePosition
})

onMounted(() => {
  setTimeout(() => {
    initScale()
    initStagePosition()
  }, 10)
})
</script>

<style lang="less" scoped>
.stage-wrapper {
  position: absolute;
  overflow: auto;
}

.stage {
  position: relative;
}

.tip-txt {
  position: absolute;
  font-size: 12px;
  line-height: 20px;
  color: #999;
}

.transparent-bg {
  transform: scale(1) !important;
  width: 100%;
  height: 100%;
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #c8c8c8 25%, transparent 25%),
    linear-gradient(-45deg, #c8c8c8 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, #c8c8c8 75%),
    linear-gradient(-45deg, transparent 75%, #c8c8c8 75%);
  background-size: 10px 10px;
  background-position:
    0 0,
    0 -5px,
    -5px 5px,
    5px 0px;
}
</style>
