<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 {'pullToRefresh'}下拉刷新
 * @property {'hitBottom'}触底
 */
const emit = defineEmits(['scroll', 'pullToRefresh', 'hitBottom'])
/**
 *  @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
    barBoxWidth?: number
    barButtonHeight?: number
    barButtonColor?: string
    barWidth?: number
    barBack?: string
    barColor?: string
  }
  touch?: boolean
  scrollBar?: boolean | undefined
}>()
/**
 * 获取父组件传递进来的插槽
 */
const slots = defineSlots<{
  default?: () => VNode
  scroll?: () => VNode
}>()
// 滚动容器以及主体内容
const xxlScrollMain = ref<HTMLElement>()
const xxlScrollBox = ref<HTMLElement>()
const scroll = ref<{
  top: number
  left: number
  barTop: number
  barHeight: number
  scrolling: boolean
}>({ top: 0, left: 0, barTop: 0, barHeight: 20, scrolling: false })
// 定义滚动条最小高度
let minBarHeight: number = 30
/* 滚动内容高度 */
const __scrollHeight = ref<number>(0)
const scrollHeight = computed<number>(() => {
  return __scrollHeight.value
})

/* 滚动容器高度 */
const __clientHeight = ref<number>(0)
const clientHeight = computed<number>(() => {
  return __clientHeight.value
})

// 定义内容大小变化监听器
const rOBS = new ResizeObserver(() => {


  resizeBarHeight()
})
onMounted(() => {
  if (xxlScrollMain.value) {
    rOBS.observe(xxlScrollMain.value)
  }
  if (xxlScrollBox.value) {
    rOBS.observe(xxlScrollBox.value)
  }
})

const scrollTop = computed(() => {
  return -scroll.value.top
})
// 控制滚动条长度
function resizeBarHeight() {
  if (xxlScrollMain.value) {
    __scrollHeight.value = xxlScrollMain.value.offsetHeight
  }
  if (xxlScrollBox.value) {
    __clientHeight.value = xxlScrollBox.value.clientHeight
  }
  if (props.scrollBar) {
    let h = (clientHeight.value / scrollHeight.value) * clientHeight.value
    if (h < minBarHeight) {
      h = minBarHeight
    }
    if (h > clientHeight.value) {
      h = clientHeight.value
    }
    scroll.value.barHeight = h
    scrollBar()
  }
  scrollTo(-scroll.value.top)
}
/* 滚动条位置 */
let timeout = 0
function scrollBar() {
  if (!props.scrollBar) {
    return
  }
  scroll.value.scrolling = true
  clearTimeout(timeout)
  timeout = setTimeout(() => {
    scroll.value.scrolling = false
  }, 1000)
  let bBH = props.scroll?.barButtonHeight || 0
  let sBoxHeight = clientHeight.value - bBH * 2
  let b = -scroll.value.top / (scrollHeight.value - clientHeight.value)
  scroll.value.barTop = b * (sBoxHeight - scroll.value.barHeight)
}
/**
 * 鼠标滚轮控制滚动
 * @param e 事件参数
 */
function wheel(e: WheelEvent) {
  let top
  /**
   * 控制滚轮滚动的像素
   * 如若没有设置 就使用系统默认的值
   */
  if (props.scroll?.sHeight) {
    // 使用props传递的值
    if (e.deltaY > 0) {
      top = props.scroll.sHeight - scroll.value.top
    } else {
      top = -scroll.value.top - props.scroll.sHeight
    }
  } else {
    // 使用系统默认值
    top = e.deltaY - scroll.value.top
  }
  scrollTo(top)
}
/* 触摸控制滚动 */
let touch = {
  x: 0,
  y: 0,
}
function touchmove(e: TouchEvent, s?: boolean) {
    let { clientX, clientY } = e.targetTouches[e.changedTouches.length - 1]
    if (s) {
      touch.x = clientX
      touch.y = clientY
      return
    }
    let { x, y } = touch
    let { top } = scroll.value
    scrollTo(y - top - clientY)
    touch.x = clientX
    touch.y = clientY
}

/**
 * 把页面滚动到某个位置
 * @param to 页面应该滚动到的位置
 */
function scrollTo(to: number) {
  to = -to
  let height = scrollHeight.value - clientHeight.value
  if (to > 0) {
    to = 0
  } else if (to + height < 0) {
    to = -height
  } else {
    emit('scroll', { scrollTop, scrollHeight, clientHeight })
  }
  scroll.value.top = to
  scrollBar()
}
/* 通过点击滚动条控制页面滚动 */
function scrollbarClick(e: MouseEvent) {
  // 获取滚动条长度
  let barH = scroll.value.barHeight
  //  计算滚动盒子长度
  let boxH = clientHeight.value - barH - (props.scroll?.barButtonHeight || 0)
  let y = e.offsetY - barH / 2
  y < 0 && (y = 0)
  y > boxH + barH / 2 && (y = boxH)
  scrollTo((scrollHeight.value - clientHeight.value) * (y / boxH))
}
// 鼠标状态
let isMouseDown: boolean = false
function dragScroll(e: MouseEvent, down?: boolean) {
  if (e.button == 0 && down !== undefined) {
    down ? (isMouseDown = true) : (isMouseDown = false)
    return
  }
  if (!isMouseDown || !props.touch) {
    return
  }
  scrollTo(-scroll.value.top - e.movementY)
}
defineExpose({
  scrollTop,
  clientHeight,
  scrollHeight,
  scrollTo,
})
onDeactivated(() => {
  rOBS.disconnect()
})
</script>
<template>
  <div
    :class="root"
    :style="{
      width,
      height,
    }">
    <!-- 滚动主体 -->
    <div
      ref="xxlScrollBox"
      @touchmove.stop="touchmove"
      @touchstart.stop="touchmove($event, true)"
      @mousedown.stop="dragScroll($event, true)"
      @mouseleave="isMouseDown = false"
      @mouseup.stop="dragScroll($event, false)"
      @mousemove.stop="dragScroll"
      :class="[root + '-ctx', 'xxl-clear-def']">
      <div
        ref="xxlScrollMain"
        :style="{
          top: `${scroll.top}px`,
          left: `${scroll.left}px`,
        }"
        :class="root + '-main'">
        <!-- 内容插槽 -->
        <slot></slot>
      </div>
      <!-- 滚动条 -->
      <div
        :class="`${root}-scrollbar-box`"
        :style="{
          '--boxWidth': `${props.scroll?.barBoxWidth || 5}px`,
          '--button': `${props.scroll?.barButtonHeight || 0}px`,
          '--barWidth': `${props.scroll?.barWidth || 5}px`,
          '--barHeight': `${scroll.barHeight || 20}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`,
        }"
        v-if="props.scrollBar && scroll.barHeight < clientHeight">
        <slot name="scroll">
          <div :class="`${root}-scrollbar-ctx`" @click.self="scrollbarClick">
            <div :class="`${root}-scrollbar-top`"></div>
            <div
              :style="{ top: `${scroll.barTop}px` }"
              :class="[
                `${root}-scrollbar-center`,
                scroll.scrolling ? `${root}-scrolling` : `${root}-scrolled`,
              ]"></div>
            <div :class="`${root}-scrollbar-botton`"></div>
          </div>
        </slot>
      </div>
    </div>
  </div>
</template>
<style lang="scss">
/* 定义class前缀 */
$root: 'xxl-batter-scroll';
.#{$root} {
  background: blueviolet;
}
.#{$root}-ctx {
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;
  .#{$root}-main {
    width: calc(100%);
    position: absolute;
  }
}

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