import { renderHook, act, waitFor } from '@testing-library/react';
import type { RefObject } from 'react';
import {
  useUniformCenteredConfig,
  useSpotlightLayout,
  useSpotlightHeight,
  useSpotlightNavigation,
} from '../hooks';
import {
  partialItemWidth,
  infiniteScrollTimeout,
} from '../../../../consts';
import { useCarouselDimensions } from '../../../../../hooks/useCarouselDimensions';
import type { CarouselDimensions } from '../../../../../hooks/useCarouselDimensions';
import {
  useCompactDesktopWidth,
  getCompactContainerStyle,
} from '../../../../../hooks/useCompactDesktopWidth';
import { useCarouselHeight } from '../../../../../hooks/useCarouselHeight';
import { useCarouselMinHeight } from '../../../../../hooks/useCarouselMinHeight';

jest.mock('../../../../../hooks/useCarouselDimensions', () => ({
  useCarouselDimensions: jest.fn(),
}));

jest.mock('../../../../../hooks/useCompactDesktopWidth', () => ({
  useCompactDesktopWidth: jest.fn(),
  getCompactContainerStyle: jest.fn(),
}));

jest.mock('../../../../../hooks/useCarouselHeight', () => ({
  useCarouselHeight: jest.fn(),
}));

jest.mock('../../../../../hooks/useCarouselMinHeight', () => ({
  useCarouselMinHeight: jest.fn(),
}));

type StoreStateSlice = {
  productListUniformMode?: boolean;
  currentPosition?: number;
};

const createStoreSetter = (initialState: StoreStateSlice) => {
  let state = { ...initialState };
  const setStoreState = jest.fn((updater: ((s: StoreStateSlice) => StoreStateSlice) | StoreStateSlice) => {
    state = typeof updater === 'function' ? updater(state) : updater;
    return state;
  });
  return { setStoreState, getState: () => state };
};

const createSlideRef = (width: number) => {
  const element = document.createElement('div');
  Object.defineProperty(element, 'clientWidth', {
    value: width,
    configurable: true,
  });
  return { current: element } as RefObject<HTMLDivElement>;
};

const createContainerRef = () => {
  const element = document.createElement('div');
  return { current: element } as RefObject<HTMLDivElement>;
};

const mockedUseCarouselDimensions = useCarouselDimensions as jest.MockedFunction<typeof useCarouselDimensions>;
const mockedUseCompactDesktopWidth = useCompactDesktopWidth as jest.MockedFunction<typeof useCompactDesktopWidth>;
const mockedGetCompactContainerStyle = getCompactContainerStyle as jest.MockedFunction<typeof getCompactContainerStyle>;
const mockedUseCarouselHeight = useCarouselHeight as jest.MockedFunction<typeof useCarouselHeight>;
const mockedUseCarouselMinHeight = useCarouselMinHeight as jest.MockedFunction<typeof useCarouselMinHeight>;

