import { useCallback, useRef, useState, useEffect, useMemo } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';

type UseTabsProps = {
  activeTab: string;
  onTabChange: (tab: string) => void;
  totalTabs: number;
  containerRef?: React.RefObject<HTMLElement | null>;
};

type Tabs = {
  visible: number[];
  overflow: number[];
};

type UseTabsReturn = {
  setTabRef: (element: HTMLButtonElement | null, index: number) => void;
  onTabClick: (labelOrIndex: string | number) => void;
  handleKeyboard: (event: React.KeyboardEvent, index: number) => void;
  visibleTabs: number[];
  overflowTabs: number[];
  isReady: boolean;
};

type UseActiveTabProps = {
  initialTab: string;
  tabsId?: string;
};

const MORE_BUTTON_WIDTH = 80;
const TABS_GAP = 8;

export function useTabs({
  activeTab,
  onTabChange,
  totalTabs,
  containerRef,
}: UseTabsProps): UseTabsReturn {
  const [tabs, setTabs] = useState<Tabs>({
    visible: Array.from({ length: totalTabs }, (_, i) => i),
    overflow: [],
  });
  const [isReady, setIsReady] = useState(false);
  const isFirstCalculation = useRef(true);
  const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
  const tabWidthsRef = useRef<number[]>([]);
  const tabLabelsRef = useRef<string[]>([]);
  const activeTabRef = useRef(activeTab);
  const calculateVisibleTabsRef = useRef<(() => void) | null>(null);

  // Synchronously update ref before any callbacks or effects run
  activeTabRef.current = activeTab;

  const setTabRef = useCallback(
    (element: HTMLButtonElement | null, index: number) => {
      tabRefs.current[index] = element;

      const width = element?.offsetWidth;
      if (width) {
        tabWidthsRef.current[index] = width;
      }

      const label = element?.getAttribute('data-label');
      if (label) {
        tabLabelsRef.current[index] = label;
      }

      // Trigger calculation once all tabs are registered
      if (
        isFirstCalculation.current &&
        tabWidthsRef.current.length >= totalTabs &&
        tabLabelsRef.current.length >= totalTabs &&
        calculateVisibleTabsRef.current
      ) {
        requestAnimationFrame(calculateVisibleTabsRef.current);
      }
    },
    [totalTabs],
  );

  const focusTab = useCallback((index: number) => {
    const currentElement = tabRefs.current[index];
    currentElement?.focus();
  }, []);

  const onTabSelect = useCallback(
    (index: number) => {
      focusTab(index);
      const label = tabRefs.current[index]?.getAttribute('data-label');
      if (label) onTabChange(label);
    },
    [onTabChange, focusTab],
  );

  const handleKeyboard = useCallback(
    (event: React.KeyboardEvent, index: number) => {
      let newIndex = index;
      if (event.key === 'ArrowRight') {
        newIndex = (index + 1) % totalTabs;
      } else if (event.key === 'ArrowLeft') {
        newIndex = (index - 1 + totalTabs) % totalTabs;
      } else if (event.key === 'Home') {
        event.preventDefault();
        newIndex = 0;
      } else if (event.key === 'End') {
        event.preventDefault();
        newIndex = totalTabs - 1;
      } else {
        return;
      }
      onTabSelect(newIndex);
    },
    [totalTabs, onTabSelect],
  );

  const replaceLastVisibleTabWithClickedOverflowTab = useCallback((clickedIndex: number) => {
    setTabs((prevTabs) => {
      const { visible: visibleTabs, overflow: overflowTabs } = prevTabs;

      const sortedVisible = [...visibleTabs].sort((a, b) => a - b);
      const lastVisible = sortedVisible[sortedVisible.length - 1];

      return {
        visible: visibleTabs.map((idx) => (idx === lastVisible ? clickedIndex : idx)),
        overflow: overflowTabs.map((idx) => (idx === clickedIndex ? lastVisible : idx)),
      };
    });
  }, []);

  const onTabClick = useCallback(
    (labelOrIndex: string | number) => {
      const clickedIndex =
        typeof labelOrIndex === 'string'
          ? tabRefs.current.findIndex((ref) => ref?.getAttribute('data-label') === labelOrIndex)
          : labelOrIndex;

      if (clickedIndex === -1) return;

      const label = tabLabelsRef.current[clickedIndex];
      if (!label) return;

      // If this is an overflow tab, replace it with a visible one
      if (tabs.overflow.includes(clickedIndex)) {
        replaceLastVisibleTabWithClickedOverflowTab(clickedIndex);
      }

      onTabChange(label);
      focusTab(clickedIndex);
    },
    [tabs.overflow, onTabChange, replaceLastVisibleTabWithClickedOverflowTab, focusTab],
  );

  const calculateVisibleTabs = useCallback(() => {
    const container = containerRef?.current;
    if (!container) return;

    const containerWidth = container.offsetWidth;
    const tabWidths = tabWidthsRef.current;
    const tabLabels = tabLabelsRef.current;

    // Wait until all tabs are registered before calculating
    if (tabWidths.length < totalTabs || tabLabels.length < totalTabs) {
      return;
    }

    // Check if container has proper width (not zero)
    if (containerWidth === 0) {
      return;
    }

    // Find active tab index by label in tabLabelsRef, not by DOM element
    // because tab might not be rendered if it's in overflow
    const activeTabIndex = tabLabels.findIndex((label) => label === activeTabRef.current);

    let tabsWidth = activeTabIndex !== -1 ? tabWidths[activeTabIndex] : 0;
    const visibleTabs = activeTabIndex !== -1 ? [activeTabIndex] : [];
    const overflowTabs = [];

    for (let i = 0; i < tabWidths.length; i++) {
      if (i === activeTabIndex) continue;

      const tabWidthWithGap = tabWidths[i] + TABS_GAP;
      const projectedWidth = tabsWidth + tabWidthWithGap;

      if (projectedWidth <= containerWidth) {
        visibleTabs.push(i);
        tabsWidth += tabWidthWithGap;
      } else {
        overflowTabs.push(i);
      }
    }

    if (overflowTabs.length > 0) {
      tabsWidth += MORE_BUTTON_WIDTH;

      while (tabsWidth > containerWidth && visibleTabs.length > 1) {
        const removed = visibleTabs.pop();
        // Never remove the active tab - it should always stay visible or be the last one
        if (removed !== undefined && removed !== activeTabIndex) {
          overflowTabs.unshift(removed);
          tabsWidth -= tabWidths[removed];
        } else if (removed === activeTabIndex) {
          // Put it back if we accidentally removed the active tab
          visibleTabs.push(removed);
          break;
        }
      }

      // If even with only the active tab visible, it doesn't fit with More button,
      // move all tabs to overflow (show only dropdown)
      if (tabsWidth > containerWidth && visibleTabs.length === 1) {
        overflowTabs.unshift(...visibleTabs);
        visibleTabs.length = 0;
      }
    }

    setTabs({
      visible: visibleTabs,
      overflow: overflowTabs,
    });

    // Set ready state on first calculation
    if (isFirstCalculation.current) {
      isFirstCalculation.current = false;
      setIsReady(true);
    }
  }, [containerRef, totalTabs]);

  // Store calculateVisibleTabs in ref for use in setTabRef
  calculateVisibleTabsRef.current = calculateVisibleTabs;

  // Reset isFirstCalculation when totalTabs changes (new page/tabs)
  useEffect(() => {
    isFirstCalculation.current = true;
    setIsReady(false);
    // Clear refs so we wait for new tabs to register
    tabWidthsRef.current = [];
    tabLabelsRef.current = [];
  }, [totalTabs]);

  // Call calculateVisibleTabs on first render and resize
  useEffect(() => {
    const container = containerRef?.current;
    if (!container) return;

    let resizeTimeout: number | null = null;

    // Use ResizeObserver to wait until container has proper size
    const resizeObserver = new ResizeObserver(() => {
      if (resizeTimeout) cancelAnimationFrame(resizeTimeout);
      resizeTimeout = requestAnimationFrame(calculateVisibleTabs);
    });

    resizeObserver.observe(container);

    const handleResize = () => {
      if (resizeTimeout) cancelAnimationFrame(resizeTimeout);
      resizeTimeout = requestAnimationFrame(calculateVisibleTabs);
    };

    window.addEventListener('resize', handleResize);

    return () => {
      resizeObserver.disconnect();
      window.removeEventListener('resize', handleResize);
      if (resizeTimeout) cancelAnimationFrame(resizeTimeout);
    };
  }, [containerRef, totalTabs, calculateVisibleTabs]);

  // Recalculate when activeTab changes to ensure it's visible
  useEffect(() => {
    if (!containerRef?.current || isFirstCalculation.current) return;
    requestAnimationFrame(calculateVisibleTabs);
  }, [activeTab, containerRef, calculateVisibleTabs]);

  return {
    setTabRef,
    onTabClick,
    handleKeyboard,
    visibleTabs: tabs.visible,
    overflowTabs: tabs.overflow,
    isReady,
  };
}

