void ProcessDependencyModel(INodeModel nodeModel, GraphViewStateComponent.StateUpdater graphUpdater,
                                    Action <IDependency, INodeModel, GraphViewStateComponent.StateUpdater> dependencyCallback)
        {
            Log($"ProcessDependencyModel {nodeModel}");

            if (!m_DependenciesByNode.TryGetValue(nodeModel.Guid, out var link))
            {
                return;
            }

            foreach (var dependency in link)
            {
                if (m_ModelsToMove.Contains(dependency.Value.DependentNode))
                {
                    continue;
                }
                if (!m_TempMovedModels.Add(dependency.Value.DependentNode))
                {
                    Log($"Skip ProcessDependency {dependency.Value.DependentNode}");
                    continue;
                }

                dependencyCallback(dependency.Value, nodeModel, graphUpdater);
                ProcessDependencyModel(dependency.Value.DependentNode, graphUpdater, dependencyCallback);
            }
        }
        void AlignDependency(IDependency dependency, INodeModel prev, GraphViewStateComponent.StateUpdater graphUpdater)
        {
            // Warning: Don't try to use the VisualElement.layout Rect as it is not up to date yet.
            // Use Node.GetPosition() when possible

            var parentUI = prev.GetUI <Node>(m_GraphView);
            var depUI    = dependency.DependentNode.GetUI <Node>(m_GraphView);

            if (parentUI == null || depUI == null)
            {
                return;
            }

            switch (dependency)
            {
            case LinkedNodesDependency linked:

                Vector2 position;

                var input  = linked.ParentPort.GetUI <Port>(m_GraphView);
                var output = linked.DependentPort.GetUI <Port>(m_GraphView);

                if (input?.Model != null && output?.Model != null &&
                    ((IPortModel)input.Model).Orientation == ((IPortModel)output.Model).Orientation)
                {
                    Vector2 inputPortPos  = input.parent.ChangeCoordinatesTo(parentUI, input.layout.center);
                    Vector2 inputPos      = prev.Position;
                    Vector2 outputPortPos = output.parent.ChangeCoordinatesTo(depUI, output.layout.center);

                    if (((IPortModel)input.Model).Orientation == PortOrientation.Horizontal)
                    {
                        position = new Vector2(
                            prev.Position.x + (linked.ParentPort.Direction == PortDirection.Output
                                    ? parentUI.layout.width + k_AlignHorizontalOffset
                                    : -k_AlignHorizontalOffset - depUI.layout.width),
                            inputPos.y + inputPortPos.y - outputPortPos.y
                            );
                    }
                    else
                    {
                        position = new Vector2(
                            inputPos.x + inputPortPos.x - outputPortPos.x,
                            prev.Position.y + (linked.ParentPort.Direction == PortDirection.Output
                                    ? parentUI.layout.height + k_AlignVerticalOffset
                                    : -k_AlignVerticalOffset - depUI.layout.height)
                            );
                    }

                    linked.DependentNode.Position = position;
                    graphUpdater.MarkChanged(linked.DependentNode);
                }
                break;
            }
        }
        internal static void PasteSerializedData(IGraphModel graph, Vector2 delta,
                                                 GraphViewStateComponent.StateUpdater graphViewUpdater,
                                                 SelectionStateComponent.StateUpdater selectionStateUpdater,
                                                 CopyPasteData copyPasteData)
        {
            var elementMapping = new Dictionary <string, IGraphElementModel>();

            if (copyPasteData.variableDeclarations.Any())
            {
                List <IVariableDeclarationModel> variableDeclarationModels =
                    copyPasteData.variableDeclarations.ToList();
                List <IVariableDeclarationModel> duplicatedModels = new List <IVariableDeclarationModel>();

                foreach (var sourceModel in variableDeclarationModels)
                {
                    duplicatedModels.Add(graph.DuplicateGraphVariableDeclaration(sourceModel));
                }

                graphViewUpdater?.MarkNew(duplicatedModels);
                selectionStateUpdater?.SelectElements(duplicatedModels, true);
            }

            var nodeMapping = new Dictionary <INodeModel, INodeModel>();

            foreach (var originalModel in copyPasteData.nodes)
            {
                if (!graph.Stencil.CanPasteNode(originalModel, graph))
                {
                    continue;
                }

                var pastedNode = graph.DuplicateNode(originalModel, delta);
                graphViewUpdater?.MarkNew(pastedNode);
                selectionStateUpdater?.SelectElements(new[] { pastedNode }, true);
                nodeMapping[originalModel] = pastedNode;
            }

            // PF FIXME we could do this in the foreach above
            foreach (var nodeModel in nodeMapping)
            {
                elementMapping.Add(nodeModel.Key.Guid.ToString(), nodeModel.Value);
            }

            foreach (var edge in copyPasteData.edges)
            {
                elementMapping.TryGetValue(edge.ToPort.NodeModel.Guid.ToString(), out var newInput);
                elementMapping.TryGetValue(edge.FromPort.NodeModel.Guid.ToString(), out var newOutput);

                var copiedEdge = graph.DuplicateEdge(edge, newInput as INodeModel, newOutput as INodeModel);
                if (copiedEdge != null)
                {
                    elementMapping.Add(edge.Guid.ToString(), copiedEdge);
                    graphViewUpdater?.MarkNew(copiedEdge);
                    selectionStateUpdater?.SelectElements(new[] { copiedEdge }, true);
                }
            }

            foreach (var stickyNote in copyPasteData.stickyNotes)
            {
                var newPosition      = new Rect(stickyNote.PositionAndSize.position + delta, stickyNote.PositionAndSize.size);
                var pastedStickyNote = graph.CreateStickyNote(newPosition);
                pastedStickyNote.Title    = stickyNote.Title;
                pastedStickyNote.Contents = stickyNote.Contents;
                pastedStickyNote.Theme    = stickyNote.Theme;
                pastedStickyNote.TextSize = stickyNote.TextSize;
                graphViewUpdater?.MarkNew(pastedStickyNote);
                selectionStateUpdater?.SelectElements(new[] { pastedStickyNote }, true);
                elementMapping.Add(stickyNote.Guid.ToString(), pastedStickyNote);
            }

            List <IPlacematModel> pastedPlacemats = new List <IPlacematModel>();

            // Keep placemats relative order
            foreach (var placemat in copyPasteData.placemats.OrderBy(p => p.ZOrder))
            {
                var newPosition    = new Rect(placemat.PositionAndSize.position + delta, placemat.PositionAndSize.size);
                var newTitle       = "Copy of " + placemat.Title;
                var pastedPlacemat = graph.CreatePlacemat(newPosition);
                pastedPlacemat.Title          = newTitle;
                pastedPlacemat.Color          = placemat.Color;
                pastedPlacemat.Collapsed      = placemat.Collapsed;
                pastedPlacemat.HiddenElements = placemat.HiddenElements;
                graphViewUpdater?.MarkNew(pastedPlacemat);
                selectionStateUpdater?.SelectElements(new[] { pastedPlacemat }, true);
                pastedPlacemats.Add(pastedPlacemat);
                elementMapping.Add(placemat.Guid.ToString(), pastedPlacemat);
            }

            // Update hidden content to new node ids.
            foreach (var pastedPlacemat in pastedPlacemats)
            {
                if (pastedPlacemat.Collapsed)
                {
                    List <IGraphElementModel> pastedHiddenContent = new List <IGraphElementModel>();
                    foreach (var elementGUID in pastedPlacemat.HiddenElements.Select(t => t.Guid.ToString()))
                    {
                        if (elementMapping.TryGetValue(elementGUID, out var pastedElement))
                        {
                            pastedHiddenContent.Add(pastedElement);
                        }
                    }

                    pastedPlacemat.HiddenElements = pastedHiddenContent;
                }
            }
        }
        public void AlignNodes(bool follow, IReadOnlyList <IGraphElementModel> entryPoints, GraphViewStateComponent.StateUpdater graphUpdater)
        {
            HashSet <INodeModel> topMostModels = new HashSet <INodeModel>();

            topMostModels.Clear();

            bool anyEdge = false;

            foreach (var edgeModel in entryPoints.OfType <IEdgeModel>())
            {
                if (!edgeModel.GraphModel.Stencil.CreateDependencyFromEdge(edgeModel, out var dependency, out var parent))
                {
                    continue;
                }
                anyEdge = true;

                AlignDependency(dependency, parent, graphUpdater);
                topMostModels.Add(dependency.DependentNode);
            }

            if (anyEdge && !follow)
            {
                return;
            }

            if (!topMostModels.Any())
            {
                foreach (var nodeModel in entryPoints.OfType <INodeModel>())
                {
                    topMostModels.Add(nodeModel);
                }
            }

            if (!anyEdge && !follow)
            {
                // Align each top-most node then move dependencies by the same delta
                foreach (INodeModel model in topMostModels)
                {
                    if (!m_DependenciesByNode.TryGetValue(model.Guid, out var dependencies))
                    {
                        continue;
                    }
                    foreach (var dependency in dependencies)
                    {
                        AlignDependency(dependency.Value, model, graphUpdater);
                    }
                }
            }
            else
            {
                // Align recursively
                m_ModelsToMove.AddRangeInternal(topMostModels);
                ProcessMovedNodeModels(AlignDependency, graphUpdater);
            }

            m_ModelsToMove.Clear();
            m_TempMovedModels.Clear();
        }
        void ProcessMovedNodeModels(Action <IDependency, INodeModel, GraphViewStateComponent.StateUpdater> dependencyCallback, GraphViewStateComponent.StateUpdater graphUpdater)
        {
            Profiler.BeginSample("GTF.ProcessMovedNodeModel");

            m_TempMovedModels.Clear();
            foreach (INodeModel nodeModel in m_ModelsToMove)
            {
                ProcessDependencyModel(nodeModel, graphUpdater, dependencyCallback);
            }

            Profiler.EndSample();
        }