<template>
  <div>
    <transition name="slide">
      <div
        v-if="isScroll"
        v-show="showTop"
        class="fixed topTool border border-[#396684] bg-[#1c2c34cc] top-0 z-99999 flex text-white py-4px px-10px"
      >
        <div class="px-10px cursor-pointer" @click="handleScrollLeft">左</div>
        <div class="px-10px cursor-pointer" @click="handleScrollCenter">中</div>
        <div class="px-10px cursor-pointer" @click="handleScrollRight">右</div>
      </div>
    </transition>
    <section
      ref="screenRef"
      :style="{ ...styles.box, ...boxStyle }"
      class="v-screen-box"
    >
      <div
        :style="{ ...styles.wrapper, ...wrapperStyle }"
        class="screen-wrapper"
        ref="screenWrapper"
      >
        <slot></slot>
      </div>
    </section>
  </div>
</template>
<script>
import {
  defineComponent,
  nextTick,
  onMounted,
  onUnmounted,
  reactive,
  ref,
  toRef,
} from 'vue'
/**
 * 防抖函数
 * @param {Function} fn
 * @param {number} delay
 * @returns {() => void}
 */
function debounce(fn, delay) {
  let timer
  return function (...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(
      () => {
        typeof fn === 'function' && fn.apply(null, args)
        clearTimeout(timer)
      },
      delay > 0 ? delay : 100
    )
  }
}
export default defineComponent({
  name: 'ScaleScreen',
  props: {
    width: {
      type: [String, Number],
      default: 1920,
    },
    height: {
      type: [String, Number],
      default: 1080,
    },
    fullScreen: {
      type: Boolean,
      default: false,
    },
    autoScale: {
      type: [Object, Boolean],
      default: true,
    },
    delay: {
      type: Number,
      default: 500,
    },
    boxStyle: {
      type: Object,
      default: () => ({}),
    },
    wrapperStyle: {
      type: Object,
      default: () => ({}),
    },
  },
  setup(props) {
    const state = reactive({
      width: 0,
      height: 0,
      originalWidth: 0,
      originalHeight: 0,
      observer: null,
      showTop: false,
      isScroll: false,
    })
    const screenRef = ref()
    const styles = reactive({
      box: {
        overflow: 'hidden',
        backgroundSize: `100% 100%`,
        background: `#000`,
        width: `100vw`,
        height: `100vh`,
      },
      wrapper: {
        transitionProperty: `all`,
        transitionTimingFunction: `cubic-bezier(0.4, 0, 0.2, 1)`,
        transitionDuration: `500ms`,
        position: `relative`,
        overflow: `hidden`,
        zIndex: 100,
        transformOrigin: `left top`,
        display: 'inline-block',
      },
    })
    const screenWrapper = ref()
    /**
     * 初始化大屏容器宽高
     */
    const initSize = () => {
      return new Promise((resolve) => {
        nextTick(() => {
          var _a, _b
          // region 获取大屏真实尺寸
          if (props.width && props.height) {
            state.width = props.width
            state.height = props.height
          } else {
            state.width =
              (_a = screenWrapper.value) === null || _a === void 0
                ? void 0
                : _a.clientWidth
            state.height =
              (_b = screenWrapper.value) === null || _b === void 0
                ? void 0
                : _b.clientHeight
          }
          // endregion
          // region 获取画布尺寸
          if (!state.originalHeight || !state.originalWidth) {
            state.originalWidth = window.screen.width
            state.originalHeight = window.screen.height
          }
          // endregion
          resolve()
        })
      })
    }
    /**
     * 更新大屏容器宽高
     */
    const updateSize = () => {
      if (state.width && state.height) {
        screenWrapper.value.style.width = `${state.width}px`
        screenWrapper.value.style.height = `${state.height}px`
      } else {
        screenWrapper.value.style.width = `${state.originalWidth}px`
        screenWrapper.value.style.height = `${state.originalHeight}px`
      }
    }
    const autoScale = (scale) => {
      if (!props.autoScale) return
      const domWidth = screenWrapper.value.clientWidth
      const domHeight = screenWrapper.value.clientHeight
      const currentWidth = document.body.clientWidth
      const currentHeight = document.body.clientHeight
      screenWrapper.value.style.transform = `scale(${scale},${scale})`
      let mx = Math.max((currentWidth - domWidth * scale) / 2, 0)
      let my = Math.max((currentHeight - domHeight * scale) / 2, 0)
      if (typeof props.autoScale === 'object') {
        !props.autoScale.x && (mx = 0)
        !props.autoScale.y && (my = 0)
      }
      screenWrapper.value.style.margin = `${my}px ${mx}px`
    }
    const updateScale = () => {
      // 获取真实视口尺寸
      const currentWidth = document.body.clientWidth
      let currentHeight = document.body.clientHeight
      if (currentWidth < 3000) currentHeight = currentHeight - 10
      // 获取大屏最终的宽高
      const realWidth = state.width || state.originalWidth
      const realHeight = state.height || state.originalHeight
      // 计算缩放比例
      const widthScale = currentWidth / +realWidth
      const heightScale = currentHeight / +realHeight
      // 若要铺满全屏，则按照各自比例缩放
      if (props.fullScreen) {
        screenWrapper.value.style.transform = `scale(${widthScale},${heightScale})`
        return false
      }
      // 按照宽高最小比例进行缩放
      let scale = Math.min(widthScale, heightScale)
      if (currentWidth < 3000) {
        scale = heightScale
        state.isScroll = true
        styles.box['overflow-x'] = 'auto'
      } else {
        state.isScroll = false
        styles.box.overflow = 'hidden'
      }
      autoScale(scale)
    }
    const onResize = debounce(async () => {
      await initSize()
      updateSize()
      updateScale()
    }, props.delay)
    const initMutationObserver = () => {
      const observer = (state.observer = new MutationObserver(() => {
        onResize()
      }))
      observer.observe(screenWrapper.value, {
        attributes: true,
        attributeFilter: ['style'],
        attributeOldValue: true,
      })
    }
    const onMouseUpdate = (e) => {
      if (e.pageY <= 30) state.showTop = true
      else state.showTop = false
    }
    onMounted(() => {
      nextTick(async () => {
        await initSize()
        updateSize()
        updateScale()
        window.addEventListener('resize', onResize)
        window.addEventListener('mousemove', onMouseUpdate)
        initMutationObserver()
      })
    })
    onUnmounted(() => {
      var _a
      window.removeEventListener('resize', onResize)
      window.removeEventListener('mousemove', onMouseUpdate)
      ;(_a = state.observer) === null || _a === void 0
        ? void 0
        : _a.disconnect()
    })
    const handleScrollLeft = () => {
      nextTick(() => {
        var _a
        ;(_a = screenRef.value) === null || _a === void 0
          ? void 0
          : _a.scrollTo({ left: 0, behavior: 'smooth' })
      })
    }
    const handleScrollCenter = () => {
      nextTick(() => {
        var _a, _b, _c
        ;(_a = screenRef.value) === null || _a === void 0
          ? void 0
          : _a.scrollTo({
              left:
                ((_c =
                  (_b = screenRef.value) === null || _b === void 0
                    ? void 0
                    : _b.scrollWidth) !== null && _c !== void 0
                  ? _c
                  : 0) /
                  2 -
                document.body.clientWidth / 2,
              behavior: 'smooth',
            })
      })
    }
    const handleScrollRight = () => {
      nextTick(() => {
        var _a, _b
        ;(_a = screenRef.value) === null || _a === void 0
          ? void 0
          : _a.scrollTo({
              left:
                (_b = screenRef.value) === null || _b === void 0
                  ? void 0
                  : _b.scrollWidth,
              behavior: 'smooth',
            })
      })
    }
    const showTop = toRef(state, 'showTop')
    const isScroll = toRef(state, 'isScroll')
    return {
      screenWrapper,
      styles,
      isScroll,
      showTop,
      screenRef,
      handleScrollLeft,
      handleScrollCenter,
      handleScrollRight,
    }
  },
})
</script>
<style scoped lang="less">
.topTool {
  left: 50%;
  transform: translateX(-50%);
}
</style>