export const useActiveTab = ({ initialTab, tabsId }: UseActiveTabProps) => {
  const location = useLocation();
  const navigate = useNavigate();
  const [activeTab, setActiveTab] = useState<string | undefined>(undefined);
  const resolvedActiveTab = activeTab ?? initialTab;
  const prevActiveTabRef = useRef(resolvedActiveTab);

  useEffect(() => {
    if (activeTab !== undefined) {
      return;
    }

    setActiveTab(getInitialTab({ initialTab, hash: location.hash, tabsId }));
  }, [activeTab, initialTab, location.hash, tabsId]);

  useEffect(() => {
    const hasActiveTabChanged = prevActiveTabRef.current !== resolvedActiveTab;
    if (!tabsId || !hasActiveTabChanged) {
      return;
    }

    prevActiveTabRef.current = resolvedActiveTab;
    const nextHashParams = new URLSearchParams(
      location.hash.startsWith('#') ? location.hash.slice(1) : location.hash,
    );
    nextHashParams.set(tabsId, resolvedActiveTab);
    const nextHash = `#${nextHashParams.toString()}`;

    if (nextHash === location.hash) {
      return;
    }

    navigate(
      {
        pathname: location.pathname,
        search: location.search,
        hash: nextHash,
      },
      { replace: true },
    );
  }, [resolvedActiveTab, navigate, location.pathname, location.search, location.hash, tabsId]);

  return useMemo(
    () => ({
      activeTab: resolvedActiveTab,
      setActiveTab,
    }),
    [resolvedActiveTab],
  );
};

type GetInitialTabProps = {
  initialTab: string;
  hash: string;
  tabsId?: string;
};

const getInitialTab = ({ initialTab, hash, tabsId }: GetInitialTabProps): string => {
  const hashParams = new URLSearchParams(hash.startsWith('#') ? hash.slice(1) : hash);
  let resultTab = initialTab;
  if (tabsId) {
    const tabFromUrl = hashParams.get(tabsId);
    resultTab = tabFromUrl ? tabFromUrl : resultTab;
  }
  return resultTab;
};
