import type { EdgeMouseHandler, OnEdgesChange } from 'reactflow' import { produce } from 'immer' import { useCallback } from 'react' import { useStoreApi } from 'reactflow' import { useWorkflowStore } from '../store' import { useCollaborativeWorkflow } from './use-collaborative-workflow' import { applyConnectedHandleNodeData, buildContextMenuEdges, clearEdgeMenuIfNeeded, clearNodeSelectionState, updateEdgeHoverState, updateEdgeSelectionState, } from './use-edges-interactions.helpers' import { useNodesSyncDraft } from './use-nodes-sync-draft' import { useNodesReadOnly } from './use-workflow' import { useWorkflowHistory, WorkflowHistoryEvent } from './use-workflow-history' export const useEdgesInteractions = () => { const store = useStoreApi() const { handleSyncWorkflowDraft } = useNodesSyncDraft() const { getNodesReadOnly } = useNodesReadOnly() const { saveStateToHistory } = useWorkflowHistory() const workflowStore = useWorkflowStore() const collaborativeWorkflow = useCollaborativeWorkflow() const deleteEdgeById = useCallback( (edgeId: string) => { const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState() const currentEdgeIndex = edges.findIndex((edge) => edge.id === edgeId) if (currentEdgeIndex < 0) return const currentEdge = edges[currentEdgeIndex]! const newNodes = applyConnectedHandleNodeData(nodes, [{ type: 'remove', edge: currentEdge }]) setNodes(newNodes) const newEdges = produce(edges, (draft) => { draft.splice(currentEdgeIndex, 1) }) setEdges(newEdges) if ( clearEdgeMenuIfNeeded({ contextMenuTarget: workflowStore.getState().contextMenuTarget, edgeIds: [currentEdge!.id], }) ) workflowStore.setState({ contextMenuTarget: undefined }) handleSyncWorkflowDraft() saveStateToHistory(WorkflowHistoryEvent.EdgeDelete) }, [collaborativeWorkflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory], ) const handleEdgeEnter = useCallback( (_, edge) => { if (getNodesReadOnly()) return const { edges, setEdges } = store.getState() setEdges(updateEdgeHoverState(edges, edge.id, true)) }, [getNodesReadOnly, store], ) const handleEdgeLeave = useCallback( (_, edge) => { if (getNodesReadOnly()) return const { edges, setEdges } = store.getState() setEdges(updateEdgeHoverState(edges, edge.id, false)) }, [getNodesReadOnly, store], ) const handleEdgeDeleteByDeleteBranch = useCallback( (nodeId: string, branchId: string) => { if (getNodesReadOnly()) return const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState() const edgeWillBeDeleted = edges.filter( (edge) => edge.source === nodeId && edge.sourceHandle === branchId, ) if (!edgeWillBeDeleted.length) return const newNodes = applyConnectedHandleNodeData( nodes, edgeWillBeDeleted.map((edge) => ({ type: 'remove' as const, edge })), ) setNodes(newNodes) const newEdges = produce(edges, (draft) => { return draft.filter((edge) => !edgeWillBeDeleted.find((e) => e.id === edge.id)) }) setEdges(newEdges) if ( clearEdgeMenuIfNeeded({ contextMenuTarget: workflowStore.getState().contextMenuTarget, edgeIds: edgeWillBeDeleted.map((edge) => edge.id), }) ) { workflowStore.setState({ contextMenuTarget: undefined }) } handleSyncWorkflowDraft() saveStateToHistory(WorkflowHistoryEvent.EdgeDeleteByDeleteBranch) }, [ getNodesReadOnly, collaborativeWorkflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory, ], ) const handleEdgeDelete = useCallback(() => { if (getNodesReadOnly()) return const { edges } = collaborativeWorkflow.getState() const currentEdge = edges.find((edge) => edge.selected) if (!currentEdge) return deleteEdgeById(currentEdge.id) }, [deleteEdgeById, getNodesReadOnly, collaborativeWorkflow]) const handleEdgeDeleteById = useCallback( (edgeId: string) => { if (getNodesReadOnly()) return deleteEdgeById(edgeId) }, [deleteEdgeById, getNodesReadOnly], ) const handleEdgesChange = useCallback( (changes) => { if (getNodesReadOnly()) return const { edges, setEdges } = collaborativeWorkflow.getState() setEdges(updateEdgeSelectionState(edges, changes)) }, [collaborativeWorkflow, getNodesReadOnly], ) const handleEdgeSourceHandleChange = useCallback( (nodeId: string, oldHandleId: string, newHandleId: string) => { if (getNodesReadOnly()) return const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState() // Find edges connected to the old handle const affectedEdges = edges.filter( (edge) => edge.source === nodeId && edge.sourceHandle === oldHandleId, ) if (affectedEdges.length === 0) return // Update node metadata: remove old handle, add new handle const newNodes = applyConnectedHandleNodeData(nodes, [ ...affectedEdges.map((edge) => ({ type: 'remove' as const, edge })), ...affectedEdges.map((edge) => ({ type: 'add' as const, edge: { ...edge, sourceHandle: newHandleId }, })), ]) setNodes(newNodes) // Update edges to use new sourceHandle and regenerate edge IDs const newEdges = produce(edges, (draft) => { draft.forEach((edge) => { if (edge.source === nodeId && edge.sourceHandle === oldHandleId) { edge.sourceHandle = newHandleId edge.id = `${edge.source}-${newHandleId}-${edge.target}-${edge.targetHandle}` } }) }) setEdges(newEdges) if ( clearEdgeMenuIfNeeded({ contextMenuTarget: workflowStore.getState().contextMenuTarget, edgeIds: affectedEdges.map((edge) => edge.id), }) ) { workflowStore.setState({ contextMenuTarget: undefined }) } handleSyncWorkflowDraft() saveStateToHistory(WorkflowHistoryEvent.EdgeSourceHandleChange) }, [ getNodesReadOnly, collaborativeWorkflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory, ], ) const handleEdgeContextMenu = useCallback( (e, edge) => { if (getNodesReadOnly()) { e.stopPropagation() return } e.preventDefault() const { nodes, setNodes, edges, setEdges } = collaborativeWorkflow.getState() setEdges(buildContextMenuEdges(edges, edge.id)) if (nodes.some((node) => node.data.selected || node.selected || node.data._isBundled)) { setNodes(clearNodeSelectionState(nodes)) } workflowStore.setState({ contextMenuTarget: { type: 'edge', edgeId: edge.id, }, }) }, [collaborativeWorkflow, workflowStore, getNodesReadOnly], ) return { handleEdgeEnter, handleEdgeLeave, handleEdgeDeleteByDeleteBranch, handleEdgeDelete, handleEdgeDeleteById, handleEdgesChange, handleEdgeSourceHandleChange, handleEdgeContextMenu, } }