import { useMemo } from 'react';
import { Edge, Position } from '@xyflow/react';
import { WorkflowReactFlowNode, WorkflowNodeData } from '../types/reactflow';
import { NODE_SPACING, LANE_WIDTH } from '../utils/transforms';

export interface AddStepEntry {
  addStepId: string;
  parentId: string;
  sourceHandle?: string;
}

interface UseNodeCallbacksProps {
  nodes: WorkflowReactFlowNode[];
  edges: Edge[];
  onEdit: (nodeId: string) => void;
  onDelete: (nodeId: string) => void;
  onSelectApp: (nodeId: string, placeholderType: 'trigger' | 'action') => void;
  onViewDataFields: (nodeId: string, nodeType: 'trigger' | 'action', actionType: string) => void;
  onAddPlaceholder: (type: 'trigger' | 'action', parentNodeId?: string, sourceHandle?: string) => void;
}

interface UseNodeCallbacksReturn {
  nodesWithCallbacks: WorkflowReactFlowNode[];
  displayNodes: WorkflowReactFlowNode[];
  addStepEntries: AddStepEntry[];
}

/**
 * Hook for adding callbacks and computed properties to workflow nodes.
 * Handles node callbacks and the "Add Step" pseudo-node.
 */
export function useNodeCallbacks({
  nodes,
  edges,
  onEdit,
  onDelete,
  onSelectApp,
  onViewDataFields,
  onAddPlaceholder,
}: UseNodeCallbacksProps): UseNodeCallbacksReturn {
  // Nodes with callbacks attached
  const nodesWithCallbacks = useMemo(
    () =>
      nodes.map((node) => ({
        ...node,
        data: {
          ...node.data,
          onEdit: (nodeId: string) => onEdit(nodeId),
          onDuplicate: undefined, // Pro feature: duplicate node
          onDelete: onDelete,
          onSelectApp: (nodeId: string) => {
            const targetNode = nodes.find((n) => n.id === nodeId);
            if (targetNode?.data.status === 'placeholder') {
              onSelectApp(nodeId, targetNode.data.placeholderType || 'action');
            }
          },
          onViewDataFields: (nodeId: string) => {
            const targetNode = nodes.find((n) => n.id === nodeId);
            if (targetNode?.data.actionType) {
              const nodeType = targetNode.type as 'trigger' | 'action';
              onViewDataFields(nodeId, nodeType, targetNode.data.actionType);
            }
          },
        },
      })),
    [nodes, onEdit, onDelete, onSelectApp, onViewDataFields],
  );

  // Compute AddStep entries for both nodes and edges
  const { displayNodes, addStepEntries } = useMemo(() => {
    const result: WorkflowReactFlowNode[] = [...nodesWithCallbacks];
    const entries: AddStepEntry[] = [];
    if (nodes.length === 0) return { displayNodes: result, addStepEntries: entries };

    const nodesWithOutgoing = new Set(edges.map((e) => e.source));
    const terminalNodes = nodes.filter((n) => !nodesWithOutgoing.has(n.id));
    let addStepIndex = 0;

    // Helper to push an AddStep node + entry
    const pushAddStep = (parentId: string, position: { x: number; y: number }, sourceHandle?: string) => {
      const id = addStepIndex === 0 ? 'node-add-step' : `node-add-step-${addStepIndex}`;
      result.push({
        id,
        type: 'addStep',
        position,
        sourcePosition: Position.Bottom,
        targetPosition: Position.Top,
        // Pre-set dimensions so React Flow considers the node "measured" immediately,
        // avoiding the hidden→measure→visible cycle that never completes for pseudo-nodes.
        initialWidth: 300,
        initialHeight: 20,
        data: {
          name: 'Add Step',
          icon: 'Plus',
          status: 'placeholder',
          config: {},
          onSelectApp: () => onAddPlaceholder('action', parentId, sourceHandle),
        } as WorkflowNodeData,
      });
      entries.push({ addStepId: id, parentId, sourceHandle });
      addStepIndex++;
    };

    // 1. Non-Router terminal nodes — single AddStep each
    const nonRouterTerminals = terminalNodes.filter((n) => n.data.actionType !== 'router');
    nonRouterTerminals.forEach((node) => {
      pushAddStep(node.id, { x: node.position.x, y: node.position.y + NODE_SPACING });
    });

    // 2. Router nodes — one AddStep per unconnected route handle
    nodes.filter((n) => n.data.actionType === 'router').forEach((routerNode) => {
      const routes = (
        (routerNode.data.config?.routes as Array<{ name: string; label?: string }>) || []
      ).filter((r) => r.name);
      if (routes.length === 0) {
        // No routes configured — treat like a normal terminal node
        if (!nodesWithOutgoing.has(routerNode.id)) {
          pushAddStep(routerNode.id, { x: routerNode.position.x, y: routerNode.position.y + NODE_SPACING });
        }
        return;
      }
      const allHandles = [...routes.map((r) => r.name), 'default'];
      const connectedHandles = new Set(
        edges.filter((e) => e.source === routerNode.id).map((e) => e.sourceHandle),
      );
      const unconnected = allHandles.filter((h) => !connectedHandles.has(h));
      if (unconnected.length === 0) return;

      const totalWidth = (allHandles.length - 1) * LANE_WIDTH;
      const startX = routerNode.position.x - totalWidth / 2;
      unconnected.forEach((handleName) => {
        const i = allHandles.indexOf(handleName);
        pushAddStep(
          routerNode.id,
          { x: startX + i * LANE_WIDTH, y: routerNode.position.y + NODE_SPACING },
          handleName,
        );
      });
    });

    // 3. Fallback: if no AddSteps created but nodes exist, use last node
    if (addStepIndex === 0 && nodes.length > 0) {
      const last = nodes[nodes.length - 1];
      pushAddStep(last.id, { x: last.position.x, y: last.position.y + NODE_SPACING });
    }

    return { displayNodes: result, addStepEntries: entries };
  }, [nodesWithCallbacks, nodes, edges, onAddPlaceholder]);

  return {
    nodesWithCallbacks,
    displayNodes,
    addStepEntries,
  };
}
