import gsap from 'gsap'
import { useWindowSize } from '@vueuse/core'
import useLenis from '@/composables/useLenis'
import { ref, reactive, computed, nextTick, onBeforeUnmount, watch } from 'vue'

// Window size for viewport calculations
const { height: wHeight } = useWindowSize()

/**
 * Bidirectional infinite virtual scroll composable
 * Manages virtual viewports with on-demand content generation.
 * The page stays at 100vh — scroll position is tracked via Lenis
 * virtual-scroll events and viewports are positioned with CSS transforms.
 *
 * @param {Object} options Configuration options
 * @param {Function} options.generateContent Function to generate content for a viewport
 * @param {Number} options.viewportHeight Height of each viewport (defaults to window height)
 * @param {Number} options.bufferSize Number of viewports to keep in memory outside visible area
 * @param {Number} options.initialIndex Starting viewport index
 * @returns {Object} Infinite scroll state and methods
 */
export const useInfiniteScroll = (options = {}) => {
    const {
        generateContent = (index) => ({ index, content: `Viewport ${index}` }),
        viewportHeight = null,
        bufferSize = 1,
        initialIndex = 0,
    } = options

    // Computed viewport height (use provided or window height)
    const vpHeight = computed(() => viewportHeight || wHeight.value)

    // Container reference
    const containerRef = ref(null)

    // Core state
    const state = reactive({
        // Accumulated virtual scroll position (not the real DOM scroll)
        scrollY: initialIndex * (viewportHeight || 0),
        // Active state to stop/start scroll
        active: true,
        // Index of the currently visible viewport
        currentViewportIndex: initialIndex,
        // Map of loaded viewports { index: { content, element, height, offsetY } }
        viewports: new Map(),
        // Loading states
        isLoadingUp: false,
        isLoadingDown: false,
        // Error handling
        errors: new Map(),
        hasError: false,
        retryCount: new Map(),
    })

    // Initialize with starting viewport
    const initializeViewports = async () => {
        // Seed scrollY once vpHeight is available
        state.scrollY = initialIndex * vpHeight.value

        const initialViewport = {
            index: initialIndex,
            content: await generateContent(initialIndex),
            height: vpHeight.value,
            offsetY: initialIndex * vpHeight.value,
        }

        state.viewports.set(initialIndex, initialViewport)

        await loadViewportsInRange(
            initialIndex - bufferSize,
            initialIndex + bufferSize,
        )

        updateViewportPositions()
    }

    // Load viewports in a range
    const loadViewportsInRange = async (startIndex, endIndex) => {
        const loadPromises = []

        for (let i = startIndex; i <= endIndex; i++) {
            if (!state.viewports.has(i)) {
                loadPromises.push(loadViewport(i))
            }
        }

        await Promise.all(loadPromises.filter(Boolean))
        updateViewportPositions()
    }

    // Load a single viewport with retry logic
    const loadViewport = async (index, isRetry = false) => {
        const maxRetries = 3
        const currentRetryCount = state.retryCount.get(index) || 0

        try {
            if (index < state.currentViewportIndex) {
                state.isLoadingUp = true
            } else if (index > state.currentViewportIndex) {
                state.isLoadingDown = true
            }

            const content = await generateContent(index)
            const viewport = {
                index,
                content,
                height: vpHeight.value,
                offsetY: index * vpHeight.value,
            }

            state.viewports.set(index, viewport)

            state.errors.delete(index)
            state.retryCount.delete(index)
            state.hasError = state.errors.size > 0

            return viewport
        } catch (error) {
            console.error(`Failed to load viewport ${index}:`, error)

            state.errors.set(index, {
                message: error.message || 'Failed to load content',
                timestamp: Date.now(),
                retryCount: currentRetryCount,
            })
            state.hasError = true

            if (currentRetryCount < maxRetries && !isRetry) {
                state.retryCount.set(index, currentRetryCount + 1)
                const delay = Math.pow(2, currentRetryCount) * 1000
                setTimeout(() => loadViewport(index, true), delay)
            }

            return null
        } finally {
            state.isLoadingUp = false
            state.isLoadingDown = false
        }
    }

    // Update viewport positions based on their index
    const updateViewportPositions = () => {
        for (const [index, viewport] of state.viewports) {
            viewport.offsetY = index * vpHeight.value
        }
    }

    // Remove viewports outside buffer range
    const cleanupViewports = () => {
        const currentIndex = state.currentViewportIndex
        const minKeepIndex = currentIndex - bufferSize - 2
        const maxKeepIndex = currentIndex + bufferSize + 2

        for (const [index] of state.viewports) {
            if (index < minKeepIndex || index > maxKeepIndex) {
                state.viewports.delete(index)
            }
        }
    }

    // Calculate which viewport should be visible based on virtual scroll position
    const calculateVisibleViewport = (scrollY) => {
        return Math.floor(scrollY / vpHeight.value)
    }

    // Handle virtual-scroll delta events from Lenis
    const onVirtualScroll = async ({ deltaY, event }) => {
        if (!state.active) return

        const isTouch = event.type.includes('touch')

        if (isTouch) {
            deltaY *= 10
        } else {
            deltaY *= 3
        }

        gsap.to(state, {
            scrollY: state.scrollY + deltaY,
            duration: 1,
            ease: 'expo.out',
        })

        const newViewportIndex = calculateVisibleViewport(state.scrollY)

        if (newViewportIndex !== state.currentViewportIndex) {
            state.currentViewportIndex = newViewportIndex

            const rangeStart = newViewportIndex - bufferSize
            const rangeEnd = newViewportIndex + bufferSize

            await loadViewportsInRange(rangeStart, rangeEnd)
            cleanupViewports()
        }
    }

    // Attach the virtual-scroll listener once Lenis is ready
    const lenis = useLenis()
    watch(
        lenis,
        (instance) => {
            if (instance) {
                instance.on('virtual-scroll', onVirtualScroll)
            }
        },
        { immediate: true },
    )

    onBeforeUnmount(() => {
        lenis.value?.off('virtual-scroll', onVirtualScroll)
    })

    // Get viewports to render (current + buffer)
    const visibleViewports = computed(() => {
        const current = state.currentViewportIndex
        const range = bufferSize + 1
        const start = current - range
        const end = current + range

        const viewports = []
        for (let i = start; i <= end; i++) {
            if (state.viewports.has(i)) {
                viewports.push(state.viewports.get(i))
            }
        }

        return viewports.sort((a, b) => a.index - b.index)
    })

    // Retry failed viewport
    const retryViewport = async (index) => {
        state.errors.delete(index)
        state.retryCount.delete(index)
        state.hasError = state.errors.size > 0
        return await loadViewport(index)
    }

    // Reset to initial state
    const reset = async () => {
        state.viewports.clear()
        state.errors.clear()
        state.retryCount.clear()
        state.hasError = false
        state.currentViewportIndex = initialIndex
        state.scrollY = initialIndex * vpHeight.value
        state.isLoadingUp = false
        state.isLoadingDown = false
        await initializeViewports()
    }

    // Start/stop scrolling
    const stop = () => {
        state.active = false

        gsap.killTweensOf(state)
    }
    const start = () => {
        state.active = true
    }

    // Initialize on creation
    nextTick(() => {
        initializeViewports()
    })

    return {
        // State
        containerRef,
        currentViewportIndex: computed(() => state.currentViewportIndex),
        visibleViewports,
        isLoadingUp: computed(() => state.isLoadingUp),
        isLoadingDown: computed(() => state.isLoadingDown),
        scrollY: computed(() => state.scrollY),
        vpHeight,

        // Methods
        reset,
        retryViewport,
        stop,
        start,

        // Error state
        hasError: computed(() => state.hasError),
        errors: computed(() =>
            Array.from(state.errors.entries()).map(([index, error]) => ({
                index,
                ...error,
            })),
        ),

        // Internal state for debugging
        _state: state,
    }
}
