import { useCallback, useEffect, useMemo, useRef, useState } from "react"

export default function useScrollSpy(
  sectionIds: string[],
  options: IntersectionObserverInit = {}
): {
  activeSectionId: string
  pauseScrollSpy: () => void
} {
  const [activeSectionId, setActiveSectionId] = useState("")
  const observerIsInactive = useRef(false)

  const mergedOptions = useMemo(
    () => ({
      root: null,
      // Make sure only to activate the section when it reaches the top of the viewport.
      // A negative value for rootMargin shrinks the root bounding box inward,
      // meaning elements will only be considered intersecting when they are further inside the viewport.
      rootMargin: "-8% 0% -90% 0%",
      threshold: 0,
      ...options,
    }),
    [options]
  )

  const handleIntersection = useCallback(
    (entries: IntersectionObserverEntry[]) => {
      if (observerIsInactive.current) {
        return
      }

      const intersectingEntries: IntersectionObserverEntry[] = []
      entries.forEach((e) => {
        if (e.isIntersecting) {
          intersectingEntries.push(e)
        }
      })
      if (intersectingEntries.length) {
        setActiveSectionId(intersectingEntries[0].target.id)
      }
    },
    []
  )

  useEffect(() => {
    const observer = new IntersectionObserver(handleIntersection, mergedOptions)
    const elements = sectionIds
      .map((id) => document.getElementById(id))
      .filter((el): el is HTMLElement => !!el)

    elements.forEach((element) => {
      observer.observe(element)
    })

    return () => elements.forEach((el) => el && observer.unobserve(el))
  }, [sectionIds, mergedOptions, handleIntersection])

  const pauseScrollSpy = () => {
    observerIsInactive.current = true

    setTimeout(() => {
      observerIsInactive.current = false
    }, 500)
  }

  return { activeSectionId, pauseScrollSpy }
}
