"use client";

import {
  useEffect,
  useRef,
  useState,
  type TouchEvent as ReactTouchEvent,
} from "react";

export interface PullToRefreshOptions {
  threshold?: number;
  maxPullDistance?: number;
  disabled?: boolean;
  refreshing?: boolean;
}

export interface PullToRefreshTouchHandlers {
  onTouchStart: (event: ReactTouchEvent<HTMLDivElement>) => void;
  onTouchMove: (event: ReactTouchEvent<HTMLDivElement>) => void;
  onTouchEnd: (event: ReactTouchEvent<HTMLDivElement>) => void;
  onTouchCancel: () => void;
}

export interface PullToRefreshState {
  isRefreshing: boolean;
  isPulling: boolean;
  isReleaseReady: boolean;
  pullDistance: number;
  touchHandlers: PullToRefreshTouchHandlers;
}

export function usePullToRefresh(
  onRefresh: (container: HTMLDivElement) => Promise<void> | void,
  options: PullToRefreshOptions = {},
): PullToRefreshState {
  const {
    threshold = 72,
    maxPullDistance = threshold * 1.5,
    disabled = false,
    refreshing,
  } = options;
  const [internalRefreshing, setInternalRefreshing] = useState(false);
  const [isPulling, setIsPulling] = useState(false);
  const [pullDistance, setPullDistance] = useState(0);
  const startYRef = useRef<number | null>(null);
  const touchIdRef = useRef<number | null>(null);
  const pullDistanceRef = useRef(0);
  const refreshPendingRef = useRef(false);
  const mountedRef = useRef(true);
  const isRefreshing = refreshing ?? internalRefreshing;

  useEffect(() => {
    return () => {
      mountedRef.current = false;
    };
  }, []);

  const resetGesture = () => {
    startYRef.current = null;
    touchIdRef.current = null;
    pullDistanceRef.current = 0;
    setIsPulling(false);
    setPullDistance(0);
  };

  const runRefresh = async (container: HTMLDivElement) => {
    if (refreshPendingRef.current || isRefreshing) return;
    refreshPendingRef.current = true;
    if (refreshing === undefined) setInternalRefreshing(true);
    try {
      await onRefresh(container);
    } finally {
      refreshPendingRef.current = false;
      if (refreshing === undefined && mountedRef.current) {
        setInternalRefreshing(false);
      }
    }
  };

  const onTouchStart = (event: ReactTouchEvent<HTMLDivElement>) => {
    const element = event.currentTarget;
    if (disabled || isRefreshing || element.scrollTop > 0) return;
    const touch = event.touches[0];
    if (!touch || event.touches.length !== 1) return;
    startYRef.current = touch.clientY;
    touchIdRef.current = touch.identifier;
    setIsPulling(true);
  };

  const onTouchMove = (event: ReactTouchEvent<HTMLDivElement>) => {
    if (
      disabled ||
      isRefreshing ||
      startYRef.current === null ||
      touchIdRef.current === null
    ) {
      return;
    }
    const touch = Array.from(event.touches).find(
      (item) => item.identifier === touchIdRef.current,
    );
    if (!touch) return;
    const delta = touch.clientY - startYRef.current;
    if (delta <= 0 || event.currentTarget.scrollTop > 0) {
      pullDistanceRef.current = 0;
      setPullDistance(0);
      return;
    }

    event.preventDefault();
    const nextDistance = Math.min(maxPullDistance, delta * 0.55);
    pullDistanceRef.current = nextDistance;
    setPullDistance(nextDistance);
  };

  const onTouchEnd = (event: ReactTouchEvent<HTMLDivElement>) => {
    if (startYRef.current === null) return;
    const shouldRefresh =
      !disabled &&
      !isRefreshing &&
      pullDistanceRef.current >= threshold;
    resetGesture();
    if (shouldRefresh) void runRefresh(event.currentTarget);
  };

  const effectivePullDistance = disabled || isRefreshing ? 0 : pullDistance;
  return {
    isRefreshing,
    isPulling: !disabled && !isRefreshing && isPulling,
    isReleaseReady: effectivePullDistance >= threshold,
    pullDistance: effectivePullDistance,
    touchHandlers: {
      onTouchStart,
      onTouchMove,
      onTouchEnd,
      onTouchCancel: resetGesture,
    },
  };
}
