import { RefObject, useCallback, useEffect, useRef, useState } from 'react';
import type { ContextType } from 'react';
import { useCarouselDimensions } from '../../../../hooks/useCarouselDimensions';
import { useCarouselHeight } from '../../../../hooks/useCarouselHeight';
import { useCarouselMinHeight } from '../../../../hooks/useCarouselMinHeight';
import {
  useCompactDesktopWidth,
  getCompactContainerStyle,
} from '../../../../hooks/useCompactDesktopWidth';
import { StoreContext } from '../../../../services/store';
import {
  infiniteScrollTimeout,
  spotlightCarouselTransitionDurationSec,
  partialItemWidth,
} from '../../../consts';
import { ISettings } from '../../../../types';

export interface LayoutParams {
  context: {
    widgetWidth: number;
    contentSize: ISettings['content_size'];
    isMobile: boolean;
    hideArrows: boolean;
  };
  config: {
    contentSize: ISettings['content_size'];
    itemCount: number;
    partialItemWidth: number;
    alwaysPartial?: boolean;
  };
  refs: {
    slideRef: RefObject<HTMLDivElement>;
  };
}

export interface HeightParams {
  refs: { outerRef: RefObject<HTMLDivElement> };
  config: {
    thumbnailShape: ISettings['thumbnail_shape'];
    contentSize: ISettings['content_size'];
    itemCount: number;
  };
  layout: {
    isCompact: boolean;
    constrainedWidgetWidth: number;
    widgetWidth: number;
  };
}

type StoreStateUpdater = ContextType<typeof StoreContext>['setStoreState'];

interface NavigationParams {
  config: {
    itemCount: number;
    contentSize: ISettings['content_size'];
    cloneCount: number;
    contentSizeNotReached: boolean;
  };
  refs: {
    slideRef: RefObject<HTMLDivElement>;
    containerRef: RefObject<HTMLDivElement>;
  };
  state: {
    itemWidth: number;
    currentPosition?: number;
  };
  actions: {
    setStoreState: StoreStateUpdater;
  };
  transitionDurationSec?: number;
}

interface NavigationState {
  activeIndex: number;
  isTransitioning: boolean;
  // When true for one cycle, suppresses the CSS transition during the clone→real snap.
  skipTransition: boolean;
}

// Forces re-render on window resize to update carousel dimensions.
const useForceResizeRerender = (): void => {
  const [, setTick] = useState(0);

  useEffect(() => {
    if (typeof window === 'undefined') {
      return;
    }

    const handleResize = () => setTick((tick) => tick + 1);
    window.addEventListener('resize', handleResize);

    window.removeEventListener('resize', handleResize);
  }, []);
};

/**
 * Decide whether the uniform carousel should use clones and partial visibility.
 * If there are fewer items than visible slots, disable cloning and partial positioning.
 */
export const useUniformCenteredConfig = (
  itemCount: number,
  contentSize: ISettings['content_size'],
  setStoreState: StoreStateUpdater,
) => {
  const normalizedContentSize = Math.max(contentSize, 1);
  const contentSizeNotReached = itemCount < normalizedContentSize;
  const partialItemWidthValue = contentSizeNotReached ? 0 : partialItemWidth;
  const cloneCount =
    contentSizeNotReached || itemCount <= 1 ? 0 : Math.min(normalizedContentSize, itemCount);

  useEffect(() => {
    setStoreState((state) => {
      if (state.productListUniformMode === contentSizeNotReached) {
        return state;
      }

      return {
        ...state,
        productListUniformMode: contentSizeNotReached,
      };
    });
  }, [contentSizeNotReached, setStoreState]);

  return { contentSizeNotReached, partialItemWidth: partialItemWidthValue, cloneCount };
};

// Manages layout dimensions, compact mode, and arrow sizing for the carousel.
export const useCarouselLayout = ({
  context: { widgetWidth, contentSize, isMobile, hideArrows },
  config: {
    contentSize: carouselContentSize,
    itemCount,
    partialItemWidth: carouselPartialItemWidth,
    alwaysPartial,
  },
  refs: { slideRef },
}: LayoutParams) => {
  useForceResizeRerender();

  const { width: constrainedWidgetWidth, isCompact } = useCompactDesktopWidth({
    widgetWidth,
    contentSize,
    isMobile,
  });

  const compactStyle = getCompactContainerStyle(isCompact, constrainedWidgetWidth);
  const carouselDimensions = useCarouselDimensions({
    widgetWidth: constrainedWidgetWidth,
    slideRef,
    hideArrows,
    hasItems: itemCount > 1,
    nbElems: carouselContentSize,
    itemCount,
    isMobile: false,
    partialItemWidth: carouselPartialItemWidth,
    alwaysPartial,
    forcedArrowSize: isCompact ? 'medium' : undefined,
  });

  const arrowSize = isCompact ? 'medium' : carouselDimensions.arrowSize;

  return {
    constrainedWidgetWidth,
    isCompact,
    compactStyle,
    carouselDimensions,
    arrowSize,
  };
};

