// Shared carousel behavior: N full cards visible, the next one peeking at the edge.
// Tracks scroll position + which card is the first/last visible so callers can wire up
// nav buttons and "click the edge card to advance" on top of a horizontally snapping track.
//
// Mouse click-and-drag is opt-in: spread `dragHandlers` onto the scrolling track to get
// direct drag-to-scroll with velocity-aware snap-to-card on release. Touch/trackpad panning
// is untouched — it already works natively via the track's own overflow-x scrolling, and drag
// handling here explicitly ignores non-mouse pointers.
function useCardCarousel(itemCount) {
  const trackRef = React.useRef(null);
  const cardRefs = React.useRef([]);
  const [atStart, setAtStart] = React.useState(true);
  const [atEnd, setAtEnd] = React.useState(true);
  const [edge, setEdge] = React.useState({ first: 0, last: 0 });
  const [isDragging, setIsDragging] = React.useState(false);
  const dragRef = React.useRef(null);

  const updateEdges = () => {
    const el = trackRef.current;
    if (!el) return;
    setAtStart(el.scrollLeft <= 1);
    setAtEnd(el.scrollLeft + el.clientWidth >= el.scrollWidth - 1);

    const trackRect = el.getBoundingClientRect();
    const cards = cardRefs.current;
    let first = 0;
    for (let i = 0; i < cards.length; i++) {
      if (cards[i] && cards[i].getBoundingClientRect().right > trackRect.left + 1) { first = i; break; }
    }
    let last = cards.length - 1;
    for (let i = cards.length - 1; i >= 0; i--) {
      if (cards[i] && cards[i].getBoundingClientRect().left < trackRect.right - 1) { last = i; break; }
    }
    setEdge({ first, last });
  };

  React.useLayoutEffect(() => {
    updateEdges();
    window.addEventListener("resize", updateEdges);
    return () => window.removeEventListener("resize", updateEdges);
  }, [itemCount]);

  const getStep = () => {
    const el = trackRef.current;
    const card = cardRefs.current[0];
    if (!el || !card) return 0;
    const gap = parseFloat(getComputedStyle(el).columnGap || "24") || 24;
    return card.getBoundingClientRect().width + gap;
  };

  const scrollByCard = (dir) => {
    const el = trackRef.current;
    const step = getStep();
    if (!el || !step) return;
    el.scrollBy({ left: step * dir, behavior: "smooth" });
  };

  const snapToIndex = (index) => {
    const el = trackRef.current;
    const step = getStep();
    if (!el || !step) return;
    const target = Math.max(0, Math.min(itemCount - 1, index));
    el.scrollTo({ left: target * step, behavior: "smooth" });
  };

  // Movement past this many px counts as a drag, not a click — lets "Read more" and
  // the edge-card advance-on-click still fire on a plain click.
  const DRAG_CLICK_THRESHOLD = 6;
  // Fast flicks (px of scroll per ms) advance/retreat a card even if released early.
  const FLICK_VELOCITY = 0.5;

  // A drag that crossed the threshold ends with a real pointerup, which the browser
  // would otherwise turn into a click on whatever's under the cursor (e.g. "Read more").
  // Swallow exactly the next click, once, at the capture phase so it never reaches the target.
  const suppressNextClick = () => {
    const el = trackRef.current;
    if (!el) return;
    el.addEventListener(
      "click",
      (e) => { e.preventDefault(); e.stopPropagation(); },
      { capture: true, once: true }
    );
  };

  const onPointerDown = (e) => {
    if (e.pointerType !== "mouse" || e.button !== 0) return;
    const el = trackRef.current;
    if (!el) return;
    dragRef.current = {
      pointerId: e.pointerId,
      dragging: false,
      startX: e.clientX,
      startScrollLeft: el.scrollLeft,
      velocity: 0,
      lastTime: e.timeStamp,
    };
  };

  const onPointerMove = (e) => {
    const state = dragRef.current;
    const el = trackRef.current;
    if (!state || !el || state.pointerId !== e.pointerId) return;
    const dx = e.clientX - state.startX;
    if (!state.dragging) {
      if (Math.abs(dx) < DRAG_CLICK_THRESHOLD) return;
      state.dragging = true;
      setIsDragging(true);
      el.style.scrollSnapType = "none";
      el.setPointerCapture(e.pointerId);
    }
    e.preventDefault();
    const nextScrollLeft = state.startScrollLeft - dx;
    const dt = e.timeStamp - state.lastTime;
    if (dt > 0) state.velocity = (nextScrollLeft - el.scrollLeft) / dt;
    el.scrollLeft = nextScrollLeft;
    state.lastTime = e.timeStamp;
  };

  const endDrag = (e) => {
    const state = dragRef.current;
    const el = trackRef.current;
    dragRef.current = null;
    if (!state || !el) return;
    if (el.hasPointerCapture && el.hasPointerCapture(state.pointerId)) {
      el.releasePointerCapture(state.pointerId);
    }
    el.style.scrollSnapType = "";
    if (!state.dragging) return;
    setIsDragging(false);
    suppressNextClick();

    const step = getStep();
    if (!step) return;
    const startIndex = Math.round(state.startScrollLeft / step);
    let targetIndex;
    if (state.velocity >= FLICK_VELOCITY) targetIndex = startIndex + 1;
    else if (state.velocity <= -FLICK_VELOCITY) targetIndex = startIndex - 1;
    else targetIndex = Math.round(el.scrollLeft / step);
    snapToIndex(targetIndex);
  };

  const dragHandlers = {
    onPointerDown,
    onPointerMove,
    onPointerUp: endDrag,
    onPointerCancel: endDrag,
  };

  const setCardRef = (i) => (el) => { cardRefs.current[i] = el; };

  return {
    trackRef, setCardRef, atStart, atEnd, edge, updateEdges, scrollByCard,
    scrollable: !(atStart && atEnd),
    dragHandlers, isDragging,
  };
}

window.useCardCarousel = useCardCarousel;
