<script setup lang="ts">
import { type VNode, ref, computed, onMounted, onDeactivated } from 'vue'

/**
 * @author xxl
 * @email xxl.nice@foxmail.com
 * @createDate 2022-9-4
 * @updateDate 2023-8-3
 */
/* 定义根组件类名 */
const root = 'xxl-batter-scroll'
/**
 * @property {'scroll'} 滚动
 * @property {'refresh'}下拉刷新
 * @property {'hitBottom'}触底
 */
const emit = defineEmits<{
  (event: 'scroll', d?: any): void
  (event: 'refresh'): void
  (event: 'hitBottom', d?: any): void
}>()
/**
 *  @property {width} 滚动容器宽度
 *  @property {height} 滚动容器高度
 *  @property {scroll} 滚动条可选参数
 *      @property {sHeight} 滚轮滚动的时候每次滚动的像素
 *      @property {barBoxWidth} 滚动栏的宽度
 *      @property {barButtonHeight} 滚动栏两边的按钮高度
 *      @property {barWidth} 滚动条宽度
 *      @property {barBack} 滚动栏背景色
 *      @property {barColor} 滚动条颜色
 *      @property {barButtonColor} 滚动栏班牛颜色
 * @property {scrollBar} 是否演示滚动条
 */
const props = defineProps<{
  width?: string | ''
  height?: string | ''
  scroll?: {
    sHeight?: number
    back?: string
    barBoxWidth?: number
    barButtonHeight?: number
    barButtonColor?: string
    barWidth?: number
    barBack?: string
    barColor?: string
    top?: number
    bottom?: number
    hideTime?: number
    offFlex?: boolean
  }
  touch?: boolean
  scrollBar?: boolean
}>()

/**
 * 获取父组件传递进来的插槽
 */
const slots = defineSlots<{
  default?: () => VNode
  scroll?: () => VNode
  refresh?: () => VNode
  hitBottom?: () => VNode
}>()
// 滚动容器
const xxlScrollBox = ref<HTMLElement>()
const xxlScrollMain = ref<HTMLElement>()
const scrollData = ref<{
  top: number
  barTop: number
  barLeft: number
  barHeightR: number
  barHeightB: number
  scrolling: boolean
  refreshState:"none"|"start"|""
}>({
  top: 0,
  barTop: 0,
  barLeft: 0,
  barHeightR: 20,
  barHeightB: 20,
  scrolling: false,
  refreshState:"none"
})
// 定义滚动条最小高度
let minBarHeight: number = 30

let client = {
  width: 0,
  height: 0,
  scrollTop: 0,
  scrollLeft: 0,
  scrollHeight: 0,
  scrollWidth: 0,
  lastTop: 0,
  lastTouch: {
    x: 0,
    y: 0,
    time: 0,
    id: 0,
  },
}
/* 滚动容器高度 */

/* 滚动条位置 */
let timeout = 0
function scrollBar() {
  if (!props.scrollBar) {
    return
  }
  scrollData.value.scrolling = true
  clearTimeout(timeout)
  if (props.scroll?.hideTime != 0) {
    timeout = setTimeout(() => {
      scrollData.value.scrolling = false
    }, props.scroll?.hideTime || 1000)
  }
  let bBH = props.scroll?.barButtonHeight || 0
  if (client.scrollHeight > client.height) {
    let sBoxHeight = client.height - bBH * 2
    let b = client.scrollTop / (client.scrollHeight - client.height)
    scrollData.value.barTop = b * (sBoxHeight - scrollData.value.barHeightR)
  }
  if (client.scrollWidth > client.width) {
    let sBoxWidth = client.width - bBH * 2
    let b2 = client.scrollLeft / (client.scrollWidth - client.width)
    scrollData.value.barLeft = b2 * (sBoxWidth - scrollData.value.barHeightB)
  }
}

// 监听页面滚动事件
function scrolled() {
  client.scrollTop = xxlScrollBox.value?.scrollTop || 0
  client.scrollLeft = xxlScrollBox.value?.scrollLeft || 0
  scrollBar()
  emit('scroll', client)
}

/**
 * 把页面滚动到某个位置
 * @param to 页面应该滚动到的位置
 */
