import { renderHook, act } from '@testing-library/react';
import { useDragAndDrop } from '../useDragAndDrop';

// Mock store state
let mockNodes: any[] = [];
let mockEdges: any[] = [];
let mockComponents: any[] = [];

const mockSetNodes = jest.fn((callback) => {
  if (typeof callback === 'function') {
    mockNodes = callback(mockNodes);
  } else {
    mockNodes = callback;
  }
});

const mockSetEdges = jest.fn((callback) => {
  if (typeof callback === 'function') {
    mockEdges = callback(mockEdges);
  } else {
    mockEdges = callback;
  }
});

jest.mock('../../store/store', () => ({
  useStore: jest.fn((selector) => {
    const state = {
      nodes: mockNodes,
      edges: mockEdges,
      components: mockComponents,
      setNodes: mockSetNodes,
      setEdges: mockSetEdges,
    };
    return selector(state);
  }),
}));

// Mock ReactFlow
const mockScreenToFlowPosition = jest.fn((pos) => pos);

jest.mock('reactflow', () => ({
  useReactFlow: () => ({
    screenToFlowPosition: mockScreenToFlowPosition,
  }),
}));

// Mock utility functions
jest.mock('../../utils/clipboardUtils', () => ({
  generateNodeId: jest.fn((label, type) => `${type}_${label.toLowerCase().replace(/\s+/g, '_')}_1`),
}));

jest.mock('../../utils/modelUtils', () => ({
  getComponentTypeFromCategory: jest.fn((category) => {
    switch (category) {
      case 'Events': return 1;
      case 'Actions': return 4;
      default: return 0;
    }
  }),
}));

