"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[];
  /**
   * Fired with the program index whenever a different card becomes the centred
   * one — by swipe, wheel or autoplay — so the age slider above can follow it.
   */
  onFocusedProgramChange?: (programIndex: number) => void;
};

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

  // The last index handed to the parent. The tripled track is silently
  // repositioned to keep the loop illusion, and those jumps leave the *relative*
  // index untouched — comparing against this makes them free of charge, and it
  // also stops a parent-driven recentre from echoing straight back out.
  const lastFocusRef = useRef<number | null>(null);

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

    lastFocusRef.current = relativeIndex;
    onFocusedProgramChange?.(relativeIndex);
  };

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

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

  // The track is tripled to fake an infinite loop, which only works while the
  // copies stay off-screen. A franchise with a short catalogue would otherwise
  // see the same card two or three times at once (2 programs -> BC, GT, BC, GT...).
  // At the widest supported viewport roughly 7 cards are visible, so below this
  // count each program is rendered exactly once and the loop is switched off.
  const LOOP_MIN_PROGRAMS = 8;
  const isLooping = programs.length >= LOOP_MIN_PROGRAMS;

  // Duplicate slides for manual infinite looping
  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;

  // Move to middle block when component mounts or programs change
  useEffect(() => {
    // `??` on purpose, not a `.length > 0` test: an EMPTY array is a real
    // answer from the parent — "no programme covers the selected age" — and
    // must be preserved. Treating it as "nothing supplied" and substituting [0]
    // highlighted the first card regardless, so picking age 4 still showed a
    // 6-10 programme as active. Only undefined means "uncontrolled".
    const initialIndexes = controlledActiveIndexes ?? [0];

    setActiveIndexes(initialIndexes);
    setCurrentBullet(initialIndexes[0] ?? 0);
    lastFocusRef.current = initialIndexes[0] ?? 0;

    const s = swiperRef.current;
    if (s && originalCount > 0) {
      const centered = initialIndexes[0] ?? 0;
      setTimeout(() => {
        try {
          s.slideTo(middleStartIndex + centered, 0);
        } catch {}
      }, 0);
    }
  }, [originalCount, controlledActiveIndexes?.join(",")]);

  // Center based on multiple active indexes coming from parent
  useEffect(() => {
    if (!Array.isArray(controlledActiveIndexes) || controlledActiveIndexes.length === 0)
      return;

    if (originalCount === 0) return;

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

    if (valid.length === 0) return;

    const s = swiperRef.current;
    const currentRelative =
      s && originalCount > 0 ? (s.activeIndex ?? 0) % originalCount : null;

    // The card already on screen satisfies the new selection, so leave it
    // alone. Without this the age we just derived FROM a swipe would bounce the
    // user off the card they chose and onto the group's midpoint.
    if (currentRelative !== null && valid.includes(currentRelative)) {
      lastFocusRef.current = currentRelative;
      setActiveIndexes(Array.from(new Set(valid)));
      setCurrentBullet(currentRelative);
      return;
    }

    // Center on the middle of active group
    const sum = valid.reduce((a, b) => a + b, 0);
    const centered = Math.round(sum / valid.length);

    if (s) {
      try {
        s.slideTo(middleStartIndex + centered, 400);
      } catch {}
    }

    lastFocusRef.current = centered;
    setActiveIndexes(Array.from(new Set(valid)));
    setCurrentBullet(centered);
  }, [controlledActiveIndexes?.join(","), originalCount]);

  // Handle slide changes (for infinite loop illusion)
  const onSlideChange = (swiper: SwiperType) => {
    let activeDupIndex = swiper.activeIndex ?? 0;

    if (!isLooping) {
      // Single set of slides: the index already is the program index.
      const relative = originalCount > 0 ? activeDupIndex % originalCount : 0;
      setCurrentBullet(relative);
      reportFocus(relative);
      return;
    }

    if (originalCount > 0) {
      // avoid interrupting touchpad momentum
      if (swiper.animating) {
        const relative = activeDupIndex % originalCount;
        setCurrentBullet(relative);
        reportFocus(relative);
        return;
      }
  
      if (activeDupIndex < originalCount) {
        const target = activeDupIndex + originalCount;
        setTimeout(() => {
          try { swiper.slideTo(target, 0); } catch {}
        }, 50);
        activeDupIndex = target;
      } 
      
      else if (activeDupIndex >= originalCount * 2) {
        const target = activeDupIndex - originalCount;
        setTimeout(() => {
          try { swiper.slideTo(target, 0); } catch {}
        }, 50);
        activeDupIndex = target;
      }
    }
  
    const relativeIndex = activeDupIndex % originalCount;
    setCurrentBullet(relativeIndex);
    reportFocus(relativeIndex);
  };
  

  return (
    <div className="w-full relative overflow-visible lg:pt-20 pt-16 program-around-slider">
      <Swiper
        onSwiper={(s) => {
          swiperRef.current = s;
          if (s && originalCount > 0) {
            try {
              s.slideTo(middleStartIndex, 0);
            } catch {}
          }
        }}
        modules={[Navigation, Autoplay,Mousewheel]}
        mousewheel={{
          forceToAxis: true,     // prefer horizontal wheel movement for this slider
          releaseOnEdges: true,  // keep wheel scroll working even at edges
          sensitivity: 1,        // or use thresholdDelta / thresholdTime depending on lib version
        }}
        centeredSlides={true}
        loop={false}
        spaceBetween={0}
        slidesOffsetBefore={0}
        slidesPerView="auto"
        slidesOffsetAfter={0}
        observer={true}
        observeParents={true}
        watchSlidesProgress={true}
        onSlideChange={onSlideChange}
        navigation={false}
        pagination={false as any}
        autoplay={{
          delay: 3500,
          disableOnInteraction: true,
        }}
        speed={600}
        className="!overflow-visible"
        wrapperClass="!overflow-visible"
        style={{
          paddingLeft: "8vw",
          paddingRight: "8vw",
          boxSizing: "content-box",
        }}
      >
        {slides.map((program, dupIdx) => {
          const originalIndex = dupIdx % originalCount;

          // Active logic
          const isActive = activeIndexes.includes(originalIndex);

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

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

          // SPACING (adjust here if you want tighter or looser)
          const baseGap = 6; // default small gap
          const activeExtra = isActive ? 8 : 0;
          const neighborExtra = isNeighbor && !isActive ? 4 : 0;

          const margin = (baseGap + activeExtra + neighborExtra) / 3;

          return (
            <SwiperSlide
              key={`${program.id ?? originalIndex}-${dupIdx}`}
              style={{
                width: "auto",
                display: "flex",
                justifyContent: "center",
                alignItems: "center",
                overflow: "visible",
                marginLeft: margin,
                marginRight: margin,
              }}
              className="!w-auto !overflow-visible lg:pb-4"
            >
              <div
                onClick={() => {
                  if (!swiperRef.current) return;
                  const target = middleStartIndex + originalIndex;
                  swiperRef.current.slideTo(target, 400);
                }}
                style={{ cursor: "pointer", overflow: "visible" }}
              >
                <ProgramCard
                  program={program}
                  isActive={isActive}
                  href={detailHref("/programs", program.id, program.slug)}
                />
              </div>
            </SwiperSlide>
          );
        })}
      </Swiper>

      {/* Custom Bullets */}
      <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={() => {
              if (!swiperRef.current) return;
              swiperRef.current.slideTo(middleStartIndex + idx, 400);
              setCurrentBullet(idx);
              setActiveIndexes([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>
  );
}
