/**
 * useNodeCallbacks Hook Tests
 *
 * Tests the node callbacks hook including:
 * - Adding callbacks to nodes
 * - Display nodes with Add Step pseudo-node
 * - Multi-action capability gating
 * - Callback invocations
 */
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook } from '@testing-library/react';
import { Edge, Position } from '@xyflow/react';
import { useNodeCallbacks } from './useNodeCallbacks';
import { WorkflowReactFlowNode } from '../types/reactflow';

// Mock flow builder helpers
vi.mock('../utils/transforms', () => ({
  CENTER_X: 250,
  NODE_SPACING: 160,
  LANE_WIDTH: 350,
}));

describe('useNodeCallbacks', () => {
  const mockOnEdit = vi.fn();
  const mockOnDelete = vi.fn();
  const mockOnSelectApp = vi.fn();
  const mockOnViewDataFields = vi.fn();
  const mockOnAddPlaceholder = vi.fn();

  const createTriggerNode = (overrides = {}): WorkflowReactFlowNode => ({
    id: 'node-trigger',
    type: 'trigger',
    position: { x: 250, y: 80 },
    sourcePosition: Position.Bottom,
    targetPosition: Position.Top,
    data: {
      name: 'User Registration',
      icon: 'Zap',
      status: 'valid',
      config: {},
      actionType: 'wordpress/user_registration',
      ...overrides,
    },
  });

  const createActionNode = (id: string, overrides = {}): WorkflowReactFlowNode => ({
    id,
    type: 'action',
    position: { x: 250, y: 240 },
    sourcePosition: Position.Bottom,
    targetPosition: Position.Top,
    data: {
      name: 'Send Email',
      icon: 'Zap',
      status: 'valid',
      config: {},
      actionType: 'wordpress/send_email',
      ...overrides,
    },
  });

  const createPlaceholderNode = (type: 'trigger' | 'action'): WorkflowReactFlowNode => ({
    id: type === 'trigger' ? 'node-trigger' : 'node-placeholder',
    type,
    position: { x: 250, y: 80 },
    sourcePosition: Position.Bottom,
    targetPosition: Position.Top,
    data: {
      name: type === 'trigger' ? 'Choose a trigger' : 'Choose an action',
      icon: 'Zap',
      status: 'placeholder',
      placeholderType: type,
      config: {},
    },
  });

  const defaultProps = {
    nodes: [] as WorkflowReactFlowNode[],
    edges: [] as Edge[],
    onEdit: mockOnEdit,
    onDelete: mockOnDelete,
    onSelectApp: mockOnSelectApp,
    onViewDataFields: mockOnViewDataFields,
    onAddPlaceholder: mockOnAddPlaceholder,
  };

  beforeEach(() => {
    vi.clearAllMocks();
    (window as any).sequensyAPI = undefined;
  });

  // ==========================================================================
  // nodesWithCallbacks Tests
  // ==========================================================================

  describe('nodesWithCallbacks', () => {
    it('returns nodes with onEdit callback attached', () => {
      const nodes = [createTriggerNode()];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes }));

      expect(result.current.nodesWithCallbacks[0].data.onEdit).toBeDefined();
      result.current.nodesWithCallbacks[0].data.onEdit!('node-trigger');
      expect(mockOnEdit).toHaveBeenCalledWith('node-trigger');
    });

    it('returns nodes with onDelete callback attached', () => {
      const nodes = [createTriggerNode()];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes }));

      expect(result.current.nodesWithCallbacks[0].data.onDelete).toBeDefined();
      result.current.nodesWithCallbacks[0].data.onDelete!('node-trigger');
      expect(mockOnDelete).toHaveBeenCalledWith('node-trigger');
    });

    it('returns nodes with onSelectApp callback for placeholders', () => {
      const nodes = [createPlaceholderNode('action')];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes }));

      expect(result.current.nodesWithCallbacks[0].data.onSelectApp).toBeDefined();
      result.current.nodesWithCallbacks[0].data.onSelectApp!('node-placeholder');
      expect(mockOnSelectApp).toHaveBeenCalledWith('node-placeholder', 'action');
    });

    it('onSelectApp does nothing for non-placeholder nodes', () => {
      const nodes = [createTriggerNode({ status: 'valid' })];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes }));

      result.current.nodesWithCallbacks[0].data.onSelectApp!('node-trigger');
      expect(mockOnSelectApp).not.toHaveBeenCalled();
    });

    it('returns nodes with onViewDataFields callback', () => {
      const nodes = [createTriggerNode()];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes }));

      expect(result.current.nodesWithCallbacks[0].data.onViewDataFields).toBeDefined();
      result.current.nodesWithCallbacks[0].data.onViewDataFields!('node-trigger');
      expect(mockOnViewDataFields).toHaveBeenCalledWith(
        'node-trigger',
        'trigger',
        'wordpress/user_registration',
      );
    });
  });

  // ==========================================================================
  // displayNodes - With Add Step Tests
  // ==========================================================================

  describe('displayNodes - with Add Step', () => {
    it('adds Add Step pseudo-node when trigger exists', () => {
      const nodes = [createTriggerNode()];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes }));

      expect(result.current.displayNodes).toHaveLength(2);
      expect(result.current.displayNodes[1].id).toBe('node-add-step');
      expect(result.current.displayNodes[1].type).toBe('addStep');
    });

    it('positions Add Step below last node', () => {
      const nodes = [createTriggerNode()];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes }));

      const addStepNode = result.current.displayNodes[1];
      expect(addStepNode.position.x).toBe(250);
      expect(addStepNode.position.y).toBe(80 + 160); // lastNode.y + NODE_SPACING
    });

    it('Add Step onSelectApp triggers onAddPlaceholder with terminal node id', () => {
      const nodes = [createTriggerNode()];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes }));

      const addStepNode = result.current.displayNodes[1];
      addStepNode.data.onSelectApp!('node-add-step');
      expect(mockOnAddPlaceholder).toHaveBeenCalledWith('action', 'node-trigger', undefined);
    });

    it('Add Step node has correct data properties', () => {
      const nodes = [createTriggerNode()];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes }));

      const addStepNode = result.current.displayNodes[1];
      expect(addStepNode.data.name).toBe('Add Step');
      expect(addStepNode.data.icon).toBe('Plus');
      expect(addStepNode.data.status).toBe('placeholder');
    });
  });

  // ==========================================================================
  // displayNodes - Without Add Step Tests
  // ==========================================================================

  describe('displayNodes - without Add Step', () => {
    it('does not add Add Step when nodes array is empty', () => {
      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes: [] }));

      expect(result.current.displayNodes).toHaveLength(0);
    });

    it('adds Add Step even when action already exists (native multi-action support)', () => {
      const nodes = [createTriggerNode(), createActionNode('action-1')];
      const edges: Edge[] = [
        { id: 'e-trigger-action', source: 'node-trigger', target: 'action-1' },
      ];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes, edges }));

      // With native multi-action support, Add Step should be shown
      expect(result.current.displayNodes).toHaveLength(3);
      expect(result.current.displayNodes.some((n) => n.id === 'node-add-step')).toBe(true);
    });

    it('ignores placeholder actions when checking for existing actions', () => {
      const nodes = [createTriggerNode(), createPlaceholderNode('action')];
      const edges: Edge[] = [
        { id: 'e-trigger-placeholder', source: 'node-trigger', target: 'node-placeholder' },
      ];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes, edges }));

      // Placeholder doesn't count as real action, so Add Step should appear
      expect(result.current.displayNodes).toHaveLength(3);
      expect(result.current.displayNodes[2].id).toBe('node-add-step');
    });
  });

  // ==========================================================================
  // Multi-action Capability Tests
  // ==========================================================================

  describe('multi-action capability', () => {
    it('shows Add Step when multi_actions capability is true', () => {
      (window as any).sequensyAPI = { capabilities: { multi_actions: true } };

      const nodes = [createTriggerNode(), createActionNode('action-1')];
      const edges: Edge[] = [
        { id: 'e-trigger-action', source: 'node-trigger', target: 'action-1' },
      ];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes, edges }));

      // Pro plugin: should show Add Step even with existing action
      expect(result.current.displayNodes).toHaveLength(3);
      expect(result.current.displayNodes[2].id).toBe('node-add-step');
    });

    it('shows Add Step in free plugin with existing action (native multi-action support)', () => {
      (window as any).sequensyAPI = undefined;

      const nodes = [createTriggerNode(), createActionNode('action-1')];
      const edges: Edge[] = [
        { id: 'e-trigger-action', source: 'node-trigger', target: 'action-1' },
      ];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes, edges }));

      // Free plugin now supports multi-actions natively
      expect(result.current.displayNodes).toHaveLength(3);
      expect(result.current.displayNodes[2].id).toBe('node-add-step');
    });

    it('handles missing capabilities object (allows multi-actions by default)', () => {
      (window as any).sequensyAPI = {};

      const nodes = [createTriggerNode(), createActionNode('action-1')];
      const edges: Edge[] = [
        { id: 'e-trigger-action', source: 'node-trigger', target: 'action-1' },
      ];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes, edges }));

      // Multi-actions now enabled by default
      expect(result.current.displayNodes).toHaveLength(3);
      expect(result.current.displayNodes[2].id).toBe('node-add-step');
    });
  });

  // ==========================================================================
  // Callback Invocations Tests
  // ==========================================================================

  describe('callback invocations', () => {
    it('onEdit receives correct node ID', () => {
      const nodes = [createTriggerNode(), createActionNode('action-1')];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes }));

      result.current.nodesWithCallbacks[1].data.onEdit!('action-1');
      expect(mockOnEdit).toHaveBeenCalledWith('action-1');
    });

    it('onViewDataFields uses correct node type', () => {
      const nodes = [createActionNode('action-1')];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes }));

      result.current.nodesWithCallbacks[0].data.onViewDataFields!('action-1');
      expect(mockOnViewDataFields).toHaveBeenCalledWith('action-1', 'action', 'wordpress/send_email');
    });
  });

  // ==========================================================================
  // Memoization Tests
  // ==========================================================================

  describe('memoization', () => {
    it('returns same nodesWithCallbacks reference when inputs unchanged', () => {
      const nodes = [createTriggerNode()];

      const { result, rerender } = renderHook((props) => useNodeCallbacks(props), {
        initialProps: { ...defaultProps, nodes },
      });

      const firstResult = result.current.nodesWithCallbacks;
      rerender({ ...defaultProps, nodes });
      const secondResult = result.current.nodesWithCallbacks;

      expect(firstResult).toBe(secondResult);
    });

    it('returns same displayNodes reference when inputs unchanged', () => {
      const nodes = [createTriggerNode()];

      const { result, rerender } = renderHook((props) => useNodeCallbacks(props), {
        initialProps: { ...defaultProps, nodes },
      });

      const firstResult = result.current.displayNodes;
      rerender({ ...defaultProps, nodes });
      const secondResult = result.current.displayNodes;

      expect(firstResult).toBe(secondResult);
    });
  });

  // ==========================================================================
  // Edge Cases Tests
  // ==========================================================================

  describe('edge cases', () => {
    it('handles node without actionType for onViewDataFields', () => {
      const nodes = [
        {
          ...createTriggerNode(),
          data: { ...createTriggerNode().data, actionType: undefined },
        },
      ];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes }));

      result.current.nodesWithCallbacks[0].data.onViewDataFields!('node-trigger');
      // Should not call onViewDataFields when actionType is undefined
      expect(mockOnViewDataFields).not.toHaveBeenCalled();
    });

    it('ignores Router routes with empty names (no phantom AddStep)', () => {
      const routerNode: WorkflowReactFlowNode = {
        id: 'router-1',
        type: 'action',
        position: { x: 250, y: 240 },
        sourcePosition: Position.Bottom,
        targetPosition: Position.Top,
        data: {
          name: 'Router',
          icon: 'GitBranch',
          status: 'valid',
          config: {
            routes: [
              { name: 'route_a', label: 'Route A', conditions: {} },
              { name: '', label: '', conditions: '' }, // empty route being edited
            ],
          },
          actionType: 'router',
        },
      };
      const nodes = [createTriggerNode(), routerNode];
      const edges: Edge[] = [
        { id: 'e-trigger-router', source: 'node-trigger', target: 'router-1' },
      ];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes, edges }));

      // Should create AddStep for route_a and default only — NOT the empty-named route
      const addSteps = result.current.displayNodes.filter((n) => n.type === 'addStep');
      expect(addSteps).toHaveLength(2); // route_a + default
      expect(result.current.addStepEntries).toHaveLength(2);
      expect(result.current.addStepEntries.map((e) => e.sourceHandle)).toEqual(['route_a', 'default']);
    });

    it('sets onDuplicate to undefined (Pro feature)', () => {
      const nodes = [createTriggerNode()];

      const { result } = renderHook(() => useNodeCallbacks({ ...defaultProps, nodes }));

      expect(result.current.nodesWithCallbacks[0].data.onDuplicate).toBeUndefined();
    });
  });
});
