"use client";

import { type RefObject, useEffect, useRef, useState } from "react";

type UseInViewOptions = {
  /** Visible fraction of the element that counts as "in view". */
  threshold?: number;
  /**
   * Stop at the first intersection. For one-shot reveals: the observer is
   * disconnected on the way in, so nothing keeps running afterwards.
   */
  once?: boolean;
};

/**
 * Tracks whether the returned ref's element intersects the viewport.
 *
 * The scroll *reveal* of `docs/brand.md` no longer goes through JavaScript at
 * all — `.reveal` in `globals.css` rides an `animation-timeline: view()`. What
 * is left are the three cases CSS cannot express, and they used to carry three
 * hand-rolled copies of the same effect: a count-up that must be driven frame
 * by frame (`CountUp`), an autoplay that pauses off-screen (`ui/Crossfade`)
 * and an opacity flourish that never replays (`ExploredBadge`).
 *
 * One `IntersectionObserver` per hook instance, disconnected on unmount — and
 * immediately on the first hit when `once` is set.
 */
export function useInView<T extends Element>({ threshold, once = false }: UseInViewOptions = {}): [
  RefObject<T | null>,
  boolean,
] {
  const ref = useRef<T>(null);
  const [inView, setInView] = useState(false);

  useEffect(() => {
    const el = ref.current;
    if (!el) {
      return;
    }
    const observer = new IntersectionObserver(
      ([entry]) => {
        const visible = entry?.isIntersecting ?? false;
        if (visible && once) {
          setInView(true);
          observer.disconnect();
          return;
        }
        setInView(visible);
      },
      { threshold },
    );
    observer.observe(el);
    return () => observer.disconnect();
  }, [threshold, once]);

  return [ref, inView];
}
