"use client";

import {
  Children,
  isValidElement,
  type ReactNode,
  useCallback,
  useEffect,
  useRef,
  useState,
} from "react";

const WHEEL_GESTURE_IDLE_MS = 320;
const TOUCH_MIN_DISTANCE = 8;
const AUTO_FOCUS_SELECTOR = [
  'textarea:not([disabled])',
  'input[type="text"]:not([disabled])',
  'input[type="number"]:not([disabled])',
].join(", ");

type QuestionSnapListProps = {
  children: ReactNode;
  className?: string;
  footer?: ReactNode;
  firstQuestionHint?: ReactNode;
  onQuestionExit?: (currentIndex: number, nextIndex: number) => void;
};

export function QuestionSnapList({
  children,
  className,
  footer,
  firstQuestionHint,
  onQuestionExit,
}: QuestionSnapListProps) {
  const questions = Children.toArray(children);
  const wheelLockedRef = useRef(false);
  const wheelUnlockTimeoutRef = useRef<number | null>(null);
  const touchStartYRef = useRef<number | null>(null);
  const questionRefs = useRef<Array<HTMLDivElement | null>>([]);
  const [activeIndex, setActiveIndex] = useState(0);

  const stepQuestion = useCallback(
    (direction: 1 | -1) => {
      const nextIndex = Math.max(
        0,
        Math.min(questions.length - 1, activeIndex + direction),
      );

      if (nextIndex === activeIndex) {
        return;
      }

      onQuestionExit?.(activeIndex, nextIndex);
      setActiveIndex(nextIndex);
    },
    [activeIndex, onQuestionExit, questions.length],
  );

  const scheduleWheelUnlock = useCallback(() => {
    if (wheelUnlockTimeoutRef.current !== null) {
      window.clearTimeout(wheelUnlockTimeoutRef.current);
    }

    wheelUnlockTimeoutRef.current = window.setTimeout(() => {
      wheelLockedRef.current = false;
      wheelUnlockTimeoutRef.current = null;
    }, WHEEL_GESTURE_IDLE_MS);
  }, []);

  useEffect(() => {
    const activeQuestion = questionRefs.current[activeIndex];

    if (!activeQuestion) {
      return;
    }

    const focusFrame = window.requestAnimationFrame(() => {
      const firstFocusableInput = activeQuestion.querySelector<
        HTMLInputElement | HTMLTextAreaElement
      >(AUTO_FOCUS_SELECTOR);

      if (!firstFocusableInput) {
        return;
      }

      firstFocusableInput.focus({ preventScroll: true });

      const valueLength = firstFocusableInput.value.length;

      if (valueLength > 0 && typeof firstFocusableInput.setSelectionRange === "function") {
        firstFocusableInput.setSelectionRange(valueLength, valueLength);
      }
    });

    return () => {
      window.cancelAnimationFrame(focusFrame);
    };
  }, [activeIndex]);

  useEffect(() => {
    return () => {
      if (wheelUnlockTimeoutRef.current !== null) {
        window.clearTimeout(wheelUnlockTimeoutRef.current);
      }
    };
  }, []);

  const handleWheel = useCallback(
    (event: React.WheelEvent<HTMLElement>) => {
      const delta = event.deltaY || event.deltaX;

      if (delta === 0 || questions.length < 2) {
        return;
      }

      event.preventDefault();

      if (!wheelLockedRef.current) {
        wheelLockedRef.current = true;
        stepQuestion(delta > 0 ? 1 : -1);
      }

      scheduleWheelUnlock();
    },
    [questions.length, scheduleWheelUnlock, stepQuestion],
  );

  const handleTouchStart = useCallback(
    (event: React.TouchEvent<HTMLElement>) => {
      touchStartYRef.current = event.touches[0]?.clientY ?? null;
    },
    [],
  );

  const handleTouchEnd = useCallback(
    (event: React.TouchEvent<HTMLElement>) => {
      const startY = touchStartYRef.current;
      const endY = event.changedTouches[0]?.clientY;

      touchStartYRef.current = null;

      if (startY === null || endY === undefined || questions.length < 2) {
        return;
      }

      const distance = startY - endY;

      if (Math.abs(distance) < TOUCH_MIN_DISTANCE) {
        return;
      }

      stepQuestion(distance > 0 ? 1 : -1);
    },
    [questions.length, stepQuestion],
  );

  const handleTouchMove = useCallback(
    (event: React.TouchEvent<HTMLElement>) => {
      event.preventDefault();
    },
    [],
  );

  if (questions.length === 0) {
    return null;
  }

  return (
    <section
      aria-label="Questions"
      className={[
        "relative touch-none overflow-hidden focus-visible:outline-none",
        "h-[calc(100svh-150px)] min-h-[430px]",
        className,
      ]
        .filter(Boolean)
        .join(" ")}
      onTouchEnd={handleTouchEnd}
      onTouchMove={handleTouchMove}
      onTouchStart={handleTouchStart}
      onWheel={handleWheel}
    >
      {questions.map((question, index) => {
        const offset = index - activeIndex;
        const isActive = offset === 0;
        const isAdjacent = Math.abs(offset) === 1;
        const showFooter = Boolean(
          footer && isActive && index === questions.length - 1,
        );
        const questionKey =
          isValidElement(question) && question.key !== null
            ? question.key
            : String(question);

        return (
          <div
            key={questionKey}
            ref={(element) => {
              questionRefs.current[index] = element;
            }}
            aria-current={isActive ? "step" : undefined}
            aria-hidden={isActive ? undefined : true}
            inert={isActive ? undefined : true}
            className={[
              "absolute inset-0 flex w-full items-center transition-all duration-300 ease-out",
              isActive
                ? "pointer-events-auto z-10 scale-100 opacity-100"
                : "pointer-events-none z-0 scale-[0.96]",
              !isActive && isAdjacent ? "opacity-20" : "",
              !isActive && !isAdjacent ? "opacity-0" : "",
            ].join(" ")}
            style={{ transform: `translateY(${offset * 50}%)` }}
          >
            <div className="w-full">
              {question}
              {showFooter ? <div className="mt-6">{footer}</div> : null}
            </div>
          </div>
        );
      })}
      {firstQuestionHint ? (
        <div
          aria-hidden="true"
          className={[
            "pointer-events-none absolute bottom-6 left-1/2 -translate-x-1/2",
            "transition-opacity duration-500  motion-safe:animate-bounce",
            activeIndex === 0 ? "opacity-100" : "opacity-0",
          ].join(" ")}
        >
          {firstQuestionHint}
        </div>
      ) : null}
    </section>
  );
}

export default QuestionSnapList;
