"use client";

import React, {
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { detailHref } from "@/src/lib/utils/detailHref";
import {
  Autoplay,
  Mousewheel,
  Navigation,
} from "swiper/modules";
import type { Swiper as SwiperType } from "swiper";

import "swiper/css";
import "swiper/css/navigation";

import type { ProcessedProgram } from "@/src/lib/types/programs";
import ProgramCard from "../ProgramCard";

type Props = {
  programs: ProcessedProgram[];
  activeIndexes?: number[];
  mobileFallbackIndex?: number;
  ageSliderRevision?: number;
  onFocusedProgramChange?: (
    programIndex: number
  ) => void;
};

export default function ProgramSwiper({
  programs,
  activeIndexes: controlledActiveIndexes,
  mobileFallbackIndex = 0,
  ageSliderRevision = 0,
  onFocusedProgramChange,
}: Props) {
  const swiperRef = useRef<SwiperType | null>(null);

  const lastFocusRef = useRef<number | null>(null);

  const internalNavigationRef = useRef(false);

  const [activeIndexes, setActiveIndexes] = useState<number[]>(
    controlledActiveIndexes ?? [0]
  );

  const [currentBullet, setCurrentBullet] = useState<number>(
    controlledActiveIndexes?.[0] ?? 0
  );

  const [activeSlideIndex, setActiveSlideIndex] = useState<number>(
    controlledActiveIndexes?.[0] ?? 0
  );

  const [mobileActiveIndex, setMobileActiveIndex] = useState<number>(
    controlledActiveIndexes?.[0] ?? 0
  );

  // Desktop-only focused card; mobile logic remains separate.
  const [desktopActiveIndex, setDesktopActiveIndex] = useState<number>(
    controlledActiveIndexes?.[0] ?? 0
  );

  const LOOP_MIN_PROGRAMS = 8;

  const isLooping = programs.length >= LOOP_MIN_PROGRAMS;

  const slides = useMemo(() => {
    if (!programs || programs.length === 0) {
      return [];
    }

    return isLooping
      ? [...programs, ...programs, ...programs]
      : programs;
  }, [programs, isLooping]);

  const originalCount = programs.length;

  const middleStartIndex = isLooping ? originalCount : 0;

  // Keep the latest age-to-program mapping available to the revision-driven
  // effect without rerunning it for age changes caused by dot/card navigation.
  const mobileAgeTargetRef = useRef({
    activeIndexes: controlledActiveIndexes ?? [],
    fallbackIndex: mobileFallbackIndex,
  });
  mobileAgeTargetRef.current = {
    activeIndexes: controlledActiveIndexes ?? [],
    fallbackIndex: mobileFallbackIndex,
  };

  const getLoopTarget = (relativeIndex: number, currentIndex: number) => {
    if (!isLooping) return relativeIndex;
    const candidates = [relativeIndex, relativeIndex + originalCount, relativeIndex + originalCount * 2];
    return candidates.reduce((closest, candidate) =>
      Math.abs(candidate - currentIndex) < Math.abs(closest - currentIndex) ? candidate : closest
    );
  };

  const hasMultipleActivePrograms = activeIndexes.length > 1;

  const hasMoreThanFourActivePrograms = activeIndexes.length > 4;

  const hasFourOrMoreActivePrograms = activeIndexes.length >= 4;

  /** Age slider changes drive mobile card movement. */
  useEffect(() => {
    if (
      typeof window === "undefined" ||
      window.innerWidth >= 768 ||
      originalCount === 0
    ) {
      return;
    }

    const { activeIndexes: ageIndexes, fallbackIndex } =
      mobileAgeTargetRef.current;
    const selected = ageIndexes.length > 0
      ? Math.max(0, Math.min(originalCount - 1, Math.floor(Number(ageIndexes[0]))))
      : Math.max(0, Math.min(originalCount - 1, Math.floor(fallbackIndex)));
    const swiper = swiperRef.current;
    const targetIndex = getLoopTarget(
      middleStartIndex + selected,
      swiper?.activeIndex ?? middleStartIndex + selected
    );

    setMobileActiveIndex(selected);
    setActiveSlideIndex(targetIndex);
    setCurrentBullet(selected);
    setActiveIndexes([selected]);

    if (swiper && !swiper.destroyed) {
      swiper.slideTo(targetIndex, 400);
    }
    // This intentionally runs only for explicit age-slider input. Dot and card
    // navigation also update the age, but must not cause the card to jump back.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [ageSliderRevision, originalCount, middleStartIndex]);

  const reportFocus = (relativeIndex: number) => {
    if (!Number.isFinite(relativeIndex)) return;

    if (lastFocusRef.current === relativeIndex) return;

    lastFocusRef.current = relativeIndex;

    onFocusedProgramChange?.(relativeIndex);
  };

  const getActiveIndexesForNavigation = (selectedIndex: number) => {
    if (
      Array.isArray(controlledActiveIndexes) &&
      controlledActiveIndexes.length > 0
    ) {
      const valid = controlledActiveIndexes
        .map(Number)
        .filter(Number.isFinite)
        .map((index) =>
          Math.max(
            0,
            Math.min(originalCount - 1, Math.floor(index))
          )
        );

      if (valid.length > 0) {
        return Array.from(new Set(valid));
      }
    }

    return [selectedIndex];
  };

  /**
   * Initial / program-count setup.
   */
  useEffect(() => {
    const initialIndexes = controlledActiveIndexes ?? [0];
    const initialIndex = initialIndexes[0] ?? 0;

    setActiveIndexes(initialIndexes);
    setCurrentBullet(initialIndex);
    setActiveSlideIndex(middleStartIndex + initialIndex);
    setMobileActiveIndex(initialIndex);
    setDesktopActiveIndex(initialIndex);

    lastFocusRef.current = initialIndex;

    if (swiperRef.current && originalCount > 0) {
      try {
        swiperRef.current.slideTo(
          middleStartIndex + initialIndex,
          0
        );
      } catch {}
    }
  }, [originalCount, middleStartIndex]);

  /**
   * Controlled range-slider changes.
   */
  useEffect(() => {
    if (originalCount === 0) {
      return;
    }

    const isMobile =
      typeof window !== "undefined" &&
      window.innerWidth < 768;

    const hasControlledSelection =
      Array.isArray(controlledActiveIndexes) &&
      controlledActiveIndexes.length > 0;

    if (isMobile || !hasControlledSelection) {
      return;
    }

    // TABLET + DESKTOP: avoid a second slideTo after internal navigation.
    if (!isMobile && internalNavigationRef.current) {
      internalNavigationRef.current = false;
      return;
    }

    const valid = (controlledActiveIndexes ?? [])
      .map(Number)
      .filter(Number.isFinite)
      .map((n) =>
        Math.max(
          0,
          Math.min(originalCount - 1, Math.floor(n))
        )
      );

    const uniqueValid = Array.from(new Set(valid));
    const swiper = swiperRef.current;

    // TABLET + DESKTOP.
    if (uniqueValid.length === 1) {
      const selected = uniqueValid[0];
      const targetIndex = getLoopTarget(middleStartIndex + selected, swiper?.activeIndex ?? middleStartIndex + selected);

      if (swiper) {
        try {
          swiper.slideTo(targetIndex, 600);
        } catch {}
      }

      setActiveSlideIndex(targetIndex);
      setDesktopActiveIndex(selected);
      lastFocusRef.current = selected;
      setActiveIndexes(uniqueValid);
      setCurrentBullet(selected);

      return;
    }

    if (uniqueValid.length >= 4) {
      const first = uniqueValid[0];
      const targetIndex = getLoopTarget(middleStartIndex + first, swiper?.activeIndex ?? middleStartIndex + first);

      if (swiper) {
        try {
          swiper.slideTo(targetIndex, 600);
        } catch {}
      }

      setActiveSlideIndex(targetIndex);
      setDesktopActiveIndex(first);
      lastFocusRef.current = first;
      setActiveIndexes(uniqueValid);
      setCurrentBullet(first);

      return;
    }

    const first = uniqueValid[0];
    const last = uniqueValid[uniqueValid.length - 1];
    const centered = Math.round((first + last) / 2);
    const targetIndex = getLoopTarget(middleStartIndex + centered, swiper?.activeIndex ?? middleStartIndex + centered);

    if (swiper) {
      try {
        swiper.slideTo(targetIndex, 600);
      } catch {}
    }

    lastFocusRef.current = centered;
    setActiveIndexes(uniqueValid);
    setCurrentBullet(centered);
    setDesktopActiveIndex(centered);
    setActiveSlideIndex(targetIndex);
  }, [
    controlledActiveIndexes?.join(","),
    originalCount,
    middleStartIndex,
  ]);

  /**
   * Handle Swiper slide changes.
   */
  const onSlideChange = (swiper: SwiperType) => {
    const activeDupIndex = swiper.activeIndex ?? 0;

    if (originalCount === 0) return;

    const isMobile =
      typeof window !== "undefined" &&
      window.innerWidth < 768;

    // NON-LOOPING.
    if (!isLooping) {
      const relativeIndex = activeDupIndex % originalCount;

      setActiveSlideIndex(activeDupIndex);
      setCurrentBullet(relativeIndex);

      // MOBILE: unchanged.
      if (isMobile) {
        setMobileActiveIndex(relativeIndex);
        setActiveIndexes([relativeIndex]);
        lastFocusRef.current = relativeIndex;
        onFocusedProgramChange?.(relativeIndex);
        return;
      }

      // TABLET + DESKTOP.
      setDesktopActiveIndex(relativeIndex);
      lastFocusRef.current = relativeIndex;
      onFocusedProgramChange?.(relativeIndex);

      return;
    }

    setActiveSlideIndex(activeDupIndex);

    const relativeIndex = activeDupIndex % originalCount;

    setCurrentBullet(relativeIndex);

    // MOBILE: unchanged.
    if (isMobile) {
      setMobileActiveIndex(relativeIndex);
      setActiveIndexes([relativeIndex]);
      lastFocusRef.current = relativeIndex;
      onFocusedProgramChange?.(relativeIndex);
      return;
    }

    // TABLET + DESKTOP.
    setDesktopActiveIndex(relativeIndex);
    lastFocusRef.current = relativeIndex;
    onFocusedProgramChange?.(relativeIndex);
  };

  /**
   * Navigation dot click.
   */
  const handleDotClick = (idx: number) => {
    const swiper = swiperRef.current;

    if (!swiper) return;

    const isMobile =
      typeof window !== "undefined" &&
      window.innerWidth < 768;

    const target = getLoopTarget(middleStartIndex + idx, swiper.activeIndex);

    const rangeIndexes = getActiveIndexesForNavigation(idx);

    // MOBILE: unchanged.
    if (isMobile) {

      setMobileActiveIndex(idx);
      setActiveIndexes([idx]);
      setActiveSlideIndex(target);
      setCurrentBullet(idx);

      swiper.slideTo(target, 600);

      lastFocusRef.current = idx;
      onFocusedProgramChange?.(idx);

      return;
    }

    // TABLET + DESKTOP.
    internalNavigationRef.current = true;

    // Preserve the selected age range.
    setActiveIndexes(rangeIndexes);

    // Explicitly activate the clicked card.
    setDesktopActiveIndex(idx);

    setActiveSlideIndex(target);
    setCurrentBullet(idx);
    lastFocusRef.current = idx;

    // Animate normally in either direction.
    swiper.slideTo(target, 600);

    onFocusedProgramChange?.(idx);
  };

  /**
   * Card click navigation.
   */
  const handleCardClick = (originalIndex: number) => {
    if (!swiperRef.current) return;

    const isMobile =
      typeof window !== "undefined" &&
      window.innerWidth < 768;

    const target = getLoopTarget(middleStartIndex + originalIndex, swiperRef.current.activeIndex);

    // MOBILE: unchanged.
    if (isMobile) {

      setMobileActiveIndex(originalIndex);
      setActiveIndexes([originalIndex]);
      setCurrentBullet(originalIndex);
      setActiveSlideIndex(target);

      swiperRef.current.slideTo(target, 600);

      lastFocusRef.current = originalIndex;
      onFocusedProgramChange?.(originalIndex);

      return;
    }

    // TABLET + DESKTOP.
    internalNavigationRef.current = true;

    const rangeIndexes =
      getActiveIndexesForNavigation(originalIndex);

    setDesktopActiveIndex(originalIndex);
    setActiveIndexes(rangeIndexes);
    setActiveSlideIndex(target);
    setCurrentBullet(originalIndex);

    lastFocusRef.current = originalIndex;

    swiperRef.current.slideTo(target, 600);

    onFocusedProgramChange?.(originalIndex);
  };

  return (
    <div className="w-full relative overflow-visible lg:pt-20 pt-16 program-around-slider">
      <Swiper
        onSwiper={(swiper) => {
          swiperRef.current = swiper;

          if (swiper && originalCount > 0) {
            const initialIndex =
              controlledActiveIndexes?.[0] ?? 0;

            const targetIndex =
              middleStartIndex + initialIndex;

            try {
              swiper.slideTo(targetIndex, 0);
              setActiveSlideIndex(targetIndex);
              setMobileActiveIndex(initialIndex);
              setDesktopActiveIndex(initialIndex);
            } catch {}
          }
        }}
        modules={[Navigation, Autoplay, Mousewheel]}
        mousewheel={{
          forceToAxis: true,
          releaseOnEdges: true,
          sensitivity: 1,
        }}
        centeredSlides={!hasFourOrMoreActivePrograms}
        centeredSlidesBounds={false}
        loop={false}
        spaceBetween={0}
        slidesOffsetBefore={
          hasMoreThanFourActivePrograms ? 0 : 0
        }
        slidesOffsetAfter={0}
        slidesPerView="auto"
        observer={true}
        observeParents={true}
        watchSlidesProgress={true}
        onSlideChange={onSlideChange}
        onTransitionEnd={(swiper) => {
          if (!isLooping || originalCount === 0) return;
          const index = swiper.activeIndex;
          if (index < originalCount || index >= originalCount * 2) {
            const centeredIndex = index < originalCount ? index + originalCount : index - originalCount;
            swiper.slideTo(centeredIndex, 0, false);
            setActiveSlideIndex(centeredIndex);
          }
        }}
        navigation={false}
        pagination={false as any}
        autoplay={{
          delay: 3500,
          disableOnInteraction: true,
        }}
        speed={600}
        className={`
          !overflow-visible
          ${
            hasMultipleActivePrograms
              ? "multiple-program-cards"
              : "single-program-card"
          }
          ${
            hasMoreThanFourActivePrograms
              ? "more-than-four-program-cards"
              : ""
          }
        `}
        wrapperClass="!overflow-visible"
        style={{
          paddingLeft: "8vw",
          paddingRight: "8vw",
          boxSizing: "content-box",
        }}
      >
        {slides.map((program, dupIdx) => {
          const originalIndex = dupIdx % originalCount;

          const isMobile =
            typeof window !== "undefined" &&
            window.innerWidth < 768;

          const isActive = isMobile
            ? originalIndex === mobileActiveIndex
            : activeIndexes.includes(originalIndex) ||
              originalIndex === desktopActiveIndex;

          const prevIndex =
            (originalIndex - 1 + originalCount) %
            originalCount;

          const nextIndex =
            (originalIndex + 1) % originalCount;

          const isNeighbor =
            activeIndexes.includes(prevIndex) ||
            activeIndexes.includes(nextIndex);

          // Follow the logical slide, not its duplicate's physical index.
          // Loop recentering should not trigger a second scale transition.
          const isMobileCenter =
            isMobile && originalIndex === mobileActiveIndex;

          const isFirstActive =
            hasFourOrMoreActivePrograms &&
            originalIndex === activeIndexes[0];

          const margin = isMobile ? 12 : 8;

          return (
            <SwiperSlide
              key={`${program.id ?? originalIndex}-${dupIdx}`}
              style={{
                width: "auto",
                display: "flex",
                justifyContent: "center",
                alignItems: "center",
                overflow: "visible",
                marginLeft: margin,
                marginRight: margin,
              }}
              className={`
                !overflow-visible
                lg:pb-4
                lg:!w-auto
                max-md:!flex-shrink-0
                ${
                  isFirstActive
                    ? "max-md:!w-[85vw] max-md:!ml-0 max-md:!mr-[0px]"
                    : "max-md:!w-[85vw] max-md:!mx-0"
                }
                md:max-lg:!w-auto
                md:max-lg:!flex-shrink-0
                ${
                  hasMultipleActivePrograms
                    ? "multiple-program-slide"
                    : "single-program-slide"
                }
                ${
                  hasMoreThanFourActivePrograms
                    ? "more-than-four-program-slide"
                    : ""
                }
              `}
            >
              <div
                onClick={() => handleCardClick(originalIndex)}
                style={{
                  cursor: "pointer",
                  overflow: "visible",
                  width: "100%",
                }}
                className={`
                  transition-all
                  duration-300
                  ease-out
                  transform-gpu
                  origin-center
                  ${
                    isMobileCenter
                      ? "max-md:opacity-100 max-md:scale-100"
                      : "max-md:opacity-[0.8] max-md:scale-[0.9]"
                  }
                `}
              >
                <ProgramCard
                  program={program}
                  isActive={isActive}
                  href={detailHref(
                    "/programs",
                    program.id,
                    program.slug
                  )}
                />
              </div>
            </SwiperSlide>
          );
        })}
      </Swiper>

      {/* Navigation dots */}
      <div className="flex justify-center items-center gap-3 mt-6">
        {programs.map((p, idx) => (
          <button
            key={p.id ?? idx}
            aria-label={`Go to slide ${idx + 1}`}
            onClick={() => handleDotClick(idx)}
            className="w-[40px] h-[40px] flex items-center justify-center cursor-pointer"
          >
            <span
              className={`
                w-[16px]
                h-[16px]
                rounded-full
                transition-transform
                ${
                  currentBullet === idx
                    ? "scale-110"
                    : "scale-100"
                }
              `}
              style={{
                background:
                  currentBullet === idx
                    ? "#0097DC"
                    : "rgba(255,255,255,0.35)",
              }}
            />
          </button>
        ))}
      </div>
    </div>
  );
}
