import { useCallback, useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react';
import './pull-to-refresh.css';

const PULL_THRESHOLD = 64;
const MAX_PULL = 96;

type PullToRefreshProps = {
  onRefresh: () => void | Promise<void>;
  children: ReactNode;
  className?: string;
  style?: CSSProperties;
  disabled?: boolean;
};

function getScrollTop() {
  return window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0;
}

export default function PullToRefresh({
  onRefresh,
  children,
  className,
  style,
  disabled = false,
}: PullToRefreshProps) {
  const rootRef = useRef<HTMLDivElement>(null);
  const [pull, setPull] = useState(0);
  const [refreshing, setRefreshing] = useState(false);
  const startY = useRef(0);
  const pulling = useRef(false);
  const busy = useRef(false);
  const pullRef = useRef(0);
  const disabledRef = useRef(disabled);
  const onRefreshRef = useRef(onRefresh);

  disabledRef.current = disabled;
  onRefreshRef.current = onRefresh;
  pullRef.current = pull;

  const finishRefresh = useCallback(async () => {
    if (busy.current) return;
    busy.current = true;
    setRefreshing(true);
    setPull(40);
    pullRef.current = 40;
    try {
      await onRefreshRef.current();
    } finally {
      setRefreshing(false);
      setPull(0);
      pullRef.current = 0;
      busy.current = false;
      pulling.current = false;
    }
  }, []);

  useEffect(() => {
    const el = rootRef.current;
    if (!el) return;

    const onTouchStart = (e: TouchEvent) => {
      if (disabledRef.current || busy.current) return;
      if (getScrollTop() > 2) return;
      startY.current = e.touches[0]?.clientY ?? 0;
      pulling.current = true;
    };

    const onTouchMove = (e: TouchEvent) => {
      if (!pulling.current || disabledRef.current || busy.current) return;
      if (getScrollTop() > 2) {
        pulling.current = false;
        setPull(0);
        pullRef.current = 0;
        return;
      }
      const y = e.touches[0]?.clientY ?? 0;
      const delta = y - startY.current;
      if (delta <= 0) {
        setPull(0);
        pullRef.current = 0;
        return;
      }
      const next = Math.min(MAX_PULL, delta * 0.45);
      setPull(next);
      pullRef.current = next;
      if (next > 8 && e.cancelable) e.preventDefault();
    };

    const onTouchEnd = () => {
      if (!pulling.current || disabledRef.current) return;
      pulling.current = false;
      if (pullRef.current >= PULL_THRESHOLD) {
        void finishRefresh();
        return;
      }
      setPull(0);
      pullRef.current = 0;
    };

    el.addEventListener('touchstart', onTouchStart, { passive: true });
    el.addEventListener('touchmove', onTouchMove, { passive: false });
    el.addEventListener('touchend', onTouchEnd);
    el.addEventListener('touchcancel', onTouchEnd);
    return () => {
      el.removeEventListener('touchstart', onTouchStart);
      el.removeEventListener('touchmove', onTouchMove);
      el.removeEventListener('touchend', onTouchEnd);
      el.removeEventListener('touchcancel', onTouchEnd);
    };
  }, [finishRefresh]);

  const showHint = pull > 8 || refreshing;
  const ready = pull >= PULL_THRESHOLD;
  const pad = showHint ? Math.max(pull, refreshing ? 40 : 0) : 0;

  return (
    <div
      ref={rootRef}
      className={['dukang-ptr', className].filter(Boolean).join(' ')}
      style={{ ...style, paddingTop: pad || undefined }}
    >
      <div
        className={`dukang-ptr-indicator${showHint ? ' dukang-ptr-indicator--visible' : ''}`}
        style={{ height: pad }}
        aria-live="polite"
      >
        <span className="dukang-ptr-text">
          {refreshing ? '刷新中…' : ready ? '松开刷新' : '下拉刷新'}
        </span>
      </div>
      {children}
    </div>
  );
}
