import React, { useContext, useId } from 'react'
import { GridSyncContext } from './context'
import type { GridScrollHeightRecord, GridScrollPosition } from './types'
import { useResizeObserver } from '@planview/pv-utilities'
import type { GridRowId } from '../../types'
import { compensateHeadAndTail, getScrollbarHeight } from '../../utils/scroll'

type ScrollBoundsType = {
    /** margin above scroll items (e.g. header rows) */
    head: number
    /** margin below scroll items (e.g. footer) */
    tail: number
}

const DEFAULT_BOUNDS: ScrollBoundsType = { head: 0, tail: 0 }

const useScrollHeight = <T extends HTMLElement>(
    ref: React.RefObject<T | null>,
    id: string,
    bounds: Partial<ScrollBoundsType>
) => {
    const { height, enabled } = useContext(GridSyncContext)
    const { subscribe, update } = height

    const { head, tail } = React.useMemo(
        () => ({ ...DEFAULT_BOUNDS, ...bounds }),
        [bounds]
    )

    const onResize = React.useCallback(() => {
        if (ref.current) {
            compensateHeadAndTail(
                ref.current,
                update(id, getScrollbarHeight(ref.current), head, tail),
                head,
                tail
            )
        }
    }, [head, id, ref, tail, update])

    const onChangeHeight = React.useCallback(
        (heights: GridScrollHeightRecord[]) => {
            if (ref.current) {
                compensateHeadAndTail(ref.current, heights, head, tail)
            }
        },
        [tail, head, ref]
    )

    useResizeObserver({ ref, onResize })

    React.useEffect(() => {
        if (enabled) {
            return subscribe(id, onChangeHeight)
        }
    }, [id, enabled, subscribe, onChangeHeight])
}

export const useScrollSync = <T extends HTMLElement>(
    ref: React.RefObject<T | null>,
    bounds: Partial<ScrollBoundsType> = DEFAULT_BOUNDS
) => {
    const id = useId()

    const { enabled, scroll } = useContext(GridSyncContext)
    const { subscribe, update } = scroll

    useScrollHeight(ref, id, bounds)

    const onChange = React.useCallback(
        (position: Partial<GridScrollPosition>) => {
            if (ref.current) {
                if (position.scrollTop !== undefined) {
                    ref.current.scrollTop = position.scrollTop
                }
                if (position.scrollLeft !== undefined) {
                    ref.current.scrollLeft = position.scrollLeft
                }
            }
        },
        [ref]
    )

    React.useEffect(() => {
        if (enabled) {
            return subscribe(id, onChange)
        }
    }, [id, enabled, subscribe, onChange])

    React.useEffect(() => {
        if (ref.current) {
            const current = ref.current
            let animationFrame: number | null = null
            const onScroll = (e: Event) => {
                if (animationFrame !== null) {
                    window.cancelAnimationFrame(animationFrame)
                    animationFrame = null
                }
                const target = e.target as HTMLElement
                animationFrame = window.requestAnimationFrame(() => {
                    update(id, {
                        scrollLeft: target.scrollLeft,
                        scrollTop: target.scrollTop,
                    })
                })
            }
            current.addEventListener('scroll', onScroll, false)
            return () => {
                if (animationFrame) {
                    window.cancelAnimationFrame(animationFrame)
                    animationFrame = null
                }
                current.removeEventListener('scroll', onScroll, false)
            }
        }
    }, [ref, id, update])
}

export const useHoverSync = (rowId: GridRowId) => {
    const id = useId()

    const [isHovered, setIsHovered] = React.useState(false)

    const { enabled, hover } = useContext(GridSyncContext)
    const { subscribe, update } = hover

    const onMouseEnter = React.useCallback(() => {
        setIsHovered(true)
        update(id, rowId)
    }, [update, id, rowId])

    const onMouseLeave = React.useCallback(() => {
        setIsHovered(false)
        update(id, null)
    }, [update, id])

    const onChange = React.useCallback(
        (hoveredRowId: GridRowId | null) => {
            setIsHovered(hoveredRowId === rowId)
        },
        [rowId]
    )

    React.useEffect(() => {
        if (enabled) {
            return subscribe(id, onChange)
        }
    }, [id, enabled, subscribe, onChange])

    return { isHovered, onMouseEnter, onMouseLeave }
}
