import {
  RefObject, UIEvent, useCallback, useRef, useState,
} from 'react';
import type { ContextType } from 'react';
import { StoreContext } from '../../../../services/store';
import type { IWidgetEvents } from '../../../../types/events';
import { useScrollEnd } from '../../../../hooks/useScrollEnd';

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

interface MobileScrollSyncParams {
  scrollContainerRef: RefObject<HTMLDivElement>;
  itemCount: number;
  cloneCount: number;
  visibleContentSize: number;
  setStoreState: StoreStateUpdater;
  triggerEvent: IWidgetEvents['triggerEvent'];
  widgetId: string;
}

/**
 * Calculate item width and shoulder visibility for a mobile carousel.
 * - 1 item is centered and uses 83.33% width so the remaining space acts as shoulders.
 * - 2+ items use 90% total width for items, with 5% shoulder padding on each side.
 */
export const useMobileCarouselDimensions = (visibleContentSize: number) => {
  if (visibleContentSize === 1) {
    return {
      itemWidthPercentage: 100 / 1.2, // 1 item = 83.33%, leaves room for shoulders
      shoulderPercentage: 5,
    };
  }

  const totalItemSpace = 90; // 2 + items = 90%, leaves 10% for shoulders
  const itemWidthPercentage = totalItemSpace / visibleContentSize;
  const shoulderPercentage = 5;

  return {
    itemWidthPercentage,
    shoulderPercentage,
  };
};

/**
 * Handles scroll synchronization, position tracking, and infinite loop wrapping
 */
export const useMobileScrollSync = ({
  scrollContainerRef,
  itemCount,
  cloneCount,
  visibleContentSize,
  setStoreState,
  triggerEvent,
  widgetId,
}: MobileScrollSyncParams) => {
  const [activeIndex, setActiveIndex] = useState(0);
  const lastScrollLeft = useRef(0);
  const isWrappingRef = useRef(false);
  const scrollDirectionRef = useRef<'left' | 'right'>('right');
  const lastNativeEventRef = useRef<Event | null>(null);
  // Track the last real index we dispatched so we only update state when it changes.
  const lastReportedRealIndex = useRef(-1);

  // Return the active item index in the full scroll strip, including clones.
  const getCurrentIndex = useCallback(() => {
    if (!scrollContainerRef.current) return 0;

    const container = scrollContainerRef.current;
    const { scrollWidth } = container;
    const { scrollLeft } = container;
    const containerWidth = container.clientWidth;

    // Calculate total items including clones (no spacers)
    const totalItems = cloneCount > 0 ? itemCount + 2 * cloneCount : itemCount;
    const itemWidth = scrollWidth / totalItems;

    if (visibleContentSize > 1) {
      // Take in account the 5% shoulder offset to get a stable snapped index.
      const shoulderOffset = containerWidth * 0.05;
      return Math.round((scrollLeft + shoulderOffset) / itemWidth);
    }

    // Find the centered/first visible item index based on scroll position
    const centerPosition = scrollLeft + containerWidth / 2;
    return Math.floor(centerPosition / itemWidth);
  }, [scrollContainerRef, itemCount, cloneCount, visibleContentSize]);

  // Track direction and last scroll position
  const handleScroll = useCallback((event: UIEvent<HTMLDivElement>) => {
    if (!scrollContainerRef.current || cloneCount === 0) {
      return;
    }

    // Ignore scroll events triggered by the wrap-back jump so that
    // only genuine user gestures can update state and send events.
    if (isWrappingRef.current) {
      return;
    }

    const container = scrollContainerRef.current;
    scrollDirectionRef.current = container.scrollLeft >= lastScrollLeft.current ? 'right' : 'left';
    lastNativeEventRef.current = event.nativeEvent;
    lastScrollLeft.current = container.scrollLeft;
  }, [scrollContainerRef, cloneCount, isWrappingRef]);

  // When the gesture has fully settled, fire one scroll event and jump back from any clone zone.
  useScrollEnd(scrollContainerRef, () => {
    // Ignore programmatic scrolls by wrap-back logic
    if (isWrappingRef.current) {
      isWrappingRef.current = false;
      return;
    }

    const container = scrollContainerRef.current;
    if (!container) return;

    const { scrollWidth } = container;
    const totalItems = itemCount + 2 * cloneCount;
    const itemWidth = scrollWidth / totalItems;
    const currentIdx = getCurrentIndex();
    const realIndex = (((currentIdx - cloneCount) % itemCount) + itemCount) % itemCount;

    // Commit the settled position to the store
    if (realIndex !== lastReportedRealIndex.current) {
      lastReportedRealIndex.current = realIndex;
      setActiveIndex(realIndex);
      setStoreState((state) => {
        if (state.currentPosition === realIndex) return state;
        return { ...state, currentPosition: realIndex };
      });
    }

    if (lastNativeEventRef.current) {
      triggerEvent(
        'carouselNativeScroll',
        {
          position: realIndex,
          direction: scrollDirectionRef.current,
          originalEvent: lastNativeEventRef.current,
        },
        widgetId,
      );
    }

    // Jump back from clone zones to the equivalent real position.
    const inStartClone = currentIdx < cloneCount;
    const inEndClone = currentIdx >= cloneCount + itemCount;

    if (inStartClone || inEndClone) {
      const targetScrollIndex = cloneCount + realIndex;
      const halfItem = itemWidth / 2;
      const targetScrollLeft = targetScrollIndex * itemWidth - container.clientWidth / 2 + halfItem;

      container.style.scrollBehavior = 'auto';
      isWrappingRef.current = true;
      container.scrollLeft = targetScrollLeft;

      requestAnimationFrame(() => {
        if (container) {
          container.style.scrollBehavior = 'smooth';
        }
      });
    }
  });

  return {
    activeIndex,
    handleScroll,
    isWrappingRef,
  };
};