function scrollTo({ x, y }: { x?: number; y?: number }) {
  if (xxlScrollBox.value) {
    y != undefined && (xxlScrollBox.value.scrollTop = y)
    x != undefined && (xxlScrollBox.value.scrollLeft = x)
  }
  scrollBar()
}
/* 通过点击滚动条控制页面滚动 */
function scrollbarClick(e: MouseEvent, isR: boolean) {
  if (isR) {
    // 获取滚动条长度
    let barH = scrollData.value.barHeightR
    //  计算滚动盒子长度
    let boxH = client.height - barH - (props.scroll?.barButtonHeight || 0)
    let y = e.offsetY - barH / 2
    y < 0 && (y = 0)
    y > boxH && (y = boxH)
    scrollTo({ y: (client.scrollHeight - client.height) * (y / boxH) })
  } else {
    // 获取滚动条长度
    let barH = scrollData.value.barHeightB
    //  计算滚动盒子长度
    let boxH = client.width - barH - (props.scroll?.barButtonHeight || 0)
    let y = e.offsetX - barH / 2
    y < 0 && (y = 0)
    y > boxH && (y = boxH)
    scrollTo({ x: (client.scrollWidth - client.width) * (y / boxH) })
  }
}

// 控制滚动条长度
function resizeBarHeight() {
  if (!xxlScrollBox.value) {
    return
  }
  const boxDom: HTMLElement = xxlScrollBox.value
  client.height = boxDom.clientHeight
  client.width = boxDom.clientWidth
  client.scrollHeight = boxDom.scrollHeight
  client.scrollWidth = boxDom.scrollWidth
  //控制组件滚动条
  if (props.scrollBar) {
    // 右侧
    let h = (client.height / client.scrollHeight) * client.height
    h < minBarHeight && (h = minBarHeight)
    scrollData.value.barHeightR = h
    let w = (client.width / client.scrollWidth) * client.width
    // 左侧
    w < minBarHeight && (w = minBarHeight)
    scrollData.value.barHeightB = w
    scrollBar()
  }
}
function test(e: Event) {
  // e.preventDefault()
}
/**
 * 鼠标滚轮控制滚动
 * @param e 事件参数
 */
function wheel(e: WheelEvent) {
  let top
  /**
   * 控制滚轮滚动的像素
   * 如若没有设置 就使用系统默认的值
   */

  // 使用props传递的值
  if (e.deltaY > 0) {
    top = client.scrollTop + (props.scroll?.sHeight || e.deltaY)
  } else {
    top = client.scrollTop - (props.scroll?.sHeight || -e.deltaY)
  }
  e.preventDefault()
  scrollTo({ y: top })
}
// 设置 触摸点相关信息
function setLastTouch(t: Touch, e: TouchEvent) {
  client.lastTouch.x = t.clientX
  client.lastTouch.y = t.clientY
  client.lastTouch.id = t.identifier
  client.lastTouch.time = e.timeStamp
}
function touchmove(e: TouchEvent) {
  e.preventDefault()
  for (let i = 0; i < e.touches.length; i++) {
    if (e.touches[i].identifier == client.lastTouch.id) {
      let { x, y } = client.lastTouch
      let { clientX, clientY } = e.touches[i]
      scrollTo({
        x: client.scrollLeft + x - clientX,
        y: client.scrollTop + y - clientY,
      })
      setLastTouch(e.touches[i], e)
      break
    }
  }

  emit('hitBottom', e.targetTouches)
}
function touchstart(e: TouchEvent) {
  if (e.touches.length == 1) {
    setLastTouch(e.touches[0], e)
  } else {
  }
  emit('hitBottom', e.targetTouches)
}

function touchend(e: TouchEvent) {
  if (e.touches.length > 0) {
    setLastTouch(e.touches[0], e)
  }
  emit('hitBottom', e.targetTouches)
}
// 定义内容大小变化监听器
const rOBS = new ResizeObserver(() => {
  resizeBarHeight()
})
onMounted(() => {
  if (xxlScrollBox.value) {
    rOBS.observe(xxlScrollBox.value)
  }
  if (xxlScrollMain.value) {
    rOBS.observe(xxlScrollMain.value)
  }
})

