import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import './CategoryPillCarousel.css';

function clamp(value, min, max) {
  return Math.max(min, Math.min(max, value));
}

export default function CategoryPillCarousel({
  items = [],
  ariaLabel = 'Filter by category',
  className = '',
}) {
  const viewportRef = useRef(null);
  const stripRef = useRef(null);
  const animationRef = useRef(0);
  const suppressClickRef = useRef(false);
  const dragStateRef = useRef({
    active: false,
    started: false,
    captured: false,
    pointerId: null,
    pointerType: 'mouse',
    startX: 0,
    startY: 0,
    startOffset: 0,
    startedOnLink: false,
  });

  const [offset, setOffset] = useState(0);
  const [dragging, setDragging] = useState(false);
  const [maxScroll, setMaxScroll] = useState(0);

  const activeIndex = useMemo(() => {
    const idx = items.findIndex((item) => !!item.active);
    return idx >= 0 ? idx : 0;
  }, [items]);

  const maxOffset = useCallback(() => {
    const viewport = viewportRef.current;
    const strip = stripRef.current;
    if (!viewport || !strip) return 0;
    return Math.max(0, strip.scrollWidth - viewport.clientWidth);
  }, []);

  const recalcBounds = useCallback(() => {
    const max = maxOffset();
    setMaxScroll(max);
    setOffset((prev) => clamp(prev, -max, 0));
  }, [maxOffset]);

  const moveTo = useCallback((nextOffset) => {
    const max = maxOffset();
    const clamped = clamp(nextOffset, -max, 0);
    setOffset(clamped);
  }, [maxOffset]);

  const animateTo = useCallback((targetOffset, duration = 380) => {
    if (animationRef.current) {
      cancelAnimationFrame(animationRef.current);
      animationRef.current = 0;
    }

    const max = maxOffset();
    const target = clamp(targetOffset, -max, 0);
    const start = offset;
    const delta = target - start;

    if (Math.abs(delta) < 1) {
      setOffset(target);
      return;
    }

    const startTime = performance.now();
    setDragging(false);

    const easeOutCubic = (t) => 1 - ((1 - t) ** 3);

    const step = (now) => {
      const elapsed = now - startTime;
      const progress = Math.min(1, elapsed / duration);
      const eased = easeOutCubic(progress);
      setOffset(start + (delta * eased));

      if (progress < 1) {
        animationRef.current = requestAnimationFrame(step);
      } else {
        animationRef.current = 0;
        setOffset(target);
      }
    };

    animationRef.current = requestAnimationFrame(step);
  }, [maxOffset, offset]);

  const moveToPill = useCallback((direction) => {
    const strip = stripRef.current;
    if (!strip) return;

    const pills = Array.from(strip.querySelectorAll('.nb-react-pill'));
    if (!pills.length) return;

    const viewLeft = -offset;
    if (direction > 0) {
      const next = pills.find((pill) => pill.offsetLeft > viewLeft + 6);
      if (next) animateTo(-next.offsetLeft);
      else animateTo(-maxOffset());
      return;
    }

    for (let i = pills.length - 1; i >= 0; i -= 1) {
      const left = pills[i].offsetLeft;
      if (left < viewLeft - 6) {
        animateTo(-left);
        return;
      }
    }
    animateTo(0);
  }, [animateTo, maxOffset, offset]);

  useEffect(() => {
    const viewport = viewportRef.current;
    const strip = stripRef.current;
    if (!viewport || !strip) return;

    const activeEl = strip.querySelector('[data-active-pill="true"]');
    if (!activeEl) {
      moveTo(0);
      return;
    }

    const centered = -(activeEl.offsetLeft - (viewport.clientWidth / 2) + (activeEl.offsetWidth / 2));
    moveTo(centered);
    recalcBounds();
  }, [activeIndex, items, moveTo, recalcBounds]);

  useEffect(() => {
    const viewport = viewportRef.current;
    const strip = stripRef.current;
    if (!viewport || !strip) return;

    const measure = () => recalcBounds();

    const rafId = requestAnimationFrame(measure);
    window.addEventListener('resize', measure, { passive: true });

    let ro = null;
    if ('ResizeObserver' in window) {
      ro = new ResizeObserver(measure);
      ro.observe(viewport);
      ro.observe(strip);
    }

    return () => {
      cancelAnimationFrame(rafId);
      window.removeEventListener('resize', measure);
      if (ro) ro.disconnect();
    };
  }, [items, recalcBounds]);

  useEffect(() => {
    const strip = stripRef.current;
    if (!strip) return;

    const onPointerDown = (event) => {
      if (event.pointerType === 'mouse' && event.button !== 0) return;

      if (animationRef.current) {
        cancelAnimationFrame(animationRef.current);
        animationRef.current = 0;
      }

      dragStateRef.current.active = true;
      dragStateRef.current.started = false;
      dragStateRef.current.captured = false;
      dragStateRef.current.pointerId = event.pointerId;
      dragStateRef.current.pointerType = event.pointerType || 'mouse';
      dragStateRef.current.startX = event.clientX;
      dragStateRef.current.startY = event.clientY;
      dragStateRef.current.startOffset = offset;
      dragStateRef.current.startedOnLink = !!event.target.closest('.nb-react-pill');

      setDragging(false);
    };

    const onPointerMove = (event) => {
      const state = dragStateRef.current;
      if (!state.active || state.pointerId !== event.pointerId) return;

      const dx = event.clientX - state.startX;
      const dy = event.clientY - state.startY;
      const threshold = state.pointerType === 'touch'
        ? 12
        : (state.startedOnLink ? 24 : 12);
      if (!state.started) {
        if (Math.abs(dx) <= threshold || Math.abs(dx) <= Math.abs(dy)) {
          return;
        }

        state.started = true;
        if (!state.captured && strip.setPointerCapture) {
          try {
            strip.setPointerCapture(event.pointerId);
            state.captured = true;
          } catch (_) {
            state.captured = false;
          }
        }
        setDragging(true);
      }

      if (state.started) {
        event.preventDefault();
      }

      moveTo(state.startOffset + dx);
    };

    const onPointerUpOrCancel = (event) => {
      const state = dragStateRef.current;
      if (!state.active || state.pointerId !== event.pointerId) return;

      suppressClickRef.current = state.started;
      state.active = false;
      state.started = false;
      state.startedOnLink = false;
      state.pointerId = null;
      setDragging(false);

      if (state.captured && strip.releasePointerCapture) {
        try { strip.releasePointerCapture(event.pointerId); } catch (_) { /* no-op */ }
      }
      state.captured = false;
    };

    const onClickCapture = (event) => {
      if (!suppressClickRef.current) return;
      event.preventDefault();
      event.stopPropagation();
      suppressClickRef.current = false;
    };

    strip.addEventListener('pointerdown', onPointerDown);
    strip.addEventListener('pointermove', onPointerMove);
    strip.addEventListener('pointerup', onPointerUpOrCancel);
    strip.addEventListener('pointercancel', onPointerUpOrCancel);
    strip.addEventListener('click', onClickCapture, true);

    return () => {
      strip.removeEventListener('pointerdown', onPointerDown);
      strip.removeEventListener('pointermove', onPointerMove);
      strip.removeEventListener('pointerup', onPointerUpOrCancel);
      strip.removeEventListener('pointercancel', onPointerUpOrCancel);
      strip.removeEventListener('click', onClickCapture, true);
    };
  }, [moveTo, offset]);

  useEffect(() => () => {
    if (animationRef.current) {
      cancelAnimationFrame(animationRef.current);
      animationRef.current = 0;
    }
  }, []);

  const max = maxScroll;
  const atStart = offset >= -2;
  const atEnd = offset <= -(max - 2);

  return (
    <div className={`nb-react-carousel ${atStart ? 'at-start' : ''} ${atEnd ? 'at-end' : ''} ${className}`.trim()}>
      <div className="nb-react-fade nb-react-fade--left" aria-hidden="true" />
      <button
        type="button"
        className="nb-react-arrow nb-react-arrow--left"
        aria-label="Previous categories"
        onClick={() => moveToPill(-1)}
      >
        <svg viewBox="0 0 20 20" fill="currentColor" className="w-[18px] h-[18px]" aria-hidden="true"><path fillRule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clipRule="evenodd"/></svg>
      </button>

      <div className="nb-react-viewport" ref={viewportRef} role="list" aria-label={ariaLabel}>
        <div
          ref={stripRef}
          className={`nb-react-strip ${dragging ? 'is-dragging' : ''}`}
          style={{ transform: `translateX(${offset}px)` }}
        >
          {items.map((item) => (
            <a
              key={`${item.href}-${item.label}`}
              href={item.href}
              className={`nb-react-pill ${item.active ? 'nb-react-pill--active' : ''}`}
              aria-current={item.active ? 'page' : 'false'}
              data-active-pill={item.active ? 'true' : undefined}
              draggable={false}
              onDragStart={(event) => event.preventDefault()}
            >
              {item.label}
            </a>
          ))}
        </div>
      </div>

      <div className="nb-react-fade nb-react-fade--right" aria-hidden="true" />
      <button
        type="button"
        className="nb-react-arrow nb-react-arrow--right"
        aria-label="Next categories"
        onClick={() => moveToPill(1)}
      >
        <svg viewBox="0 0 20 20" fill="currentColor" className="w-[18px] h-[18px]" aria-hidden="true"><path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd"/></svg>
      </button>
    </div>
  );
}
