"use client";

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

interface PullToRefreshProps {
  children: ReactNode;
}

const THRESHOLD = 80;        // px of pull distance to trigger refresh
const MAX_PULL = 140;        // px before rubberbanding clamps
const REFRESH_INDICATOR_HEIGHT = 56;

/**
 * Custom pull-to-refresh — no third-party library.
 *
 * Only engages when the page is scrolled to the top (`window.scrollY === 0`).
 * Uses rubberband resistance so the pull feels heavy past a comfortable
 * range, and clamps the visible offset at MAX_PULL. The release animation
 * snaps back to 0 (or to REFRESH_INDICATOR_HEIGHT if a refresh is in
 * flight). Calls `router.refresh()` so the server component re-renders
 * with fresh data.
 *
 * Respects `prefers-reduced-motion: reduce` — the transition is disabled
 * and the offset still updates, but the snap-back doesn't animate. The
 * indicator spinner animation is also dropped under reduced motion.
 */
export function PullToRefresh({ children }: PullToRefreshProps) {
  const router = useRouter();
  const startY = useRef<number | null>(null);
  const pulling = useRef(false);
  const [offset, setOffset] = useState(0);
  const [refreshing, setRefreshing] = useState(false);
  const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);

  useEffect(() => {
    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
    setPrefersReducedMotion(mq.matches);
    const handler = (e: MediaQueryListEvent) => setPrefersReducedMotion(e.matches);
    mq.addEventListener("change", handler);
    return () => mq.removeEventListener("change", handler);
  }, []);

  function onTouchStart(e: React.TouchEvent<HTMLDivElement>) {
    if (refreshing) return;
    // Only start a pull if the page is scrolled to the top — pulling
    // mid-scroll would hijack normal scroll.
    if (window.scrollY > 0) return;
    startY.current = e.touches[0].clientY;
    pulling.current = true;
  }

  function onTouchMove(e: React.TouchEvent<HTMLDivElement>) {
    if (!pulling.current || startY.current === null) return;
    const dy = e.touches[0].clientY - startY.current;
    if (dy <= 0) {
      setOffset(0);
      return;
    }
    // Rubberband: resistance curve so it gets harder to pull further.
    const resistance = 1 - Math.min(1, dy / 400);
    const next = Math.min(MAX_PULL, dy * resistance);
    setOffset(next);
  }

  async function onTouchEnd() {
    if (!pulling.current) return;
    pulling.current = false;
    startY.current = null;
    if (offset >= THRESHOLD) {
      setRefreshing(true);
      setOffset(REFRESH_INDICATOR_HEIGHT);
      router.refresh();
      // Give the refresh a moment to complete visually. The actual data
      // fetch happens on the server; this is just the "spinner shown
      // for a beat" feel.
      await new Promise((r) => setTimeout(r, 600));
      setRefreshing(false);
      setOffset(0);
    } else {
      setOffset(0);
    }
  }

  return (
    <div
      onTouchStart={onTouchStart}
      onTouchMove={onTouchMove}
      onTouchEnd={onTouchEnd}
      onTouchCancel={onTouchEnd}
      style={{
        transform: `translateY(${offset}px)`,
        transition: pulling.current || 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;
