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

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

    const props = {
        /**
         * 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) => ['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' },
    }

    export default defineComponent({
        name: 'LazyContainer',
        components: { Skeleton },
        inheritAttrs: false,
        props,
        emits: ['init'],
        setup(props, { emit }) {
            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()
                }
            }
            return {
                elRef,
                ...toRefs(state),
            }
        },
    })
</script>

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