"use client";

import {
  useEffect,
  useId,
  useRef,
  useState,
  useSyncExternalStore,
  type ReactNode,
} from "react";
import { flushSync } from "react-dom";
const queries = ["(max-width: 766px)", "(max-width: 1023px)"];
const subscribe = (callback: () => void) => {
  const lists = queries.map((query) => window.matchMedia(query));
  lists.forEach((list) => list.addEventListener("change", callback));
  return () =>
    lists.forEach((list) => list.removeEventListener("change", callback));
};
const visibleCount = () =>
  window.matchMedia(queries[0]).matches
    ? 1
    : window.matchMedia(queries[1]).matches
      ? 2
      : 3;
const wrap = (index: number, count: number) =>
  ((index % count) + count) % count;
const trailing = 3;

export function Carousel({
  children,
  count,
  label,
  autoplayOffset = 0,
}: {
  children: ReactNode[];
  count: number;
  label: string;
  autoplayOffset?: number;
}) {
  const visible = useSyncExternalStore(subscribe, visibleCount, () => 3);
  const [position, setPosition] = useState({ index: 0, animate: true });
  const [layout, setLayout] = useState(visible);
  const [paused, setPaused] = useState(false);
  const gesture = useRef<{ x: number; y: number } | null>(null);
  const suppressClickUntil = useRef(0);
  const track = useRef<HTMLDivElement>(null);
  const trackId = useId();
  const span = Math.max(3, Math.ceil(count / 2)),
    leading = span;
  if (layout !== visible) {
    setLayout(visible);
    setPosition((p) => ({ index: wrap(p.index, count), animate: false }));
  }
  useEffect(() => {
    if (position.animate) return;
    let frame = requestAnimationFrame(() => {
      frame = requestAnimationFrame(() =>
        setPosition((p) => ({ ...p, animate: true })),
      );
    });
    return () => cancelAnimationFrame(frame);
  }, [position]);
  useEffect(() => {
    if (paused) return;
    const media = window.matchMedia("(prefers-reduced-motion: reduce)");
    const advance = () => {
      if (!media.matches && !document.hidden)
        setPosition((p) =>
          !p.animate
            ? p
            : p.index < count
              ? { index: p.index + 1, animate: true }
              : { index: wrap(p.index, count), animate: false },
        );
    };
    let interval: number | undefined;
    const start = window.setTimeout(() => {
      advance();
      interval = window.setInterval(advance, 5000);
    }, 5000 + autoplayOffset);
    return () => {
      window.clearTimeout(start);
      window.clearInterval(interval);
    };
  }, [count, paused, autoplayOffset]);
  function offset() {
    const node = track.current,
      slide = node?.firstElementChild;
    if (!node || !slide) return position.index;
    const style = getComputedStyle(node);
    const value =
      -new DOMMatrix(style.transform).m41 /
        (slide.getBoundingClientRect().width + parseFloat(style.columnGap)) -
      leading;
    return Number.isFinite(value)
      ? Math.round(value * 1000) / 1000
      : position.index;
  }
  function go(target: number, drawn = offset()) {
    setPaused(true);
    const lowest = -leading,
      highest = count + trailing - visible;
    const from =
      Math.abs(target - drawn) > span
        ? target - Math.sign(target - drawn) * span
        : drawn;
    const low = Math.min(from, target),
      high = Math.max(from, target);
    const shift =
      low >= lowest && high <= highest
        ? 0
        : Math.ceil((lowest - low) / count) * count;
    if (shift || from !== drawn) {
      flushSync(() => setPosition({ index: from + shift, animate: false }));
      track.current?.getBoundingClientRect();
    }
    setPosition({ index: target + shift, animate: true });
  }
  function select(slide: number) {
    const drawn = offset();
    go(slide + Math.round((drawn - slide) / count) * count, drawn);
  }
  const current = wrap(Math.round(position.index), count);
  const labelText = label.toLowerCase();
  return (
    <div
      className="carousel"
      aria-label={label}
      aria-roledescription="carousel"
      role="region"
      onFocusCapture={(event) => {
        if (!(
          event.target instanceof Element &&
          event.target.closest("[data-carousel-pause]")
        ))
          setPaused(true);
      }}
      onPointerDown={(event) => {
        if (!(
          event.target instanceof Element &&
          event.target.closest("[data-carousel-pause]")
        ))
          setPaused(true);
      }}
    >
      <button
        data-carousel-pause
        className="sr-only focus:not-sr-only"
        type="button"
        onClick={() => setPaused((value) => !value)}
      >
        {paused ? "Play" : "Pause"} {label}
      </button>
      <div
        className="carousel-window"
        onPointerDown={(event) => {
          gesture.current = { x: event.clientX, y: event.clientY };
        }}
        onPointerCancel={() => {
          gesture.current = null;
        }}
        onPointerUp={(event) => {
          const start = gesture.current;
          gesture.current = null;
          if (!start) return;
          const dx = event.clientX - start.x,
            dy = event.clientY - start.y;
          if (Math.abs(dx) > 40 && Math.abs(dx) > Math.abs(dy)) {
            suppressClickUntil.current = event.timeStamp + 500;
            go(position.index + (dx < 0 ? 1 : -1));
          }
        }}
        onClickCapture={(event) => {
          if (event.timeStamp < suppressClickUntil.current) {
            event.preventDefault();
            event.stopPropagation();
          }
        }}
        onDragStart={(event) => event.preventDefault()}
      >
        <div
          className="carousel-track"
          id={trackId}
          ref={track}
          style={{
            transform: `translateX(calc(${position.index + leading} * (-100% - var(--carousel-gap)) / var(--cards)))`,
            transition: position.animate ? undefined : "none",
          }}
          onTransitionEnd={(event) => {
            if (
              event.target === event.currentTarget &&
              position.index !== current &&
              Math.abs(offset() - position.index) < 0.01
            )
              setPosition({ index: current, animate: false });
          }}
        >
          {[
            ...Array.from({ length: count + trailing }, (_, at) => at),
            ...Array.from({ length: leading }, (_, i) => i - leading),
          ].map((at) => {
            const hidden =
              at < position.index || at >= position.index + visible;
            return (
              <div
                className="carousel-slide"
                key={at}
                style={at < 0 ? { order: -1 } : undefined}
                role="group"
                aria-roledescription="slide"
                aria-label={`${wrap(at, count) + 1} of ${count}`}
                aria-hidden={hidden}
                inert={hidden}
              >
                {children[wrap(at, count)]}
              </div>
            );
          })}
        </div>
      </div>
      <div className="carousel-controls">
        <button
          type="button"
          className="carousel-arrow"
          aria-label={`Previous ${labelText} slide`}
          aria-controls={trackId}
          onClick={() => go(position.index - 1)}
        >
          <svg aria-hidden="true" width="9" height="16" viewBox="0 0 9 16">
            <path
              d="M8 1 1 8l7 7"
              fill="none"
              stroke="currentColor"
              strokeWidth="2"
              strokeLinecap="round"
              strokeLinejoin="round"
            />
          </svg>
        </button>
        <div className="carousel-dots" aria-label={`${label} slides`}>
          {Array.from({ length: count }, (_, i) => (
            <button
              type="button"
              key={i}
              className={i === current ? "active" : ""}
              aria-label={`Show ${labelText} slide ${i + 1}`}
              aria-pressed={i === current}
              onClick={() => select(i)}
            />
          ))}
        </div>
        <button
          type="button"
          className="carousel-arrow"
          aria-label={`Next ${labelText} slide`}
          aria-controls={trackId}
          onClick={() => go(position.index + 1)}
        >
          <svg aria-hidden="true" width="9" height="16" viewBox="0 0 9 16">
            <path
              d="m1 1 7 7-7 7"
              fill="none"
              stroke="currentColor"
              strokeWidth="2"
              strokeLinecap="round"
              strokeLinejoin="round"
            />
          </svg>
        </button>
      </div>
      <p className="sr-only" aria-live="polite" aria-atomic="true">
        {paused ? `Showing ${labelText} slide ${current + 1} of ${count}` : ""}
      </p>
    </div>
  );
}
