<script lang="ts" setup>
import { onMounted, reactive, ref, toRef } from 'vue'
import { Skeleton } from 'ant-design-vue'
import { useTimeoutFn } from '@vueuse/core'
import { useIntersectionObserver } from '@/hooks/event/useIntersectionObserver'

defineOptions({ name: 'LazyContainer', inheritAttrs: false })

const props = defineProps({
  /**
   * Waiting time, if the time is specified, whether visible or not, it will be automatically loaded after the specified time
   */
  timeout: { type: Number },
  /**
   * The viewport where the component is located.
   * If the component is scrolling in the page container, the viewport is the container
   */
  viewport: {
    type: (typeof window !== 'undefined' ? window.HTMLElement : Object) as PropType<HTMLElement>,
    default: () => null,
  },
  /**
   * Preload threshold, css unit
   */
  threshold: { type: String, default: '0px' },
  /**
   * The scroll direction of the viewport, vertical represents the vertical direction, horizontal represents the horizontal direction
   */
  direction: {
    type: String,
    default: 'vertical',
    validator: (v: string) => ['vertical', 'horizontal'].includes(v),
  },
  /**
   * The label name of the outer container that wraps the component
   */
  tag: { type: String, default: 'div' },
  maxWaitingTime: { type: Number, default: 80 },
  /**
   * transition name
   */
  transitionName: { type: String, default: 'lazy-container' },
})

const emit = defineEmits(['init'])

interface State {
  isInit: boolean
  loading: boolean
  intersectionObserverInstance: IntersectionObserver | null
}

const elRef = ref()
const state = reactive<State>({
  isInit: false,
  loading: false,
  intersectionObserverInstance: null,
})

onMounted(() => {
  immediateInit()
  initIntersectionObserver()
})

// If there is a set delay time, it will be executed immediately
function immediateInit() {
  const { timeout } = props
  timeout
    && useTimeoutFn(() => {
      init()
    }, timeout)
}

function init() {
  state.loading = true

  useTimeoutFn(() => {
    if (state.isInit)
      return
    state.isInit = true
    emit('init')
  }, props.maxWaitingTime || 80)
}

function initIntersectionObserver() {
  const { timeout, direction, threshold } = props
  if (timeout)
    return
  // According to the scrolling direction to construct the viewport margin, used to load in advance
  let rootMargin = '0px'
  switch (direction) {
    case 'vertical':
      rootMargin = `${threshold} 0px`
      break
    case 'horizontal':
      rootMargin = `0px ${threshold}`
      break
  }

  try {
    const { stop, observer } = useIntersectionObserver({
      rootMargin,
      target: toRef(elRef.value, '$el'),
      onIntersect: (entries: any[]) => {
        const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio
        if (isIntersecting) {
          init()
          if (observer)
            stop()
        }
      },
      root: toRef(props, 'viewport'),
    })
  }
  catch (e) {
    init()
  }
}
</script>

<template>
  <transition-group v-bind="$attrs" ref="elRef" class="h-full w-full" :name="transitionName" :tag="tag" mode="out-in">
    <div v-if="state.isInit" key="component">
      <slot :loading="state.loading" />
    </div>
    <div v-else key="skeleton">
      <slot v-if="$slots.skeleton" name="skeleton" />
      <Skeleton v-else />
    </div>
  </transition-group>
</template>