// Calculates enforced minimum height for the carousel container.
export const useCarouselHeightConfig = ({
  refs: { outerRef },
  config: { thumbnailShape, contentSize, itemCount },
  layout: { isCompact, constrainedWidgetWidth, widgetWidth },
}: HeightParams) => {
  const carouselHeight = useCarouselHeight({
    ref: outerRef,
    shape: thumbnailShape,
    contentSize,
    itemCount,
    partialItemWidth,
  });
  const enforceConfiguredHeight = contentSize <= 3 && !isCompact;

  const enforcedMinHeight = useCarouselMinHeight(
    outerRef,
    carouselHeight,
    enforceConfiguredHeight,
    `${itemCount}-${isCompact ? constrainedWidgetWidth : widgetWidth}`,
  );

  return { enforcedMinHeight };
};

// Handlesandle navigation state, slide transitions, and position syncing for infinite carousels.
export const useInfiniteNavigation = ({
  config: { itemCount, contentSize, contentSizeNotReached },
  refs: { containerRef },
  state: { currentPosition },
  actions: { setStoreState },
  transitionDurationSec = spotlightCarouselTransitionDurationSec,
}: NavigationParams) => {
  const [navigationState, setNavigationState] = useState<NavigationState>({
    activeIndex: 0,
    isTransitioning: false,
    skipTransition: false,
  });

  const { activeIndex, isTransitioning } = navigationState;

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

  const clearPendingTimeout = useCallback(() => {
    if (timeoutRef.current !== null) {
      if (typeof window !== 'undefined') {
        window.clearTimeout(timeoutRef.current);
      }
      timeoutRef.current = null;
    }
  }, []);

  useEffect(() => () => clearPendingTimeout(), [clearPendingTimeout]);

  const updateStorePosition = useCallback(
    (position: number) => {
      setStoreState((state) => {
        if (state.currentPosition === position) {
          return state;
        }

        return {
          ...state,
          currentPosition: position,
        };
      });
    },
    [setStoreState],
  );

  const goToSlide = useCallback(
    (index: number, shouldUpdateCurrentPosition = true) => {
      if (itemCount === 0) {
        return;
      }

      if (contentSizeNotReached) {
        const normalizedIndex = ((index % itemCount) + itemCount) % itemCount;
        setNavigationState((state) => ({
          ...state,
          activeIndex: normalizedIndex,
          isTransitioning: false,
        }));

        if (shouldUpdateCurrentPosition) {
          updateStorePosition(normalizedIndex);
        }
        return;
      }

      // Normalise the destination index now so we can update the store immediately.
      // This makes the spotlight grow start at the same time as the slide animation.
      let newIndex = index;
      if (index >= itemCount) newIndex = Math.abs(index) - itemCount;
      if (index < 0) newIndex = itemCount - Math.abs(index);

      setNavigationState((state) => ({ ...state, activeIndex: index, isTransitioning: true }));

      if (shouldUpdateCurrentPosition) {
        updateStorePosition(newIndex);
      }

      clearPendingTimeout();

      if (typeof window === 'undefined') {
        setNavigationState((state) => ({ ...state, activeIndex: index, isTransitioning: false }));
        return;
      }

      // After the transition, snap back to the normalized index without visible animation.
      timeoutRef.current = window.setTimeout(() => {
        const isWrapping = newIndex !== index;

        setNavigationState((state) => ({
          ...state,
          activeIndex: newIndex,
          isTransitioning: false,
          skipTransition: isWrapping,
        }));
      }, transitionDurationSec * 1000 + 50);
    },
    [
      itemCount,
      contentSizeNotReached,
      containerRef,
      updateStorePosition,
      clearPendingTimeout,
      transitionDurationSec,
    ],
  );

  useEffect(() => {
    if (currentPosition === undefined || currentPosition === activeIndex || isTransitioning) {
      return;
    }

    const diff = Math.abs(currentPosition - activeIndex);

    // If the external position lands in the cloned buffer, use the clone equivalent
    // so the carousel can move continuously instead of jumping straight to the real index.
    if (diff > contentSize || diff + 1 === itemCount) {
      if (currentPosition > activeIndex) {
        goToSlide(currentPosition - itemCount);
      } else {
        goToSlide(currentPosition + itemCount);
      }
    } else {
      goToSlide(currentPosition);
    }
  }, [currentPosition, activeIndex, contentSize, itemCount, goToSlide, isTransitioning]);

  const handleSlide = useCallback(
    (direction: 'left' | 'right') => {
      if (contentSizeNotReached || isTransitioning) {
        return;
      }

      const delta = direction === 'left' ? -1 : 1;
      goToSlide(activeIndex + delta);
    },
    [contentSizeNotReached, isTransitioning, activeIndex, goToSlide],
  );

  // Clear skipTransition after the browser has painted the transition:none frame.
  useEffect(() => {
    if (!navigationState.skipTransition) return;
    const id = window.setTimeout(() => {
      setNavigationState((state) => ({ ...state, skipTransition: false }));
    }, 0);
    return () => window.clearTimeout(id);
  }, [navigationState.skipTransition]);

  return {
    activeIndex,
    isTransitioning,
    skipTransition: navigationState.skipTransition,
    handleSlide,
    goToSlide,
  };
};
