import { FloatingIndicator, UnstyledButton, Box, Text } from "@mantine/core";
import { Carousel } from "@mantine/carousel";
import {
  useState,
  useEffect,
  ReactNode,
  useRef,
  useCallback,
  useMemo,
} from "react";
import { useRouter } from "@tanstack/react-router";

interface TabItem {
  label: string;
  /** Stable URL slug for the ?tab= param; defaults to label. Set this when label is translated. */
  value?: string;
  content: ReactNode;
}

const tabValue = (tab: TabItem) => (tab.value ?? tab.label).toLowerCase();

interface SwipeableTabsProps {
  tabs: TabItem[];
  defaultTab?: number;
  mb?: string | number;
  onTabChange?: (index: number, tab: TabItem) => void;
}

function SwipeableTabs({
  tabs,
  defaultTab = 0,
  onTabChange,
  mb,
}: SwipeableTabsProps) {
  const router = useRouter();
  const search = router.state.location.search as any;
  const [embla, setEmbla] = useState<any>(null);

  const getActiveTabFromUrl = useCallback(() => {
    const urlTab = search?.tab;
    if (typeof urlTab === "string") {
      const tabIndex = tabs.findIndex(
        (tab) => tabValue(tab) === urlTab.toLowerCase()
      );
      return tabIndex !== -1 ? tabIndex : defaultTab;
    }
    return defaultTab;
  }, [search?.tab, tabs, defaultTab]);

  const [activeTab, setActiveTab] = useState(getActiveTabFromUrl);
  const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
  const controlsRefs = useRef<Record<number, HTMLSpanElement | null>>({});
  const slideRefs = useRef<Record<number, HTMLDivElement | null>>({});
  const [carouselHeight, setCarouselHeight] = useState<number | "auto">("auto");

  const changeTab = useCallback(
    (index: number) => {
      if (index === activeTab || index < 0 || index >= tabs.length) return;

      setActiveTab(index);
      embla?.scrollTo(index);
      onTabChange?.(index, tabs[index]);

      document
        .getElementById("scroll-wrapper")
        ?.querySelector(".mantine-ScrollArea-viewport")
        ?.scrollTo({ top: 0 });

      const tabLabel = tabValue(tabs[index]);
      if (typeof window !== "undefined") {
        const url = new URL(window.location.href);
        url.searchParams.set("tab", tabLabel);
        window.history.replaceState(null, "", url.toString());
      }
    },
    [activeTab, tabs, embla, onTabChange]
  );

  const handleEmblaSelect = useCallback(() => {
    if (!embla) return;
    const newIndex = embla.selectedScrollSnap();
    changeTab(newIndex);
  }, [embla, changeTab]);

  useEffect(() => {
    if (!embla) return;

    embla.on("select", handleEmblaSelect);
    return () => embla.off("select", handleEmblaSelect);
  }, [embla, handleEmblaSelect]);

  useEffect(() => {
    const newActiveTab = getActiveTabFromUrl();
    if (newActiveTab !== activeTab) {
      setActiveTab(newActiveTab);
      embla?.scrollTo(newActiveTab);
    }
  }, [search?.tab]);

  const updateHeight = useCallback(() => {
    const activeSlideRef = slideRefs.current[activeTab];
    if (activeSlideRef) {
      const height = activeSlideRef.scrollHeight;
      setCarouselHeight(height);
    }
  }, [activeTab]);

  useEffect(() => {
    updateHeight();
  }, [activeTab, updateHeight]);

  useEffect(() => {
    const timeoutId = setTimeout(updateHeight, 0);
    return () => clearTimeout(timeoutId);
  }, [updateHeight]);

  useEffect(() => {
    const activeSlideRef = slideRefs.current[activeTab];
    if (!activeSlideRef) return;

    let timeoutId: any;
    const resizeObserver = new ResizeObserver(() => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(updateHeight, 16);
    });

    resizeObserver.observe(activeSlideRef);

    return () => {
      resizeObserver.disconnect();
      clearTimeout(timeoutId);
    };
  }, [activeTab, updateHeight]);

  const setControlRef = useCallback(
    (index: number) => (node: HTMLSpanElement | null) => {
      controlsRefs.current[index] = node;
    },
    []
  );

  const setSlideRef = useCallback(
    (index: number) => (node: HTMLDivElement | null) => {
      slideRefs.current[index] = node;
    },
    []
  );

  return (
    <Box style={{ touchAction: "pan-y" }}>
      <Box
        ref={setRootRef}
        pos="sticky"
        top={0}
        style={{
          display: "flex",
          paddingInline: "var(--mantine-spacing-md)",
          marginBottom: mb !== undefined ? mb : "var(--mantine-spacing-md)",
          zIndex: 100,
          backgroundColor: "var(--mantine-color-body)",
        }}
      >
        <FloatingIndicator
          target={controlsRefs.current[activeTab]}
          parent={rootRef}
          styles={{
            root: {
              borderBottom: "2px solid var(--mantine-primary-color-filled)",
              paddingInline: "0.5rem",
            },
          }}
        />
        {tabs.map((tab, index) => (
          <UnstyledButton
            key={`${tab.label}-${index}`}
            onClick={() => changeTab(index)}
            style={{
              flex: 1,
              padding: "var(--mantine-spacing-sm) var(--mantine-spacing-xs)",
              textAlign: "center",
              color:
                activeTab === index
                  ? "var(--mantine-primary-color-filled)"
                  : "var(--mantine-color-text)",
              fontWeight: activeTab === index ? 600 : 400,
              transition: "color 200ms ease, font-weight 200ms ease",
              backgroundColor: "transparent",
              border: "none",
              borderRadius: 0,
            }}
          >
            <Text
              size="sm"
              component="span"
              style={{
                display: "inline-block",
                paddingInline: "0.5rem",
                paddingBottom: "0.25rem",
              }}
              ref={setControlRef(index)}
            >
              {tab.label}
            </Text>
          </UnstyledButton>
        ))}
      </Box>

      <Carousel
        getEmblaApi={setEmbla}
        withControls={false}
        withIndicators={false}
        slideSize="100%"
        initialSlide={activeTab}
        style={{
          overflow: "hidden",
          height: carouselHeight === "auto" ? "auto" : `${carouselHeight}px`,
          transition: "height 300ms ease",
          touchAction: "pan-y",
          width: "100%",
          maxWidth: "100vw",
        }}
      >
        {tabs.map((tab, index) => (
          <Carousel.Slide key={`${tab.label}-content-${index}`}>
            <Box ref={setSlideRef(index)} style={{ height: "auto", width: "100%", maxWidth: "100vw", overflow: "hidden" }}>
              {tab.content}
            </Box>
          </Carousel.Slide>
        ))}
      </Carousel>
    </Box>
  );
}

export default SwipeableTabs;
