<!--
* @FileDescription: 分割面板 (未系统测试，稳定性待验证)
* @Author: 黄琪勇
* @Date:2023-08-03 11:37:38
-->
<template>
   <div ref="splitDom" class="split" :class="[splitData.isMoving && 'no-select', isHorizontal ? 'horizontal' : 'vertical']">
      <div v-if="splitData.isMoving" class="cover"></div>
      <template v-if="isHorizontal">
         <div :style="{ right: `${anotherOffset}%` }" class="left-pane pane" :class="{ 'pane-moving': splitData.isMoving }">
            <slot name="left"></slot>
         </div>
         <div class="trigger-con" :style="{ left: `${splitData.offset}%` }" @mousedown="handleMousedown">
            <slot name="trigger">
               <trigger mode="vertical" />
            </slot>
         </div>
         <div :style="{ left: `${splitData.offset}%` }" class="right-pane pane" :class="{ 'pane-moving': splitData.isMoving }">
            <slot name="right"></slot>
         </div>
      </template>
      <template v-else>
         <div :style="{ bottom: `${anotherOffset}%` }" class="top-pane pane" :class="{ 'pane-moving': splitData.isMoving }">
            <slot name="top"></slot>
         </div>
         <div class="trigger-con" :style="{ top: `${splitData.offset}%` }" @mousedown="handleMousedown">
            <slot name="trigger">
               <trigger mode="horizontal" />
            </slot>
         </div>
         <div :style="{ top: `${splitData.offset}%` }" class="bottom-pane pane" :class="{ 'pane-moving': splitData.isMoving }">
            <slot name="bottom"></slot>
         </div>
      </template>
   </div>
</template>

<script setup lang="ts">
import { shallowReactive, computed, onMounted, onBeforeUnmount, watch, nextTick, ref } from "vue"
import trigger from "./trigger.vue"

onMounted(() => {
   resizeComputeOffset()
   window.addEventListener("resize", resizeComputeOffset)
})
onBeforeUnmount(() => {
   window.removeEventListener("resize", resizeComputeOffset)
})
watch(
   () => props.modelValue,
   () => {
      computeOffset()
   }
)
const props = withDefaults(
   defineProps<{
      modelValue?: number | string
      mode?: "horizontal" | "vertical"
      min?: number | string
      max?: number | string
   }>(),
   {
      modelValue: 0.5,
      mode: "horizontal",
      min: "40", //左边最小值
      max: "40" // 右边最小值
   }
)
const emits = defineEmits<{
   (e: "update:modelValue", value: number | string): void
   (e: "on-move-start"): void
   (e: "on-moving", event: MouseEvent): void
   (e: "on-move-end"): void
   (e: "on-resize-change"): void
}>()

const splitDom = ref<HTMLDivElement>()
const splitData = shallowReactive({
   initOffset: 0,
   offset: 0,
   oldOffset: 0,
   isMoving: false,
   computedMin: 0,
   computedMax: 0
})
const isHorizontal = computed(() => {
   return props.mode === "horizontal"
})
const anotherOffset = computed(() => {
   return 100 - splitData.offset
})
const valueIsPx = computed(() => {
   return typeof props.modelValue === "string"
})
const offsetSize = computed(() => {
   return isHorizontal.value ? "offsetWidth" : "offsetHeight"
})
function handleMousedown(e: MouseEvent) {
   splitData.initOffset = isHorizontal.value ? e.pageX : e.pageY
   splitData.oldOffset = typeof props.modelValue === "string" ? parseFloat(props.modelValue) : props.modelValue
   splitData.isMoving = true
   document.addEventListener("mousemove", handleMove)
   document.addEventListener("mouseup", handleUp)
   emits("on-move-start")
}
function handleMove(e: MouseEvent) {
   const pageOffset = isHorizontal.value ? e.pageX : e.pageY
   const offset = pageOffset - splitData.initOffset
   const splitWidth = splitDom.value![offsetSize.value]
   const value = valueIsPx.value ? splitData.oldOffset + offset : px2percent(splitWidth * splitData.oldOffset + offset, splitWidth)
   upDateValue(value)
   emits("on-moving", e)
}
function handleUp() {
   splitData.isMoving = false
   document.removeEventListener("mousemove", handleMove)
   document.removeEventListener("mouseup", handleUp)
   emits("on-move-end")
}
function upDateValue(value: number) {
   const anotherValue = getAnotherOffset(value)
   if (value <= splitData.computedMin) value = splitData.computedMin
   if (anotherValue <= splitData.computedMax) value = getAnotherOffset(splitData.computedMax)
   emits("update:modelValue", valueIsPx.value ? value + "" : value)
}
function computeOffset() {
   nextTick(() => {
      splitData.offset =
         ((valueIsPx.value ? px2percent(props.modelValue, splitDom.value![offsetSize.value]) : (props.modelValue as number)) * 10000) / 100
   })
}
// 窗口大小改变的时候需要重新计算值
function resizeComputeOffset() {
   computeMinMax()
   upDateValue(typeof props.modelValue === "string" ? parseFloat(props.modelValue) : props.modelValue)
   computeOffset()
   emits("on-resize-change")
}
function computeMinMax() {
   const splitWidth = splitDom.value![offsetSize.value]
   let computedMin = getComputedThresholdValue("min")
   let computedMax = getComputedThresholdValue("max")
   // 当最小最大值相加大于窗口时候 取0.1作为最小和最大值
   if (getAnotherOffset(computedMin + computedMax) < 0) {
      computedMin = valueIsPx.value ? splitWidth * 0.1 : 0.1
      computedMax = valueIsPx.value ? splitWidth * 0.1 : 0.1
   }
   splitData.computedMin = computedMin
   splitData.computedMax = computedMax
}
function getAnotherOffset(value: number) {
   return valueIsPx.value ? splitDom.value![offsetSize.value] - value : 1 - value
}
function getComputedThresholdValue(type: "min" | "max"): number {
   const splitWidth = splitDom.value![offsetSize.value]
   const typeValue = props[type]
   if (valueIsPx.value) return typeof typeValue === "string" ? parseFloat(typeValue) : splitWidth * typeValue
   else return typeof typeValue === "string" ? px2percent(typeValue, splitWidth) : typeValue
}
function px2percent(numerator: string | number, denominator: string | number) {
   return (
      parseFloat(typeof numerator === "number" ? numerator + "" : numerator) /
      parseFloat(typeof denominator === "number" ? denominator + "" : denominator)
   )
}
</script>

<style lang="scss" scoped>
$trigger-width: 6px; //确保与trigger里面宽度一致
.split {
   position: relative;
   width: 100%;
   height: 100%;
   &.no-select {
      user-select: none;
   }
   .cover {
      position: absolute;
      width: 100%;
      height: 100%;
      z-index: 1;
   }
   .pane {
      position: absolute;
      &.left-pane,
      &.right-pane {
         top: 0;
         bottom: 0;
      }
      &.left-pane {
         left: 0;
      }
      &.right-pane {
         padding-left: $trigger-width;
         right: 0;
      }
      &.top-pane,
      &.bottom-pane {
         left: 0;
         right: 0;
      }
      &.top-pane {
         top: 0;
      }
      &.bottom-pane {
         padding-top: $trigger-width;
         bottom: 0;
      }
      &.pane-moving {
         user-select: none;
      }
   }
   .trigger-con {
      position: absolute;
      z-index: 10;
   }
   &.horizontal {
      .trigger-con {
         top: 0;
         bottom: 0;
      }
   }
   &.vertical {
      .trigger-con {
         left: 0;
         right: 0;
      }
   }
}
</style>