describe('useDragAndDrop', () => {
  let mockSetHasUnsavedChanges: jest.Mock;

  beforeEach(() => {
    jest.clearAllMocks();
    mockSetHasUnsavedChanges = jest.fn();
    mockNodes = [
      {
        id: 'node-1',
        position: { x: 100, y: 100 },
        width: 200,
        height: 100,
        data: { label: 'Node 1' },
      },
      {
        id: 'node-2',
        position: { x: 400, y: 100 },
        width: 200,
        height: 100,
        data: { label: 'Node 2' },
      },
    ];
    mockEdges = [
      {
        id: 'edge-1',
        source: 'node-1',
        target: 'node-2',
        data: {},
      },
    ];
    mockComponents = [];
  });

  const renderUseDragAndDrop = (isLocked = false) => {
    return renderHook(() =>
      useDragAndDrop({
        isLocked,
        setHasUnsavedChanges: mockSetHasUnsavedChanges,
      })
    );
  };

  describe('initial state', () => {
    it('should return initial state values', () => {
      const { result } = renderUseDragAndDrop();

      expect(result.current.isDraggingCondition).toBe(false);
      expect(result.current.hoveredDropEdge).toBeNull();
      expect(result.current.draggedComponentType).toBeNull();
      expect(result.current.isDragActive).toBe(false);
    });

    it('should return all required handlers', () => {
      const { result } = renderUseDragAndDrop();

      expect(typeof result.current.onDrop).toBe('function');
      expect(typeof result.current.onDragOver).toBe('function');
      expect(typeof result.current.onComponentDrag).toBe('function');
      expect(typeof result.current.onComponentDragEnd).toBe('function');
      expect(typeof result.current.onComponentDragStart).toBe('function');
      expect(typeof result.current.onConditionDragStart).toBe('function');
      expect(typeof result.current.onConditionDragEnd).toBe('function');
      expect(typeof result.current.findNearestEdge).toBe('function');
    });
  });

  describe('findNearestEdge', () => {
    it('should find nearest edge to position', () => {
      const { result } = renderUseDragAndDrop();

      // Position near the midpoint of edge-1 (between node-1 at 200,150 center and node-2 at 500,150 center)
      // Midpoint is approximately (350, 150)
      const nearestEdge = result.current.findNearestEdge({ x: 350, y: 150 });

      expect(nearestEdge).toBeDefined();
      expect(nearestEdge?.id).toBe('edge-1');
    });

    it('should return null when no edge within max distance', () => {
      const { result } = renderUseDragAndDrop();

      // Position far from any edge
      const nearestEdge = result.current.findNearestEdge({ x: 1000, y: 1000 });

      expect(nearestEdge).toBeNull();
    });

    it('should return null when edges array is empty', () => {
      mockEdges = [];
      const { result } = renderUseDragAndDrop();

      const nearestEdge = result.current.findNearestEdge({ x: 350, y: 150 });

      expect(nearestEdge).toBeNull();
    });

    it('should respect maxDistance parameter', () => {
      const { result } = renderUseDragAndDrop();

      // Position that's 100px away from edge midpoint
      const nearestEdgeSmallRadius = result.current.findNearestEdge({ x: 450, y: 150 }, 50);
      expect(nearestEdgeSmallRadius).toBeNull();

      const nearestEdgeLargeRadius = result.current.findNearestEdge({ x: 450, y: 150 }, 200);
      expect(nearestEdgeLargeRadius?.id).toBe('edge-1');
    });

    it('should use provided edges and nodes arrays', () => {
      const { result } = renderUseDragAndDrop();

      const customNodes = [
        { id: 'custom-1', position: { x: 0, y: 0 }, width: 100, height: 50, data: {} },
        { id: 'custom-2', position: { x: 200, y: 0 }, width: 100, height: 50, data: {} },
      ];
      const customEdges = [
        { id: 'custom-edge', source: 'custom-1', target: 'custom-2' },
      ];

      // Midpoint is at (150, 25)
      const nearestEdge = result.current.findNearestEdge(
        { x: 150, y: 25 },
        80,
        customEdges,
        customNodes
      );

      expect(nearestEdge?.id).toBe('custom-edge');
    });
  });

  describe('onDrop', () => {
    const createMockDragEvent = (data: Record<string, string>) => {
      return {
        preventDefault: jest.fn(),
        clientX: 300,
        clientY: 200,
        dataTransfer: {
          getData: jest.fn((key) => data[key] || ''),
        },
      } as unknown as React.DragEvent;
    };

    it('should prevent default behavior', () => {
      const { result } = renderUseDragAndDrop();
      const event = createMockDragEvent({
        'application/reactflow': 'element',
        'application/plugin': 'test_plugin',
        'component': JSON.stringify({ label: 'Test', category: 'Actions' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      expect(event.preventDefault).toHaveBeenCalled();
    });

    it('should not create node when canvas is locked', () => {
      const { result } = renderUseDragAndDrop(true); // locked = true
      const event = createMockDragEvent({
        'application/reactflow': 'element',
        'application/plugin': 'test_plugin',
        'component': JSON.stringify({ label: 'Test', category: 'Actions' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      expect(mockSetNodes).not.toHaveBeenCalled();
    });

    it('should create node on drop with correct data', () => {
      const { result } = renderUseDragAndDrop();
      const event = createMockDragEvent({
        'application/reactflow': 'element',
        'application/plugin': 'action:test',
        'component': JSON.stringify({ label: 'Test Action', category: 'Actions' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      expect(mockSetNodes).toHaveBeenCalled();
      expect(mockSetHasUnsavedChanges).toHaveBeenCalledWith(true);
    });

    it('should create start node for start type', () => {
      const { result } = renderUseDragAndDrop();
      const event = createMockDragEvent({
        'application/reactflow': 'start',
        'application/plugin': 'event:test',
        'component': JSON.stringify({ label: 'Test Event', category: 'Events' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      expect(mockSetNodes).toHaveBeenCalled();
      const nodesCall = mockSetNodes.mock.calls[0][0];
      const newNode = typeof nodesCall === 'function'
        ? nodesCall(mockNodes)[mockNodes.length]
        : nodesCall[nodesCall.length - 1];
      expect(newNode.type).toBe('start');
    });

    it('should create gateway node for gateway type', () => {
      const { result } = renderUseDragAndDrop();
      const event = createMockDragEvent({
        'application/reactflow': 'gateway',
        'application/plugin': 'gateway',
        'component': JSON.stringify({ label: 'Gateway', category: 'Others' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      expect(mockSetNodes).toHaveBeenCalled();
      const nodesCall = mockSetNodes.mock.calls[0][0];
      const newNode = typeof nodesCall === 'function'
        ? nodesCall(mockNodes)[mockNodes.length]
        : nodesCall[nodesCall.length - 1];
      expect(newNode.type).toBe('gateway');
    });

    it('should mark the dropped node as selected and deselect existing nodes', () => {
      // Pre-select an existing node
      mockNodes[0].selected = true;
      const { result } = renderUseDragAndDrop();
      const event = createMockDragEvent({
        'application/reactflow': 'element',
        'application/plugin': 'action:test',
        'component': JSON.stringify({ label: 'Test Action', category: 'Actions' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      // The new node (last in array) should be selected
      const newNode = mockNodes[mockNodes.length - 1];
      expect(newNode.selected).toBe(true);
      expect(newNode.data.plugin).toBe('action:test');
      
      // Existing nodes should be deselected
      expect(mockNodes[0].selected).toBe(false);
    });

    it('should deselect existing edges when dropping a node', () => {
      mockEdges[0].selected = true;
      const { result } = renderUseDragAndDrop();
      const event = createMockDragEvent({
        'application/reactflow': 'element',
        'application/plugin': 'action:test',
        'component': JSON.stringify({ label: 'Test Action', category: 'Actions' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      // The previously selected edge should be deselected
      expect(mockEdges[0].selected).toBe(false);
    });

    it('should not drop when type or plugin is missing', () => {
      const { result } = renderUseDragAndDrop();
      const event = createMockDragEvent({
        'component': JSON.stringify({ label: 'Test' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      expect(mockSetNodes).not.toHaveBeenCalled();
    });
  });

  describe('onDragOver', () => {
    it('should prevent default and set drop effect', () => {
      const { result } = renderUseDragAndDrop();
      const event = {
        preventDefault: jest.fn(),
        clientX: 300,
        clientY: 200,
        dataTransfer: {
          dropEffect: '',
        },
      } as unknown as React.DragEvent;

      act(() => {
        result.current.onDragOver(event);
      });

      expect(event.preventDefault).toHaveBeenCalled();
      expect(event.dataTransfer.dropEffect).toBe('move');
    });
  });

  describe('onComponentDrag', () => {
    it('should set drag data on dataTransfer', () => {
      const { result } = renderUseDragAndDrop();
      const component = {
        type: 'element',
        plugin: 'action:test',
        label: 'Test Action',
        category: 'Actions',
      };
      const event = {
        dataTransfer: {
          setData: jest.fn(),
          effectAllowed: '',
        },
      } as unknown as React.DragEvent;

      act(() => {
        result.current.onComponentDrag(event, component);
      });

      expect(event.dataTransfer.setData).toHaveBeenCalledWith('application/reactflow', 'element');
      expect(event.dataTransfer.setData).toHaveBeenCalledWith('application/plugin', 'action:test');
      expect(event.dataTransfer.setData).toHaveBeenCalledWith('component', JSON.stringify(component));
      expect(event.dataTransfer.effectAllowed).toBe('move');
    });

    it('should set isDragActive to true', () => {
      const { result } = renderUseDragAndDrop();
      const event = {
        dataTransfer: {
          setData: jest.fn(),
          effectAllowed: '',
        },
      } as unknown as React.DragEvent;

      expect(result.current.isDragActive).toBe(false);

      act(() => {
        result.current.onComponentDrag(event, { type: 'element', plugin: 'test' });
      });

      expect(result.current.isDragActive).toBe(true);
    });
  });

  describe('onComponentDragEnd', () => {
    it('should reset drag state', () => {
      const { result } = renderUseDragAndDrop();

      // First, start dragging
      act(() => {
        result.current.onComponentDrag(
          { dataTransfer: { setData: jest.fn(), effectAllowed: '' } } as any,
          { type: 'element', plugin: 'test' }
        );
      });

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

      // Then end drag
      act(() => {
        result.current.onComponentDragEnd();
      });

      expect(result.current.isDragActive).toBe(false);
      expect(result.current.draggedComponentType).toBeNull();
    });
  });

  describe('onComponentDragStart', () => {
    it('should set draggedComponentType', () => {
      const { result } = renderUseDragAndDrop();
      const component = { type: 'element', category: 'Actions' };

      act(() => {
        result.current.onComponentDragStart(component);
      });

      expect(result.current.draggedComponentType).toEqual({
        type: 'element',
        category: 'Actions',
      });
    });

    it('should set isDraggingCondition for Conditions category', () => {
      const { result } = renderUseDragAndDrop();

      act(() => {
        result.current.onComponentDragStart({ category: 'Conditions' });
      });

      expect(result.current.isDraggingCondition).toBe(true);
    });

    it('should set isDraggingCondition for Decisions category', () => {
      const { result } = renderUseDragAndDrop();

      act(() => {
        result.current.onComponentDragStart({ category: 'Decisions' });
      });

      expect(result.current.isDraggingCondition).toBe(true);
    });

    it('should not set isDraggingCondition for other categories', () => {
      const { result } = renderUseDragAndDrop();

      act(() => {
        result.current.onComponentDragStart({ category: 'Actions' });
      });

      expect(result.current.isDraggingCondition).toBe(false);
    });
  });

  describe('condition drag handlers', () => {
    it('onConditionDragStart should set isDraggingCondition', () => {
      const { result } = renderUseDragAndDrop();

      act(() => {
        result.current.onConditionDragStart();
      });

      expect(result.current.isDraggingCondition).toBe(true);
    });

    it('onConditionDragEnd should reset condition drag state', () => {
      const { result } = renderUseDragAndDrop();

      // Start condition drag
      act(() => {
        result.current.onConditionDragStart();
      });

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

      // End condition drag
      act(() => {
        result.current.onConditionDragEnd();
      });

      expect(result.current.isDraggingCondition).toBe(false);
      expect(result.current.hoveredDropEdge).toBeNull();
    });
  });

  describe('condition drop on edge', () => {
    const createMockDragEvent = (data: Record<string, string>) => {
      return {
        preventDefault: jest.fn(),
        clientX: 350,
        clientY: 150, // Near the midpoint of edge-1
        dataTransfer: {
          getData: jest.fn((key) => data[key] || ''),
        },
      } as unknown as React.DragEvent;
    };

    it('should attach condition to nearest edge when dropping Conditions category', () => {
      const { result } = renderUseDragAndDrop();

      const event = createMockDragEvent({
        'application/reactflow': 'element',
        'application/plugin': 'condition:is_new',
        'component': JSON.stringify({ label: 'Is New', category: 'Conditions' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      // Should update edge (and deselect nodes), not create new node
      expect(mockSetEdges).toHaveBeenCalled();
      // setNodes is called to deselect existing nodes, but no new node is added
      expect(mockNodes.length).toBe(2);
      expect(mockSetHasUnsavedChanges).toHaveBeenCalledWith(true);
    });

    it('should attach condition to nearest edge when dropping Decisions category', () => {
      const { result } = renderUseDragAndDrop();

      const event = createMockDragEvent({
        'application/reactflow': 'element',
        'application/plugin': 'decision:check',
        'component': JSON.stringify({ label: 'Check Value', category: 'Decisions' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      expect(mockSetEdges).toHaveBeenCalled();
      expect(mockSetHasUnsavedChanges).toHaveBeenCalledWith(true);
    });

    it('should show alert when dropping condition far from any edge', () => {
      const alertSpy = jest.spyOn(window, 'alert').mockImplementation(() => {});

      const { result } = renderUseDragAndDrop();

      const event = {
        preventDefault: jest.fn(),
        clientX: 1000,
        clientY: 1000, // Far from any edge
        dataTransfer: {
          getData: jest.fn((key: string) => {
            if (key === 'application/reactflow') return 'element';
            if (key === 'application/plugin') return 'condition:test';
            if (key === 'component') return JSON.stringify({ label: 'Test', category: 'Conditions' });
            return '';
          }),
        },
      } as unknown as React.DragEvent;

      act(() => {
        result.current.onDrop(event);
      });

      expect(alertSpy).toHaveBeenCalledWith(expect.stringContaining('Decision components can only be attached'));
      expect(mockSetEdges).not.toHaveBeenCalled();

      alertSpy.mockRestore();
    });

    it('should show alert when dropping condition on locked edge', () => {
      mockEdges = [
        {
          id: 'edge-1',
          source: 'node-1',
          target: 'node-2',
          data: { locked: true },
        },
      ];

      const alertSpy = jest.spyOn(window, 'alert').mockImplementation(() => {});

      const { result } = renderUseDragAndDrop();

      const event = {
        preventDefault: jest.fn(),
        clientX: 350,
        clientY: 150,
        dataTransfer: {
          getData: jest.fn((key: string) => {
            if (key === 'application/reactflow') return 'element';
            if (key === 'application/plugin') return 'condition:test';
            if (key === 'component') return JSON.stringify({ label: 'Test', category: 'Conditions' });
            return '';
          }),
        },
      } as unknown as React.DragEvent;

      act(() => {
        result.current.onDrop(event);
      });

      expect(alertSpy).toHaveBeenCalledWith(expect.stringContaining('Cannot attach conditions to locked connections'));

      alertSpy.mockRestore();
    });

    it('should set edge type to condition when adding condition', () => {
      const { result } = renderUseDragAndDrop();

      const event = createMockDragEvent({
        'application/reactflow': 'element',
        'application/plugin': 'condition:is_new',
        'component': JSON.stringify({ label: 'Is New', category: 'Conditions' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      const edgesCallback = mockSetEdges.mock.calls[0][0];
      const updatedEdges = typeof edgesCallback === 'function' ? edgesCallback(mockEdges) : edgesCallback;
      const updatedEdge = updatedEdges.find((e: any) => e.id === 'edge-1');

      expect(updatedEdge.type).toBe('condition');
      expect(updatedEdge.data.condition).toBe('condition:is_new');
      expect(updatedEdge.data.conditionLabel).toBe('Is New');
    });

    it('should preserve annotation type when adding condition to edge with annotation', () => {
      mockEdges = [
        {
          id: 'edge-1',
          source: 'node-1',
          target: 'node-2',
          data: { annotation: 'Some note' },
        },
      ];

      const { result } = renderUseDragAndDrop();

      const event = createMockDragEvent({
        'application/reactflow': 'element',
        'application/plugin': 'condition:test',
        'component': JSON.stringify({ label: 'Test', category: 'Conditions' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      const edgesCallback = mockSetEdges.mock.calls[0][0];
      const updatedEdges = typeof edgesCallback === 'function' ? edgesCallback(mockEdges) : edgesCallback;
      const updatedEdge = updatedEdges.find((e: any) => e.id === 'edge-1');

      expect(updatedEdge.type).toBe('condition');
    });

    it('should mark the target edge as selected when dropping condition', () => {
      const { result } = renderUseDragAndDrop();

      const event = createMockDragEvent({
        'application/reactflow': 'element',
        'application/plugin': 'condition:is_new',
        'component': JSON.stringify({ label: 'Is New', category: 'Conditions' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      const edgesCallback = mockSetEdges.mock.calls[0][0];
      const updatedEdges = typeof edgesCallback === 'function' ? edgesCallback(mockEdges) : edgesCallback;
      const targetEdge = updatedEdges.find((e: any) => e.id === 'edge-1');
      expect(targetEdge.selected).toBe(true);
    });

    it('should deselect all nodes when dropping condition on edge', () => {
      // Pre-select a node
      mockNodes[0].selected = true;
      const { result } = renderUseDragAndDrop();

      const event = createMockDragEvent({
        'application/reactflow': 'element',
        'application/plugin': 'condition:is_new',
        'component': JSON.stringify({ label: 'Is New', category: 'Conditions' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      // Nodes should be deselected (setNodes called to deselect)
      expect(mockNodes[0].selected).toBe(false);
    });

    it('should deselect other edges when dropping condition on edge', () => {
      // Add a second edge that is pre-selected
      mockEdges.push({
        id: 'edge-2',
        source: 'node-2',
        target: 'node-1',
        data: {},
        selected: true,
      });

      const { result } = renderUseDragAndDrop();

      const event = createMockDragEvent({
        'application/reactflow': 'element',
        'application/plugin': 'condition:is_new',
        'component': JSON.stringify({ label: 'Is New', category: 'Conditions' }),
      });

      act(() => {
        result.current.onDrop(event);
      });

      const edgesCallback = mockSetEdges.mock.calls[0][0];
      const updatedEdges = typeof edgesCallback === 'function' ? edgesCallback(mockEdges) : edgesCallback;
      // Target edge should be selected
      const targetEdge = updatedEdges.find((e: any) => e.id === 'edge-1');
      expect(targetEdge.selected).toBe(true);
      // Other edge should be deselected
      const otherEdge = updatedEdges.find((e: any) => e.id === 'edge-2');
      expect(otherEdge.selected).toBe(false);
    });
  });

  describe('onDragOver with condition dragging', () => {
    it('should update hoveredDropEdge when dragging condition over an edge', () => {
      const { result } = renderUseDragAndDrop();

      // Start condition drag
      act(() => {
        result.current.onConditionDragStart();
      });

      const event = {
        preventDefault: jest.fn(),
        clientX: 350,
        clientY: 150, // Near edge midpoint
        dataTransfer: {
          dropEffect: '',
        },
      } as unknown as React.DragEvent;

      act(() => {
        result.current.onDragOver(event);
      });

      expect(result.current.hoveredDropEdge).toBeDefined();
      expect(result.current.hoveredDropEdge?.id).toBe('edge-1');
    });

    it('should clear hoveredDropEdge when dragging away from edges', () => {
      const { result } = renderUseDragAndDrop();

      // Start condition drag and hover over edge
      act(() => {
        result.current.onConditionDragStart();
      });

      act(() => {
        result.current.onDragOver({
          preventDefault: jest.fn(),
          clientX: 350,
          clientY: 150,
          dataTransfer: { dropEffect: '' },
        } as unknown as React.DragEvent);
      });

      expect(result.current.hoveredDropEdge).not.toBeNull();

      // Drag away from edge
      act(() => {
        result.current.onDragOver({
          preventDefault: jest.fn(),
          clientX: 1000,
          clientY: 1000,
          dataTransfer: { dropEffect: '' },
        } as unknown as React.DragEvent);
      });

      expect(result.current.hoveredDropEdge).toBeNull();
    });

    it('should not update hoveredDropEdge for locked edges', () => {
      mockEdges = [
        {
          id: 'edge-1',
          source: 'node-1',
          target: 'node-2',
          data: { locked: true },
        },
      ];

      const { result } = renderUseDragAndDrop();

      act(() => {
        result.current.onConditionDragStart();
      });

      const event = {
        preventDefault: jest.fn(),
        clientX: 350,
        clientY: 150,
        dataTransfer: { dropEffect: '' },
      } as unknown as React.DragEvent;

      act(() => {
        result.current.onDragOver(event);
      });

      expect(result.current.hoveredDropEdge).toBeNull();
    });

    it('should not check edges when not dragging a condition', () => {
      const { result } = renderUseDragAndDrop();

      const event = {
        preventDefault: jest.fn(),
        clientX: 350,
        clientY: 150,
        dataTransfer: { dropEffect: '' },
      } as unknown as React.DragEvent;

      act(() => {
        result.current.onDragOver(event);
      });

      // hoveredDropEdge should remain null since we're not in condition drag mode
      expect(result.current.hoveredDropEdge).toBeNull();
    });
  });

  describe('findNearestEdge edge cases', () => {
    it('should handle edges with missing source node', () => {
      mockEdges = [
        {
          id: 'edge-orphan',
          source: 'missing-node',
          target: 'node-2',
        },
      ];

      const { result } = renderUseDragAndDrop();

      const nearestEdge = result.current.findNearestEdge({ x: 350, y: 150 });
      expect(nearestEdge).toBeNull();
    });

    it('should handle edges with missing target node', () => {
      mockEdges = [
        {
          id: 'edge-orphan',
          source: 'node-1',
          target: 'missing-node',
        },
      ];

      const { result } = renderUseDragAndDrop();

      const nearestEdge = result.current.findNearestEdge({ x: 350, y: 150 });
      expect(nearestEdge).toBeNull();
    });
  });

  describe('label extraction from plugin', () => {
    it('should extract label from plugin when no label provided', () => {
      const { result } = renderUseDragAndDrop();
      const event = {
        preventDefault: jest.fn(),
        clientX: 300,
        clientY: 200,
        dataTransfer: {
          getData: jest.fn((key: string) => {
            if (key === 'application/reactflow') return 'element';
            if (key === 'application/plugin') return 'eca_base.action_test';
            if (key === 'component') return JSON.stringify({ category: 'Actions' }); // No label
            return '';
          }),
        },
      } as unknown as React.DragEvent;

      act(() => {
        result.current.onDrop(event);
      });

      expect(mockSetNodes).toHaveBeenCalled();
    });

    it('should use plugin as fallback label', () => {
      const { result } = renderUseDragAndDrop();
      const event = {
        preventDefault: jest.fn(),
        clientX: 300,
        clientY: 200,
        dataTransfer: {
          getData: jest.fn((key: string) => {
            if (key === 'application/reactflow') return 'element';
            if (key === 'application/plugin') return 'simple_plugin';
            if (key === 'component') return JSON.stringify({ category: 'Actions' });
            return '';
          }),
        },
      } as unknown as React.DragEvent;

      act(() => {
        result.current.onDrop(event);
      });

      expect(mockSetNodes).toHaveBeenCalled();
    });
  });
});