describe('SpotlightCarousel hooks', () => {
  beforeAll(() => {
    window.requestAnimationFrame = (callback: FrameRequestCallback): number => window.setTimeout(() => callback(0), 0);
  });

  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('useUniformCenteredConfig', () => {
    it('disables partial width and cloning when content size is not reached', async () => {
      const { setStoreState, getState } = createStoreSetter({ productListUniformMode: false });

      const { result } = renderHook(() =>
        useUniformCenteredConfig(2, 4, setStoreState as unknown as (value: any) => void),
      );

      expect(result.current.contentSizeNotReached).toBe(true);
      expect(result.current.partialItemWidth).toBe(0);
      expect(result.current.cloneCount).toBe(0);

      await waitFor(() => {
        expect(getState().productListUniformMode).toBe(true);
      });
    });

    it('enables cloning when there are enough items', () => {
      const { result } = renderHook(() =>
        useUniformCenteredConfig(5, 4, jest.fn()),
      );

      expect(result.current.contentSizeNotReached).toBe(false);
      expect(result.current.partialItemWidth).toBe(partialItemWidth);
      expect(result.current.cloneCount).toBe(4);
    });
  });

  describe('useSpotlightLayout', () => {
    it('forces medium arrows in compact mode and returns compact styles', () => {
      const mockDimensions: CarouselDimensions = {
        containerWidth: 800,
        arrowSize: 'small',
        arrowSpace: 0,
        contentWidth: 700,
        itemWidth: 140,
      };

      mockedUseCarouselDimensions.mockReturnValue(mockDimensions);
      mockedUseCompactDesktopWidth.mockReturnValue({ width: 520, isCompact: true });
      mockedGetCompactContainerStyle.mockReturnValue({ maxWidth: '520px' });

      const slideRef = createSlideRef(600);

      const { result } = renderHook(() =>
        useSpotlightLayout({
          context: {
            widgetWidth: 900,
            contentSize: 4,
            isMobile: false,
            hideArrows: false,
          },
          config: { contentSize: 4, itemCount: 5, partialItemWidth: 0.4 },
          refs: { slideRef },
        }),
      );

      expect(result.current.arrowSize).toBe('medium');
      expect(result.current.carouselDimensions).toBe(mockDimensions);
      expect(result.current.compactStyle).toEqual({ maxWidth: '520px' });
      expect(mockedUseCarouselDimensions).toHaveBeenCalledWith(expect.objectContaining({ forcedArrowSize: 'medium' }));
    });

    it('uses calculated arrow size when not compact', () => {
      const nonCompactDimensions: CarouselDimensions = {
        containerWidth: 700,
        arrowSize: 'large',
        arrowSpace: 0,
        contentWidth: 600,
        itemWidth: 150,
      };
      mockedUseCarouselDimensions.mockReturnValue(nonCompactDimensions);
      mockedUseCompactDesktopWidth.mockReturnValue({ width: 700, isCompact: false });
      mockedGetCompactContainerStyle.mockReturnValue(undefined);

      const slideRef = createSlideRef(600);

      const { result } = renderHook(() =>
        useSpotlightLayout({
          context: {
            widgetWidth: 700,
            contentSize: 5,
            isMobile: false,
            hideArrows: false,
          },
          config: { contentSize: 5, itemCount: 5, partialItemWidth: 0.4 },
          refs: { slideRef },
        }),
      );

      expect(result.current.arrowSize).toBe('large');
      expect(result.current.compactStyle).toBeUndefined();
      expect(mockedUseCarouselDimensions).toHaveBeenCalledWith(expect.objectContaining({ forcedArrowSize: undefined }));
    });
  });

  describe('useSpotlightHeight', () => {
    it('returns enforced minimum height from useCarouselMinHeight', () => {
      mockedUseCarouselHeight.mockReturnValue(320);
      mockedUseCarouselMinHeight.mockReturnValue(360);

      const outerRef = createContainerRef();

      const { result } = renderHook(() =>
        useSpotlightHeight({
          refs: { outerRef },
          config: { thumbnailShape: 'square', contentSize: 3, itemCount: 3 },
          layout: { isCompact: false, constrainedWidgetWidth: 900, widgetWidth: 900 },
        }),
      );

      expect(result.current.enforcedMinHeight).toBe(360);
      expect(mockedUseCarouselHeight).toHaveBeenCalledWith(expect.objectContaining({ partialItemWidth }));
      expect(mockedUseCarouselMinHeight).toHaveBeenCalledWith(
        outerRef,
        320,
        true,
        '3-900',
      );
    });
  });

  describe('useSpotlightNavigation', () => {
    afterEach(() => {
      jest.useRealTimers();
    });

    it('prevents navigation when content size is not reached', () => {
      const slideRef = createSlideRef(600);
      const containerRef = createContainerRef();
      const { setStoreState } = createStoreSetter({ currentPosition: 0 });

      const { result } = renderHook(() =>
        useSpotlightNavigation({
          config: { itemCount: 2, contentSize: 4, cloneCount: 0, contentSizeNotReached: true },
          refs: { slideRef, containerRef },
          state: { itemWidth: 150, currentPosition: 0 },
          actions: { setStoreState: setStoreState as unknown as (value: any) => void },
        }),
      );

      act(() => {
        result.current.handleSlide('right');
      });

      expect(result.current.activeIndex).toBe(0);
      expect(setStoreState).not.toHaveBeenCalled();
    });

    it('cycles through clones and updates store state after transition', () => {
      jest.useFakeTimers();

      const slideRef = createSlideRef(600);
      const containerRef = createContainerRef();
      const store = createStoreSetter({ currentPosition: 0 });

      const { result } = renderHook(() =>
        useSpotlightNavigation({
          config: { itemCount: 3, contentSize: 3, cloneCount: 3, contentSizeNotReached: false },
          refs: { slideRef, containerRef },
          state: { itemWidth: 150, currentPosition: undefined },
          actions: { setStoreState: store.setStoreState as unknown as (value: any) => void },
        }),
      );

      act(() => {
        result.current.handleSlide('right');
      });

      expect(result.current.isTransitioning).toBe(true);

      act(() => {
        jest.advanceTimersByTime(infiniteScrollTimeout + 5);
      });

      expect(result.current.activeIndex).toBe(1);
      expect(store.getState().currentPosition).toBe(1);
    });
  });
});
