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

// Mock store state
let mockNodes: any[] = [];
let mockEdges: 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,
      setNodes: mockSetNodes,
      setEdges: mockSetEdges,
    };
    return selector(state);
  }),
}));

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

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

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

  beforeEach(() => {
    jest.clearAllMocks();
    mockSetHasUnsavedChanges = jest.fn();
    mockNodes = [
      {
        id: 'event_1',
        type: 'start',
        position: { x: 100, y: 100 },
        width: 200,
        height: 80,
        data: { label: 'Start Event' },
      },
    ];
    mockEdges = [];
  });

  const renderUseQuickAdd = () => {
    return renderHook(() =>
      useQuickAdd({
        setHasUnsavedChanges: mockSetHasUnsavedChanges,
      })
    );
  };

  describe('addSuccessorNode', () => {
    it('should return addSuccessorNode function', () => {
      const { result } = renderUseQuickAdd();
      
      expect(typeof result.current.addSuccessorNode).toBe('function');
    });

    it('should create a new node when addSuccessorNode is called', () => {
      const { result } = renderUseQuickAdd();
      
      const component = {
        plugin: 'action:save',
        label: 'Save Entity',
        category: 'Actions',
      };
      
      act(() => {
        result.current.addSuccessorNode(component, 'event_1');
      });
      
      expect(mockSetNodes).toHaveBeenCalled();
      expect(mockNodes.length).toBe(2);
      expect(mockNodes[1].data.label).toBe('Save Entity');
      expect(mockNodes[1].data.plugin).toBe('action:save');
    });

    it('should create an edge connecting source to new node', () => {
      const { result } = renderUseQuickAdd();
      
      const component = {
        plugin: 'action:save',
        label: 'Save Entity',
        category: 'Actions',
      };
      
      act(() => {
        result.current.addSuccessorNode(component, 'event_1');
      });
      
      expect(mockSetEdges).toHaveBeenCalled();
      expect(mockEdges.length).toBe(1);
      expect(mockEdges[0].source).toBe('event_1');
    });

    it('should position new node below source node', () => {
      const { result } = renderUseQuickAdd();
      
      const component = {
        plugin: 'action:save',
        label: 'Save Entity',
        category: 'Actions',
      };
      
      act(() => {
        result.current.addSuccessorNode(component, 'event_1');
      });
      
      const newNode = mockNodes[1];
      const sourceNode = mockNodes[0];
      
      // New node should be below source
      expect(newNode.position.y).toBeGreaterThan(sourceNode.position.y);
    });

    it('should set correct node type for gateway components', () => {
      const { result } = renderUseQuickAdd();
      
      const component = {
        plugin: 'gateway:exclusive',
        label: 'Exclusive Gateway',
        category: 'Gateways',
        type: 'gateway',
      };
      
      act(() => {
        result.current.addSuccessorNode(component, 'event_1');
      });
      
      expect(mockNodes[1].type).toBe('gateway');
    });

    it('should set correct node type for action components', () => {
      const { result } = renderUseQuickAdd();
      
      const component = {
        plugin: 'action:save',
        label: 'Save Entity',
        category: 'Actions',
      };
      
      act(() => {
        result.current.addSuccessorNode(component, 'event_1');
      });
      
      expect(mockNodes[1].type).toBe('element');
    });

    it('should mark model as having unsaved changes', () => {
      const { result } = renderUseQuickAdd();
      
      const component = {
        plugin: 'action:save',
        label: 'Save Entity',
        category: 'Actions',
      };
      
      act(() => {
        result.current.addSuccessorNode(component, 'event_1');
      });
      
      expect(mockSetHasUnsavedChanges).toHaveBeenCalledWith(true);
    });

    it('should mark the new node as selected and deselect others', () => {
      // Start with a selected node
      mockNodes[0].selected = true;
      const { result } = renderUseQuickAdd();
      
      const component = {
        plugin: 'action:save',
        label: 'Save Entity',
        category: 'Actions',
      };
      
      act(() => {
        result.current.addSuccessorNode(component, 'event_1');
      });
      
      // The new node should be selected
      const newNode = mockNodes[mockNodes.length - 1];
      expect(newNode.selected).toBe(true);
      expect(newNode.data.plugin).toBe('action:save');
      expect(newNode.data.label).toBe('Save Entity');
      
      // The original node should be deselected
      expect(mockNodes[0].selected).toBe(false);
    });

    it('should not create node if source node not found', () => {
      const { result } = renderUseQuickAdd();
      
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
      
      const component = {
        plugin: 'action:save',
        label: 'Save Entity',
        category: 'Actions',
      };
      
      act(() => {
        result.current.addSuccessorNode(component, 'nonexistent_node');
      });
      
      expect(consoleSpy).toHaveBeenCalled();
      expect(mockSetNodes).not.toHaveBeenCalled();
      
      consoleSpy.mockRestore();
    });

    it('should offset position if node already exists at target position', () => {
      // The source node is at position (100, 100) with height 80
      // New node would be placed at approximately (100, 100 + 80 + 150) = (100, 330)
      // Add a node at that exact position to trigger collision detection
      const expectedY = 100 + 80 + 150; // source.y + source.height + NODE_SPACING_Y (150)
      
      mockNodes.push({
        id: 'existing_node',
        type: 'element',
        position: { x: 100, y: expectedY }, // At the expected new node position
        width: 200,
        height: 80,
        data: { label: 'Existing Node' },
      });

      // Connect nodes so they form a flow (edges needed for flow-aware positioning)
      mockEdges = [{ id: 'edge-1', source: 'event_1', target: 'existing_node', data: {} }];
      
      const { result } = renderUseQuickAdd();
      
      const component = {
        plugin: 'action:save',
        label: 'Save Entity',
        category: 'Actions',
      };
      
      act(() => {
        result.current.addSuccessorNode(component, 'event_1');
      });
      
      const newNode = mockNodes[mockNodes.length - 1];
      
      // New node should be offset (moved away from the collision).
      // Flow-aware positioning tries right first (no neighbor flow to block),
      // then down.  Either way it must not sit at the blocked position.
      expect(
        newNode.position.x !== 100 || newNode.position.y !== expectedY
      ).toBe(true);
    });

    it('should use plugin name as label if label not provided', () => {
      const { result } = renderUseQuickAdd();
      
      const component = {
        plugin: 'some_module.some_action',
        label: '', // Empty label to test fallback to plugin ID extraction
        category: 'Actions',
      };
      
      act(() => {
        result.current.addSuccessorNode(component, 'event_1');
      });
      
      // Should extract last part of plugin ID when label is empty
      expect(mockNodes[1].data.label).toBe('some_action');
    });

    it('should deselect existing edges when adding successor node', () => {
      // Start with a selected edge
      mockEdges = [{ id: 'edge-0', source: 'event_1', target: 'other', selected: true, data: {} }];
      const { result } = renderUseQuickAdd();
      
      const component = {
        plugin: 'action:save',
        label: 'Save Entity',
        category: 'Actions',
      };
      
      act(() => {
        result.current.addSuccessorNode(component, 'event_1');
      });
      
      // The previously selected edge should be deselected
      const previousEdge = mockEdges.find(e => e.id === 'edge-0');
      expect(previousEdge!.selected).toBe(false);
      
      // The new edge should not be selected (only the new node is selected)
      const newEdge = mockEdges.find(e => e.id !== 'edge-0');
      expect(newEdge).toBeDefined();
      expect(newEdge!.selected).toBeUndefined();
    });

    it('should shift neighboring flows right when new node would intrude', () => {
      // Two flows side by side with tight spacing
      mockNodes = [
        // Left flow
        { id: 'event_L',  type: 'start', position: { x: 100, y: 100 }, width: 200, height: 80, data: { label: 'Left Event' } },
        { id: 'action_L', type: 'element', position: { x: 100, y: 330 }, width: 200, height: 100, data: { label: 'Left Action' } },
        // Right flow, very close (starts at x=350)
        { id: 'event_R',  type: 'start', position: { x: 350, y: 100 }, width: 200, height: 80, data: { label: 'Right Event' } },
        { id: 'action_R', type: 'element', position: { x: 350, y: 330 }, width: 200, height: 100, data: { label: 'Right Action' } },
      ];
      mockEdges = [
        { id: 'e1', source: 'event_L', target: 'action_L', data: {} },
        { id: 'e2', source: 'event_R', target: 'action_R', data: {} },
      ];

      // Fill the left flow's column so all downward positions are blocked
      const stepY = 100 + 150; // DEFAULT_HEIGHT + NODE_SPACING_Y
      for (let i = 0; i < 55; i++) {
        const id = `filler_${i}`;
        mockNodes.push({
          id,
          type: 'element',
          position: { x: 100, y: 330 + stepY * (i + 1) },
          width: 200,
          height: 100,
          data: { label: `Filler ${i}` },
        });
        mockEdges.push({ id: `ef_${i}`, source: 'action_L', target: id, data: {} });
      }

      const { result } = renderUseQuickAdd();

      const component = {
        plugin: 'action:save',
        label: 'Save Entity',
        category: 'Actions',
      };

      const originalRightEventX = mockNodes.find(n => n.id === 'event_R')!.position.x;

      act(() => {
        result.current.addSuccessorNode(component, 'event_L');
      });

      const rightEventAfter = mockNodes.find(n => n.id === 'event_R')!;
      const rightActionAfter = mockNodes.find(n => n.id === 'action_R')!;

      // The right flow should have been shifted further right
      expect(rightEventAfter.position.x).toBeGreaterThan(originalRightEventX);
      expect(rightActionAfter.position.x).toBeGreaterThan(originalRightEventX);
    });

    it('should include component description and documentationUrl in node data', () => {
      const { result } = renderUseQuickAdd();
      
      const component = {
        plugin: 'action:save',
        label: 'Save Entity',
        category: 'Actions',
        description: 'Saves the entity to the database',
        documentationUrl: 'https://docs.example.com/save',
      };
      
      act(() => {
        result.current.addSuccessorNode(component, 'event_1');
      });
      
      expect(mockNodes[1].data.description).toBe('Saves the entity to the database');
      expect(mockNodes[1].data.documentationUrl).toBe('https://docs.example.com/save');
    });
  });
});
