"use client";

import { ReactNode, useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
import { useRouter } from "next/navigation";

interface PullToRefreshProps {
  children: ReactNode;
}

const THRESHOLD = 80;
const MAX_PULL = 140;
const REFRESH_INDICATOR_HEIGHT = 56;

function subscribeReducedMotion(callback: () => void): () => void {
  const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
  mq.addEventListener("change", callback);
  return () => mq.removeEventListener("change", callback);
}

function getReducedMotionSnapshot(): boolean {
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}

function getReducedMotionServerSnapshot(): boolean {
  return false;
}

export function PullToRefresh({ children }: PullToRefreshProps) {
  const router = useRouter();
  const startY = useRef<number | null>(null);
  const [isPulling, setIsPulling] = useState(false);
  const [offset, setOffset] = useState(0);
  const [refreshing, setRefreshing] = useState(false);
  const prefersReducedMotion = useSyncExternalStore(
    subscribeReducedMotion,
    getReducedMotionSnapshot,
    getReducedMotionServerSnapshot,
  );

  // Reset pulling state after touch release so the no-transition snap-back works.
  useEffect(() => {
    if (!isPulling && offset === 0) return;
  }, [isPulling, offset]);

  const onTouchStart = useCallback((e: React.TouchEvent<HTMLDivElement>) => {
    if (refreshing) return;
    if (window.scrollY > 0) return;
    startY.current = e.touches[0].clientY;
    setIsPulling(true);
  }, [refreshing]);

  const onTouchMove = useCallback((e: React.TouchEvent<HTMLDivElement>) => {
    if (!isPulling || startY.current === null) return;
    const dy = e.touches[0].clientY - startY.current;
    if (dy <= 0) {
      setOffset(0);
      return;
    }
    const resistance = 1 - Math.min(1, dy / 400);
    const next = Math.min(MAX_PULL, dy * resistance);
    setOffset(next);
  }, [isPulling]);

  const onTouchEnd = useCallback(async () => {
    if (!isPulling) return;
    setIsPulling(false);
    startY.current = null;
    if (offset >= THRESHOLD) {
      setRefreshing(true);
      setOffset(REFRESH_INDICATOR_HEIGHT);
      router.refresh();
      await new Promise((r) => setTimeout(r, 600));
      setRefreshing(false);
      setOffset(0);
    } else {
      setOffset(0);
    }
  }, [isPulling, offset, router]);

  return (
    <div
      onTouchStart={onTouchStart}
      onTouchMove={onTouchMove}
      onTouchEnd={onTouchEnd}
      onTouchCancel={onTouchEnd}
      style={{
        transform: `translateY(${offset}px)`,
        transition: isPulling || prefersReducedMotion ? "none" : "transform 200ms var(--ease-apple)",
        overscrollBehavior: "contain",
        position: "relative",
      }}
    >
      {offset > 0 && (
        <div
          aria-hidden="true"
          className="flex items-center justify-center"
          style={{
            position: "absolute",
            top: 0,
            left: 0,
            right: 0,
            height: `${REFRESH_INDICATOR_HEIGHT}px`,
            transform: `translateY(-${REFRESH_INDICATOR_HEIGHT}px)`,
          }}
        >
          <div
            className="rounded-full"
            style={{
              width: "32px",
              height: "32px",
              border: "3px solid var(--color-surface-3)",
              borderTopColor: "var(--color-accent)",
              animation: refreshing && !prefersReducedMotion ? "spin 0.8s linear infinite" : "none",
            }}
          />
        </div>
      )}
      {children}
    </div>
  );
}

export default PullToRefresh;
