// See: https://github.com/inertiajs/inertia/blob/48bcd21fb7daf467d0df1bfde2408f161f94a579/packages/react/src/WhenVisible.ts
import { createElement, useCallback, useEffect, useRef, useState, ReactNode, ElementType } from 'react'

import type { ReloadOptions } from './types'
import useModal from './useModal'

interface WhenVisibleProps {
    children: ReactNode
    data?: string | string[]
    params?: ReloadOptions
    buffer?: number
    as?: ElementType
    always?: boolean
    fallback?: ReactNode
}

const WhenVisible = ({ children, data, params, buffer, as, always, fallback }: WhenVisibleProps) => {
    always = always ?? false
    as = as ?? 'div'
    fallback = fallback ?? null

    const [loaded, setLoaded] = useState(false)
    const hasFetched = useRef(false)
    const fetching = useRef(false)
    const ref = useRef<HTMLElement>(null)

    const modal = useModal()

    const getReloadParams = useCallback((): ReloadOptions => {
        if (data) {
            return {
                only: Array.isArray(data) ? data : [data],
            }
        }

        if (!params) {
            throw new Error('You must provide either a `data` or `params` prop.')
        }

        return params
    }, [params, data])

    useEffect(() => {
        if (!ref.current) {
            return
        }

        const observer = new IntersectionObserver(
            (entries) => {
                if (!entries[0].isIntersecting) {
                    return
                }

                if (!always && hasFetched.current) {
                    observer.disconnect()
                }

                if (fetching.current) {
                    return
                }

                hasFetched.current = true
                fetching.current = true

                const reloadParams = getReloadParams()

                modal?.reload({
                    ...reloadParams,
                    onStart: () => {
                        fetching.current = true
                        reloadParams.onStart?.()
                    },
                    onFinish: () => {
                        setLoaded(true)
                        fetching.current = false
                        reloadParams.onFinish?.()

                        if (!always) {
                            observer.disconnect()
                        }
                    },
                })
            },
            {
                rootMargin: `${buffer || 0}px`,
            },
        )

        observer.observe(ref.current)

        return () => {
            observer.disconnect()
        }
    }, [ref, getReloadParams, buffer])

    if (always || !loaded) {
        return createElement(
            as,
            {
                props: null,
                ref,
            },
            loaded ? children : fallback,
        )
    }

    return loaded ? children : null
}

WhenVisible.displayName = 'InertiaWhenVisible'

export default WhenVisible