defineExpose({
  scrollTo,
})
onDeactivated(() => {
  rOBS.disconnect()
})
</script>
<template>
  <div
    ref="xxlScrollRoot"
    :class="root"
    :style="{
      width,
      height,
      background: `${props.scroll?.back || 'var(--xxl-color-white)'}`,
      '--boxWidth': `${props.scroll?.barBoxWidth || 10}px`,
      '--button': `${props.scroll?.barButtonHeight || 0}px`,
      '--barWidth': `${props.scroll?.barWidth || 5}px`,
      '--barHeight-r': `${scrollData.barHeightR}px`,
      '--barHeight-b': `${scrollData.barHeightB}px`,
      '--barBack': `${props.scroll?.barBack || 'none'}`,
      '--barColor': `${
        props.scroll?.barColor || 'var(--xxl-scroll-bar-color)'
      }`,
      '--barButtonColor': `${
        props.scroll?.barButtonColor || 'var(--xxl-scroll-bar-color)'
      }px`,
    }">
    <!-- 滚动主体 -->
    <div
      ref="xxlScrollBox"
      @scroll="scrolled"
      @wheel="wheel"
      @touchmove="touchmove"
      @touchstart="touchstart"
      @touchend="touchend"
      :class="[root + '-ctx', 'xxl-clear-def']">
      <div
        ref="xxlScrollMain"
        :style="{
          top: `${scrollData.top}px`,
        }"
        :class="root + '-main'">
        <slot name="refresh"></slot>
        <!-- 内容插槽 -->
        <slot></slot>
        <slot name="hitBottom"></slot>
      </div>
    </div>
    <!-- 滚动条 -->
    <div
      :class="`${root}-scrollbar-box-r`"
      v-if="props.scrollBar && scrollData.barHeightR < client.height"
      @click.self="scrollbarClick($event, true)">
      <div :class="`${root}-scrollbar-top`"></div>
      <div
        :style="{ top: `${scrollData.barTop}px` }"
        :class="[
          `${root}-scrollbar-center-r`,
          scrollData.scrolling ? `${root}-scrolling` : `${root}-scrolled`,
        ]"></div>
      <div :class="`${root}-scrollbar-botton`"></div>
    </div>
    <div
      :class="`${root}-scrollbar-box-b`"
      v-if="props.scrollBar && scrollData.barHeightB < client.width"
      @click.self="scrollbarClick($event, false)">
      <div :class="`${root}-scrollbar-left`"></div>
      <div
        :style="{ left: `${scrollData.barLeft}px` }"
        :class="[
          `${root}-scrollbar-center-b`,
          scrollData.scrolling ? `${root}-scrolling` : `${root}-scrolled`,
        ]"></div>
      <div :class="`${root}-scrollbar-right`"></div>
    </div>
  </div>
</template>
<style lang="scss">
/* 定义class前缀 */
$root: 'xxl-batter-scroll';
.#{$root} {
  overflow: hidden !important;
  position: relative;
}
.#{$root}-ctx {
  width: 100%;
  height: 100%;
  &::-webkit-scrollbar {
    background: none;
  }
  overflow: hidden;
  position: relative;
  .#{$root}-main {
    position: absolute;
  }
  .#{$root}-main-scrolled {
    position: absolute;
    transition: top 1s;
  }
}

/* 滚动条样式 */
.#{$root}-scrollbar-box-r {
  height: 100%;
  width: var(--boxWidth);
  right: 0px;
  top: 0px;
}
.#{$root}-scrollbar-box-b {
  width: 100%;
  height: var(--boxWidth);
  bottom: 0px;
  left: 0px;
}
.#{$root}-scrollbar-box-r,
.#{$root}-scrollbar-box-b {
  position: absolute;
  background-color: var(--barBack);

  .#{$root}-scrollbar-center-r,
  .#{$root}-scrollbar-center-b {
    cursor: grab;
    background: var(--barColor);
    border-radius: calc(var(--barWidth) / 2);
    position: absolute;
  }
  .#{$root}-scrollbar-center-r {
    margin: var(--button) 0px;
    left: calc((var(--boxWidth) - var(--barWidth)) / 2);
    height: var(--barHeight-r);
    width: var(--barWidth);
  }
  .#{$root}-scrollbar-center-b {
    margin: 0px var(--button);
    bottom: calc((var(--boxWidth) - var(--barWidth)) / 2);
    height: var(--barWidth);
    width: var(--barHeight-b);
  }
  .#{$root}-scrolled {
    transition-property: opacity;
    transition-duration: 1s;
    transition-timing-function: linear;
    opacity: 0;
  }
  .#{$root}-scrolling {
    opacity: 1;
  }
  @mixin m-bar-button-r {
    cursor: pointer;
    width: 100%;
    height: var(--button);
    background-color: var(--xxl-scroll-bar-color);
    position: absolute;
  }
  @mixin m-bar-button-b {
    cursor: pointer;
    height: 100%;
    width: var(--button);
    background-color: var(--xxl-scroll-bar-color);
    position: absolute;
  }
  /* 回到顶部按钮 */
  .#{$root}-scrollbar-top {
    @include m-bar-button-r;
    top: 0;
  }
  /* 回到底部按钮 */
  .#{$root}-scrollbar-botton {
    @include m-bar-button-r;
    bottom: 0;
  }
  .#{$root}-scrollbar-left {
    @include m-bar-button-b;
    left: 0;
  }
  .#{$root}-scrollbar-right {
    @include m-bar-button-b;
    right: 0;
  }
}
</style>
