"use client";

import { useCallback, useEffect, useRef, useState } from "react";

/**
 * Carousel hook using a continuous track position model.
 *
 * `getSlideOffset(i)` returns a fractional position for each slide:
 *   0 = centered (active), -1 = previous, +1 = next
 *
 * During drag the offset moves continuously. On release it snaps to the
 * nearest integer index. Wraps infinitely.
 */
export function useCarousel<T>({ items, autoPlayMs = 5000 }: { items: T[]; autoPlayMs?: number }) {
  const total = items.length;
  const [activeIndex, setActiveIndex] = useState(0);
  const [dragOffset, setDragOffset] = useState(0);
  const [isDragging, setIsDragging] = useState(false);

  // Ref mirror so callbacks don't re-create on every index change
  // Track whether last interaction was a drag to prevent link clicks
  const wasDraggingRef = useRef(false);
  const stateRef = useRef({ activeIndex, dragOffset, total });
  stateRef.current = { activeIndex, dragOffset, total };

  // --- Auto-play pause ---
  const pausedRef = useRef(false);
  const pauseTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);

  const pauseAutoPlay = useCallback(() => {
    pausedRef.current = true;
    clearTimeout(pauseTimerRef.current);
    pauseTimerRef.current = setTimeout(() => {
      pausedRef.current = false;
    }, autoPlayMs * 2);
  }, [autoPlayMs]);

  useEffect(() => () => clearTimeout(pauseTimerRef.current), []);

  // --- Navigation ---
  const goTo = useCallback(
    (i: number) => {
      pauseAutoPlay();
      setActiveIndex(i);
      setDragOffset(0);
    },
    [pauseAutoPlay]
  );

  const goNext = useCallback(() => {
    pauseAutoPlay();
    setActiveIndex(prev => (prev + 1) % stateRef.current.total);
    setDragOffset(0);
  }, [pauseAutoPlay]);

  const goPrev = useCallback(() => {
    pauseAutoPlay();
    setActiveIndex(prev => (prev - 1 + stateRef.current.total) % stateRef.current.total);
    setDragOffset(0);
  }, [pauseAutoPlay]);

  const reset = useCallback(() => {
    setActiveIndex(0);
    setDragOffset(0);
  }, []);

  // --- Drag logic ---
  const startXRef = useRef(0);
  const containerWidthRef = useRef(0);
  const draggingRef = useRef(false);

  const startDrag = useCallback(
    (clientX: number, container: HTMLElement) => {
      startXRef.current = clientX;
      containerWidthRef.current = container.getBoundingClientRect().width;
      draggingRef.current = true;
      wasDraggingRef.current = false;
      setIsDragging(true);
      setDragOffset(0);
      pauseAutoPlay();
    },
    [pauseAutoPlay]
  );

  const moveDrag = useCallback((clientX: number) => {
    if (!draggingRef.current) return;
    const width = containerWidthRef.current || 1;
    const delta = clientX - startXRef.current;
    // Mark as a real drag once moved more than 5px (not just a click)
    if (Math.abs(delta) > 5) wasDraggingRef.current = true;
    // Positive offset = dragged right = reveal previous
    // Negative offset = dragged left = reveal next
    setDragOffset(delta / width);
  }, []);

  const endDrag = useCallback(() => {
    if (!draggingRef.current) return;
    draggingRef.current = false;
    setIsDragging(false);

    const { dragOffset: currentOffset, activeIndex: currentIndex, total: n } = stateRef.current;

    if (currentOffset < -0.15) {
      // Dragged left → go next
      setActiveIndex((currentIndex + 1) % n);
    } else if (currentOffset > 0.15) {
      // Dragged right → go prev
      setActiveIndex((currentIndex - 1 + n) % n);
    }
    setDragOffset(0);
  }, []);

  // --- Touch handlers ---
  const onTouchStart = useCallback(
    (e: React.TouchEvent) => {
      const t = e.touches[0];
      if (t) startDrag(t.clientX, e.currentTarget as HTMLElement);
    },
    [startDrag]
  );

  const onTouchMove = useCallback(
    (e: React.TouchEvent) => {
      const t = e.touches[0];
      if (t) moveDrag(t.clientX);
    },
    [moveDrag]
  );

  const onTouchEnd = useCallback(() => endDrag(), [endDrag]);

  // --- Mouse handlers ---
  const onMouseDown = useCallback(
    (e: React.MouseEvent) => {
      e.preventDefault();
      startDrag(e.clientX, e.currentTarget as HTMLElement);
    },
    [startDrag]
  );

  const onMouseMove = useCallback((e: React.MouseEvent) => moveDrag(e.clientX), [moveDrag]);

  const onMouseUp = useCallback(() => endDrag(), [endDrag]);
  const onMouseLeave = useCallback(() => endDrag(), [endDrag]);

  // --- Keyboard ---
  const onKeyDown = useCallback(
    (e: React.KeyboardEvent) => {
      if (e.key === "ArrowLeft") goPrev();
      else if (e.key === "ArrowRight") goNext();
    },
    [goPrev, goNext]
  );

  // --- Auto-play ---
  useEffect(() => {
    if (total <= 1) return;
    const id = setInterval(() => {
      if (!pausedRef.current) {
        setActiveIndex(prev => (prev + 1) % total);
        setDragOffset(0);
      }
    }, autoPlayMs);
    return () => clearInterval(id);
  }, [total, autoPlayMs]);

  /**
   * Get the visual offset for slide `i`.
   * Returns: 0 = centered, -1 = left neighbor, +1 = right neighbor.
   * Incorporates drag offset for real-time movement.
   */
  const getSlideOffset = useCallback(
    (i: number) => {
      let diff = i - activeIndex;
      // Shortest path wrapping — use half = floor(total/2) so both
      // directions get equal neighbor count
      const half = Math.floor(total / 2);
      if (diff > half) diff -= total;
      if (diff < -half) diff += total;
      // dragOffset is positive when dragging right (revealing prev)
      // so slide positions shift right: diff + dragOffset
      return diff + dragOffset;
    },
    [activeIndex, dragOffset, total]
  );

  /** True if the last pointer interaction was a drag (not a tap/click) */
  const wasDragging = useCallback(() => wasDraggingRef.current, []);

  return {
    items,
    total,
    activeIndex,
    isDragging,
    wasDragging,
    getSlideOffset,
    goTo,
    goNext,
    goPrev,
    reset,
    onTouchStart,
    onTouchMove,
    onTouchEnd,
    onMouseDown,
    onMouseMove,
    onMouseUp,
    onMouseLeave,
    onKeyDown,
  };
}

/** @deprecated Use `useCarousel` instead */
export const useInfiniteCarousel = useCarousel;
