import { useCallback, useLayoutEffect, useRef, useState, type DependencyList, type UIEvent } from "react";

export const SCROLL_FOLLOW_BOTTOM_THRESHOLD_PX = 32;

export interface ScrollFollowMetrics {
  scrollTop: number;
  scrollHeight: number;
  clientHeight: number;
}

export function isScrollNearBottom(metrics: ScrollFollowMetrics, thresholdPx = SCROLL_FOLLOW_BOTTOM_THRESHOLD_PX): boolean {
  return metrics.scrollHeight - metrics.clientHeight - metrics.scrollTop <= thresholdPx;
}

export function scrollFollowStateFromMetrics(metrics: ScrollFollowMetrics, thresholdPx = SCROLL_FOLLOW_BOTTOM_THRESHOLD_PX): boolean {
  return isScrollNearBottom(metrics, thresholdPx);
}

export function scrollMetricsFromElement(node: HTMLElement): ScrollFollowMetrics {
  return {
    scrollTop: node.scrollTop,
    scrollHeight: node.scrollHeight,
    clientHeight: node.clientHeight
  };
}

export function scrollElementToBottom(node: HTMLElement): void {
  node.scrollTop = node.scrollHeight;
}

export function useScrollFollow<T extends HTMLElement>(deps: DependencyList, thresholdPx = SCROLL_FOLLOW_BOTTOM_THRESHOLD_PX) {
  const ref = useRef<T | null>(null);
  const [following, setFollowingState] = useState(true);
  const followingRef = useRef(true);

  const setFollowing = useCallback((next: boolean) => {
    followingRef.current = next;
    setFollowingState(next);
  }, []);

  const onScroll = useCallback((event?: UIEvent<T>) => {
    const node = event?.currentTarget ?? ref.current;
    if (!node) return;
    setFollowing(scrollFollowStateFromMetrics(scrollMetricsFromElement(node), thresholdPx));
  }, [setFollowing, thresholdPx]);

  const scrollToBottom = useCallback(() => {
    if (!ref.current) return;
    scrollElementToBottom(ref.current);
    setFollowing(true);
  }, [setFollowing]);

  useLayoutEffect(() => {
    if (!followingRef.current || !ref.current) return;
    scrollElementToBottom(ref.current);
  }, deps);

  return { ref, following, onScroll, scrollToBottom };
}
