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

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

/**
 * Bidirectional infinite scroll composable
 * Manages virtual viewports with on-demand content generation
 *
 * @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({
        // Current scroll position
        scrollY: 0,
        // 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,
        // Total virtual height (for scrollbar)
        totalHeight: 0,
        // Error handling
        errors: new Map(),
        hasError: false,
        retryCount: new Map(),
    })

    // Initialize with starting viewport
    const initializeViewports = async () => {
        // Create initial viewport
        const initialViewport = {
            index: initialIndex,
            content: await generateContent(initialIndex),
            height: vpHeight.value,
            offsetY: initialIndex * vpHeight.value,
        }

        state.viewports.set(initialIndex, initialViewport)

        // Load buffer viewports above and below
        await loadViewportsInRange(
            initialIndex - bufferSize,
            initialIndex + bufferSize,
        )

        // Update positions and total height
        updateViewportPositions()
        updateTotalHeight()
    }

    // 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 {
            // Set loading state based on direction
            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)

            // Clear any previous errors for this 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)

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

            // Retry logic
            if (currentRetryCount < maxRetries && !isRetry) {
                state.retryCount.set(index, currentRetryCount + 1)
                console.log(
                    `Retrying viewport ${index} (attempt ${currentRetryCount + 1}/${maxRetries})`,
                )

                // Exponential backoff
                const delay = Math.pow(2, currentRetryCount) * 1000
                setTimeout(() => {
                    loadViewport(index, true)
                }, delay)
            }

            return null
        } finally {
            // Clear loading states
            state.isLoadingUp = false
            state.isLoadingDown = false
        }
    }

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

    // Update total virtual height for scrollbar
    const updateTotalHeight = () => {
        if (state.viewports.size === 0) {
            state.totalHeight = wHeight.value * bufferSize // Minimum height
            return
        }

        const indexes = Array.from(state.viewports.keys())
        // const minIndex = Math.min(...indexes)
        const maxIndex = Math.max(...indexes)

        // Calculate total height needed to accommodate all viewports
        // Add extra buffer above and below for infinite scroll
        const bufferViewports = 10
        const totalRange = maxIndex + bufferViewports
        state.totalHeight = totalRange * 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)
            }
        }

        updateTotalHeight()
    }

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

    // Handle scroll events
    const handleScroll = async (scrollData) => {
        state.scrollY = scrollData.scroll

        const newViewportIndex = calculateVisibleViewport(state.scrollY)

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

            // Load new viewports in range
            const rangeStart = newViewportIndex - bufferSize
            const rangeEnd = newViewportIndex + bufferSize

            await loadViewportsInRange(rangeStart, rangeEnd)

            // Cleanup old viewports
            cleanupViewports()
        }
    }

    // Setup scroll listener with Lenis
    const lenis = useLenis(handleScroll)

    // 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)
    })

    // Scroll to a specific viewport
    const scrollToViewport = (index) => {
        if (lenis.value) {
            const targetY = index * vpHeight.value
            lenis.value.scrollTo(targetY, { immediate: false })
        }
    }

    // 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.isLoadingUp = false
        state.isLoadingDown = false
        await initializeViewports()
    }

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

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

        // Methods
        scrollToViewport,
        reset,
        retryViewport,

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

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