import React from 'react';
import { renderHook, act } from '@testing-library/react-native';
import {
  DragStateProvider,
  useDragState,
} from '../../contexts/DragStateContext';
import { DEFAULT_DRAG_CONFIG } from '../../constants';
import { createMockFlashListRef } from '../utils/test-utils';

describe('DragStateContext', () => {
  describe('useDragState', () => {
    it('throws when used outside DragStateProvider', async () => {
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();

      await expect(renderHook(() => useDragState())).rejects.toThrow(
        'useDragState must be used within DragStateProvider',
      );

      consoleSpy.mockRestore();
    });

    it('provides context value when used within DragStateProvider', async () => {
      const wrapper = ({ children }: { children: React.ReactNode }) => (
        <DragStateProvider>{children}</DragStateProvider>
      );

      const { result } = await renderHook(() => useDragState(), { wrapper });

      expect(result.current).toBeDefined();
      expect(result.current.isDragging).toBeDefined();
      expect(result.current.draggedIndex).toBeDefined();
      expect(result.current.draggedItemId).toBeDefined();
    });
  });

  describe('DragStateProvider', () => {
    const createWrapper = (config?: Parameters<typeof DragStateProvider>[0]['config']) => {
      const Wrapper = ({ children }: { children: React.ReactNode }) => (
        <DragStateProvider config={config}>{children}</DragStateProvider>
      );
      return Wrapper;
    };

    it('initializes SharedValues with default values', async () => {
      const { result } = await renderHook(() => useDragState(), {
        wrapper: createWrapper(),
      });

      expect(result.current.isDragging.value).toBe(false);
      expect(result.current.draggedIndex.value).toBe(-1);
      expect(result.current.draggedItemId.value).toBe('');
      expect(result.current.currentTranslateY.value).toBe(0);
      expect(result.current.draggedScale.value).toBe(1);
      expect(result.current.scrollOffset.value).toBe(0);
      expect(result.current.contentHeight.value).toBe(0);
      expect(result.current.visibleHeight.value).toBe(0);
      expect(result.current.measuredItemHeight.value).toBe(0);
      expect(result.current.isDropping.value).toBe(false);
    });

    it('uses default drag config', async () => {
      const { result } = await renderHook(() => useDragState(), {
        wrapper: createWrapper(),
      });

      expect(result.current.config).toEqual(DEFAULT_DRAG_CONFIG);
    });

    it('merges custom config with defaults', async () => {
      const customConfig = {
        longPressDuration: 500,
        dragScale: 1.1,
      };

      const { result } = await renderHook(() => useDragState(), {
        wrapper: createWrapper(customConfig),
      });

      expect(result.current.config.longPressDuration).toBe(500);
      expect(result.current.config.dragScale).toBe(1.1);
      // Should still have other default values
      expect(result.current.config.estimatedItemHeight).toBe(
        DEFAULT_DRAG_CONFIG.estimatedItemHeight,
      );
    });

    it('provides setListRef function', async () => {
      const { result } = await renderHook(() => useDragState(), {
        wrapper: createWrapper(),
      });

      expect(typeof result.current.setListRef).toBe('function');
    });

    it('provides scrollToOffset function', async () => {
      const { result } = await renderHook(() => useDragState(), {
        wrapper: createWrapper(),
      });

      expect(typeof result.current.scrollToOffset).toBe('function');
    });

    it('scrollToOffset calls the ref scrollToOffset when ref is set', async () => {
      const { result } = await renderHook(() => useDragState(), {
        wrapper: createWrapper(),
      });

      const mockRef = createMockFlashListRef();

      await act(async () => {
        result.current.setListRef(mockRef as any);
      });

      await act(async () => {
        result.current.scrollToOffset(100, true);
      });

      expect(mockRef.scrollToOffset).toHaveBeenCalledWith({
        offset: 100,
        animated: true,
      });
    });

    it('scrollToOffset does not crash when ref is null', async () => {
      const { result } = await renderHook(() => useDragState(), {
        wrapper: createWrapper(),
      });

      // Should not throw when ref is not set
      await expect(
        act(async () => {
          result.current.scrollToOffset(100);
        }),
      ).resolves.not.toThrow();
    });

    it('resetDragState resets all values to defaults', async () => {
      const { result } = await renderHook(() => useDragState(), {
        wrapper: createWrapper(),
      });

      // Simulate drag state
      await act(async () => {
        result.current.isDragging.value = true;
        result.current.draggedIndex.value = 2;
        result.current.draggedItemId.value = 'item-2';
        result.current.currentTranslateY.value = 100;
        result.current.draggedScale.value = 1.05;
        result.current.measuredItemHeight.value = 80;
        result.current.isDropping.value = true;
      });

      // Reset
      await act(async () => {
        result.current.resetDragState();
      });

      // Verify reset
      expect(result.current.isDragging.value).toBe(false);
      expect(result.current.draggedIndex.value).toBe(-1);
      expect(result.current.draggedItemId.value).toBe('');
      expect(result.current.currentTranslateY.value).toBe(0);
      expect(result.current.draggedScale.value).toBe(1);
      expect(result.current.measuredItemHeight.value).toBe(0);
      expect(result.current.isDropping.value).toBe(false);
    });

  });
});
