/**
 * Copyright 2022-2025 Theai, Inc. dba Inworld AI
 */
#include "GraphEditor/InworldGraphAssetEditor/EdGraph_InworldGraph.h"

#include "Graph/Assets/InworldGraphAsset.h"
#include "Graph/InworldGraph.h"
#include "Graph/InworldEdge.h"
#include "GraphEditor/InworldGraphAssetEditor/EdNode_InworldGraphEdge.h"
#include "GraphEditor/InworldGraphAssetEditor/EdNode_InworldGraphNode.h"
#include "GraphEditor/InworldGraphAssetEditor/InworldGraphEditorStyle.h"
#include "InworldRuntimeEditor.h"
#include "UObject/ObjectSaveContext.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "AssetRegistry/AssetRegistryModule.h"

static bool UEdGraph_InworldGraphSavePropagationActive{false};

void UEdGraph_InworldGraph::RefreshOpenedAssets()
{
	for (TObjectIterator<UEdGraph_InworldGraph> It; It; ++It)
	{
		UEdGraph_InworldGraph* EdGraph = *It;
		if (!IsValid(EdGraph))
		{
			continue;
		}

		TSharedPtr<SGraphEditor> GraphEd = SGraphEditor::FindGraphEditorForGraph(EdGraph);
		if (GraphEd.IsValid())
		{
			EdGraph->GetSchema()->ForceVisualizationCacheClear();
			EdGraph->ValidateGraph(false);
			GraphEd->NotifyGraphChanged();
		}
	}
}

void UEdGraph_InworldGraph::RebuildInworldGraph()
{
	UInworldGraphAsset* Graph = GetInworldGraph();
	Clear();

	Graph->RuntimeGraph->SetGraphId(Graph->GetName());
	for (int i = 0; i < Nodes.Num(); ++i)
	{
		if (UEdNode_InworldGraphNode* EdNode = Cast<UEdNode_InworldGraphNode>(Nodes[i]))
		{
			if (EdNode->InworldGraphNode == nullptr || EdNode->InworldGraphNode->RuntimeNode == nullptr)
				continue;

			UInworldNodeAsset* GraphNode = EdNode->InworldGraphNode;
			if (GraphNode->NodeClass && (GraphNode->NodeClassName.IsEmpty() || GraphNode->NodeClass->GetName() != GraphNode->NodeClassName))
			{
				GraphNode->NodeClassName = GraphNode->NodeClass->GetName();
				GraphNode->NodeClassPath = GraphNode->NodeClass->GetClassPathName().ToString();
			}
			NodeMap.Add(GraphNode, EdNode);
			FString NativeNodeId = GraphNode->NodeTitle.Replace(TEXT(" "), TEXT(""));
			GraphNode->RuntimeNode->SetNodeName(NativeNodeId);
			Graph->AllNodes.Add(GraphNode);

			for (int PinIdx = 0; PinIdx < EdNode->Pins.Num(); ++PinIdx)
			{
				UEdGraphPin* Pin = EdNode->Pins[PinIdx];
				if (Pin->Direction != EEdGraphPinDirection::EGPD_Output)
					continue;

				for (int LinkToIdx = 0; LinkToIdx < Pin->LinkedTo.Num(); ++LinkToIdx)
				{
					UInworldNodeAsset* ChildNode = nullptr;
					int32 ChildPinIndex = 0;

					if (UEdNode_InworldGraphNode* EdNode_Child = Cast<UEdNode_InworldGraphNode>(Pin->LinkedTo[LinkToIdx]->GetOwningNode()))
					{
						ChildNode = EdNode_Child->InworldGraphNode;
						ChildPinIndex = EdNode_Child->Pins.Find(Pin->LinkedTo[LinkToIdx]);
					}
					else if (UEdNode_InworldGraphEdge* EdNode_Edge = Cast<UEdNode_InworldGraphEdge>(Pin->LinkedTo[LinkToIdx]->GetOwningNode()))
					{
						UEdNode_InworldGraphNode* Child = EdNode_Edge->GetEndNode();
						if (Child != nullptr)
						{
							ChildNode = Child->InworldGraphNode;

							// get the pin index from the edge
							ChildPinIndex = EdNode_Edge->InworldGraphEdge->EndPinIndex;
						}
					}

					if (ChildNode != nullptr)
					{
						GraphNode->ChildrenNodes.Add(ChildNode);
						ChildNode->ParentNodes.Add(GraphNode);
					}
					else
					{
						UE_LOG(LogInworldRuntimeEditor, Error, TEXT("UEdGraph_InworldGraph::RebuildInworldGraph can't find child node"));
					}
				}
			}
		}
		else if (UEdNode_InworldGraphEdge* EdgeNode = Cast<UEdNode_InworldGraphEdge>(Nodes[i]))
		{
			UEdNode_InworldGraphNode* StartNode = EdgeNode->GetStartNode();
			UEdNode_InworldGraphNode* EndNode = EdgeNode->GetEndNode();
			UInworldEdgeAsset* Edge = EdgeNode->InworldGraphEdge;

			if (StartNode == nullptr || EndNode == nullptr || Edge == nullptr)
			{
				UE_LOG(LogInworldRuntimeEditor, Error, TEXT("UEdGraph_InworldGraph::RebuildInworldGraph add edge failed."));
				continue;
			}

			EdgeMap.Add(Edge, EdgeNode);
			Graph->AllEdges.Add(Edge);

			Edge->Rename(nullptr, Graph, REN_DontCreateRedirectors | REN_DoNotDirty);
			Edge->StartNode = StartNode->InworldGraphNode;
			Edge->EndNode = EndNode->InworldGraphNode;
			Edge->StartNode->Edges.Add(Edge->EndNode, Edge);

			// checking that the pin indices are saved
			UEdGraphPin* StartPin = EdgeNode->GetStartPin();
			UEdGraphPin* EndPin = EdgeNode->GetEndPin();

			if (StartPin && EndPin)
			{
				Edge->StartPinIndex = StartNode->Pins.Find(StartPin);
				Edge->EndPinIndex = EndNode->Pins.Find(EndPin);
			}
		}
	}

	for (int i = 0; i < Graph->AllNodes.Num(); ++i)
	{
		UInworldNodeAsset* Node = Graph->AllNodes[i];
		
		if ((Node->NodeAs == EInworldNodeType::START_NODE && !Node->IsRootNode()) || 
			(Node->NodeAs == EInworldNodeType::END_NODE && !Node->IsLeafNode()) ||
			(Node->NodeAs == EInworldNodeType::START_END_NODE && !(Node->IsRootNode() && Node->IsLeafNode())))
		{
			Node->NodeAs = EInworldNodeType::NODE;
		}

		if (Node->NodeAs == EInworldNodeType::START_NODE && Node->IsRootNode())
		{
			Graph->StartNodes.Add(Node);
		}
		if (Node->NodeAs == EInworldNodeType::END_NODE && Node->IsLeafNode())
		{
			Graph->EndNodes.Add(Node);
		}
		if (Node->NodeAs == EInworldNodeType::START_END_NODE && 
			Node->IsRootNode() && Node->IsLeafNode())
		{
			Graph->StartNodes.Add(Node);
			Graph->EndNodes.Add(Node);
		}

		Node->Rename(nullptr, Graph, REN_DontCreateRedirectors | REN_DoNotDirty);
	}

	if (Graph->StartNodes.Num() == 0)
	{
		for (int i = 0; i < Graph->AllNodes.Num(); ++i)
		{
			UInworldNodeAsset* Node = Graph->AllNodes[i];

			if (Node->ParentNodes.Num() == 0 && Node->ChildrenNodes.Num() > 0)
			{
				Graph->StartNodes.Add(Node);
				Node->NodeAs = EInworldNodeType::START_NODE;
				break;
			}
		}
	}

	{
		TMap<FString, int32> SuffixCounters;
		TSet<FString> OccupiedNames;

		for (int32 i = 0; i < Graph->AllNodes.Num(); ++i)
		{
			if (UInworldNodeAsset* Node = Graph->AllNodes[i])
			{
				if (OccupiedNames.Contains(Node->RuntimeNode->GetNodeName()))
				{
					FString BaseName = Node->RuntimeNode->GetNodeName();
					int32& Suffix = SuffixCounters.FindOrAdd(BaseName, 1);

					FString NewName;
					do
					{
						NewName = FString::Printf(TEXT("%s_%d"), *BaseName, Suffix);
						Suffix++;
					} while (OccupiedNames.Contains(NewName));

					Node->RuntimeNode->SetNodeName(NewName);
				}
				OccupiedNames.Add(Node->RuntimeNode->GetNodeName());
			}
		}
	}

	if (Graph->StartNodes.Num() > 0)
	{
		SortNodes(Graph->StartNodes);
	}

	Graph->AllEdges.Sort(
		[](const UInworldEdgeAsset& A, const UInworldEdgeAsset& B)
		{
			if (A.EndNode != B.EndNode)
			{
				return A.EndNode < B.EndNode;
			}
			return A.EndPinIndex < B.EndPinIndex;
		});

	Graph->StartNodes.Sort(
		[&](const UInworldNodeAsset& L, const UInworldNodeAsset& R)
		{
			UEdNode_InworldGraphNode* EdNode_LNode = NodeMap[&L];
			UEdNode_InworldGraphNode* EdNode_RNode = NodeMap[&R];
			return EdNode_LNode->NodePosY < EdNode_RNode->NodePosY;
		});

	UInworldGraph* RuntimeGraph = Graph->RuntimeGraph;

	for (int i = 0; i < Graph->AllNodes.Num(); ++i)
	{
		UInworldNodeAsset* Node = Graph->AllNodes[i];
		if (Node->RuntimeNode == nullptr || RuntimeGraph->Nodes.Contains(Node->RuntimeNode))
			continue;

		RuntimeGraph->Nodes.Add(Node->RuntimeNode);
		
		if (Node->NodeAs == EInworldNodeType::START_NODE)
		{
			RuntimeGraph->StartNodes.Add(Node->RuntimeNode->GetNodeName());
		}
		else if (Node->NodeAs == EInworldNodeType::END_NODE)
		{
			RuntimeGraph->EndNodes.Add(Node->RuntimeNode->GetNodeName());
		}
		else if (Node->NodeAs == EInworldNodeType::START_END_NODE)
		{
			RuntimeGraph->EndNodes.Add(Node->RuntimeNode->GetNodeName());
			RuntimeGraph->StartNodes.Add(Node->RuntimeNode->GetNodeName());
		}
	}
	
	for (int i = 0; i < Graph->AllEdges.Num(); ++i)
	{
		UInworldEdgeAsset* Edge = Graph->AllEdges[i];
		if (IsValid(Edge->RuntimeEdge) && 
			Edge->GetStartNodeName() != "StartNode_NotValid" &&
			Edge->GetEndNodeName() != "EndNode_NotValid" && 
			RuntimeGraph->GetNodeByName(Edge->GetStartNodeName()) &&
			RuntimeGraph->GetNodeByName(Edge->GetEndNodeName()))
		{
			Edge->RuntimeEdge->Source = Edge->GetStartNodeName();
			Edge->RuntimeEdge->Destination = Edge->GetEndNodeName();
			RuntimeGraph->Edges.Add(Edge->RuntimeEdge);
		}
	}

	// Create runtime edges for Getter nodes.
	for (int i = 0; i < Graph->AllNodes.Num(); ++i)
	{
		UInworldNodeAsset* GetterNode = Graph->AllNodes[i];
		if (!GetterNode->RuntimeNode->IsGetter() || 
			GetterNode->ParentNodes.Num() > 0 ||
			GetterNode->NodeAs == EInworldNodeType::START_NODE || 
			GetterNode->NodeAs == EInworldNodeType::START_END_NODE)
		{
			continue;
		}

		UInworldNodeAsset* SourceSibling = nullptr;
		for (UInworldNodeAsset* GetterChild : GetterNode->ChildrenNodes)
		{
			for (UInworldNodeAsset* Sibling : GetterChild->ParentNodes)
			{
				if (IsValid(Sibling) && Sibling != GetterNode && !Sibling->RuntimeNode->IsGetter())
				{
					if (UInworldEdgeAsset* ParentEdge = Sibling->Edges.FindRef(GetterChild))
					{
						if (!ParentEdge->GetIsRequired())
						{
							continue;
						}
					}
					SourceSibling = Sibling;
					break;
				}
			}

			if (SourceSibling)
			{
				break;
			}
		}

		if (!SourceSibling)
		{
			if (Graph->StartNodes.Num() > 0)
			{
				SourceSibling = Graph->StartNodes[0];
			}
		}

		if (SourceSibling) 
		{ 
			const FString SourceName = SourceSibling->RuntimeNode->GetNodeName();
			const FString DestName = GetterNode->RuntimeNode->GetNodeName();
			if (!RuntimeGraph->GetNodeByName(SourceName) || !RuntimeGraph->GetNodeByName(DestName))
			{
				continue;
			}

			bool bEdgeExists = false;
			for (UInworldEdge* ExistingEdge : RuntimeGraph->Edges)
			{
				if (IsValid(ExistingEdge) && ExistingEdge->Source == SourceName && ExistingEdge->Destination == DestName)
				{
					bEdgeExists = true;
					break;
				}
			}
			if (!bEdgeExists)
			{
				UInworldEdge* GetterEdge = NewObject<UInworldEdge>(RuntimeGraph);
				GetterEdge->Source = SourceName;
				GetterEdge->Destination = DestName;
				GetterEdge->IsRequired = true;
				GetterEdge->IsLoopEdge = false;
				RuntimeGraph->Edges.Add(GetterEdge);
			}
		}
	}

	ValidateGraph();
}

void UEdGraph_InworldGraph::RebuildEditorGraph()
{
	UInworldGraphAsset* Graph = GetInworldGraph();

	Graph->AllEditorNodes.Empty();
	Graph->AllEditorEdges.Empty();
	for (int i = 0; i < Nodes.Num(); ++i)
	{
		if (UEdNode_InworldGraphNode* EdNode = Cast<UEdNode_InworldGraphNode>(Nodes[i]))
		{
			if (EdNode->InworldGraphNode == nullptr)
				continue;

			UInworldNodeAsset* Node = EdNode->InworldGraphNode;

			if ((Node->NodeAs == EInworldNodeType::START_NODE && !EdNode->IsRootNode()) ||
				(Node->NodeAs == EInworldNodeType::END_NODE && !EdNode->IsLeafNode()) ||
				(Node->NodeAs == EInworldNodeType::START_END_NODE && !(EdNode->IsRootNode() && EdNode->IsLeafNode())))
			{
				Node->NodeAs = EInworldNodeType::NODE;
			}

			Graph->AllEditorNodes.Add(Node);
		}
		else if (UEdNode_InworldGraphEdge* EdgeNode = Cast<UEdNode_InworldGraphEdge>(Nodes[i]))
		{
			if (EdgeNode->InworldGraphEdge == nullptr)
				continue;
		}
	}
}

UInworldGraphAsset* UEdGraph_InworldGraph::GetInworldGraph() const
{
	return CastChecked<UInworldGraphAsset>(GetOuter());
}

bool UEdGraph_InworldGraph::Modify(bool bAlwaysMarkDirty /*= true*/)
{
	bool Rtn = Super::Modify(bAlwaysMarkDirty);

	GetInworldGraph()->Modify();

	for (int32 i = 0; i < Nodes.Num(); ++i)
	{
		Nodes[i]->Modify();
	}

	return Rtn;
}

void UEdGraph_InworldGraph::Clear()
{
	UInworldGraphAsset* Graph = GetInworldGraph();

	Graph->ClearGraph();
	NodeMap.Reset();
	EdgeMap.Reset();

	for (int i = 0; i < Nodes.Num(); ++i)
	{
		if (UEdNode_InworldGraphNode* EdNode = Cast<UEdNode_InworldGraphNode>(Nodes[i]))
		{
			UInworldNodeAsset* GraphNode = EdNode->InworldGraphNode;
			if (GraphNode)
			{
				GraphNode->ParentNodes.Reset();
				GraphNode->ChildrenNodes.Reset();
				GraphNode->Edges.Reset();
			}
		}
	}
}

void UEdGraph_InworldGraph::ValidateGraph(bool bWithNotification)
{
	if (UInworldGraphAsset* Graph = GetInworldGraph())
	{
		Graph->Validations.Empty();

		TArray<FString> FailedMsgs;
		if (!Graph->RuntimeGraph->GenerateConfig(FailedMsgs))
		{
			FString FaildMsg = FString::Printf(TEXT("Failed for %s:\n"), *Graph->GetName());
			if (FailedMsgs.Num() > 0)
			{
				for (const FString& FailedMsg : FailedMsgs)
				{
					FaildMsg += FString::Printf(TEXT("%s\n"), *FailedMsg);
					Graph->Validations.Add({TEXT("Error"), FailedMsg});
				}
			}
			if (bWithNotification)
			{
				FNotificationInfo Info(FText::FromString(FaildMsg));
				Info.ExpireDuration = 3.0f;
				Info.bUseThrobber = true;
				Info.bUseSuccessFailIcons = true;
				Info.Image = FInworldGraphEditorStyle::Get()->GetBrush("Inworld.Graph.Warning");
				FSlateNotificationManager::Get().AddNotification(Info);
			}
			return;
		}

		if (Graph->IsValidAsSubgraph())
		{
			Graph->Validations.Add({TEXT("Can be used as Subgraph."), TEXT("Has only one Start and one End node.")});
		}
		else
		{
			Graph->Validations.Add({TEXT("Can't be used as Subgraph."), TEXT("Has multiple Start or End nodes.")});
		}
	}
}

void UEdGraph_InworldGraph::SortNodes(TArray<UInworldNodeAsset*> RootNodes)
{
	TArray<UInworldNodeAsset*> SortedNodes;

	TArray<UInworldNodeAsset*> CurrLevel = {RootNodes};
	TArray<UInworldNodeAsset*> NextLevel;
	TSet<UInworldNodeAsset*> Visited;

	auto Comp = [&](const UInworldNodeAsset& L, const UInworldNodeAsset& R)
	{
		UEdNode_InworldGraphNode* EdNode_L = NodeMap[&L];
		UEdNode_InworldGraphNode* EdNode_R = NodeMap[&R];
		return EdNode_L->NodePosX < EdNode_R->NodePosX;
	};

	while (CurrLevel.Num() > 0)
	{
		CurrLevel.Sort(Comp);

		for (UInworldNodeAsset* Node : CurrLevel)
		{
			SortedNodes.Add(Node);
			Visited.Add(Node);

			for (UInworldNodeAsset* Child : Node->ChildrenNodes)
			{
				if (Child->RuntimeNode != nullptr && !Visited.Contains(Child) && !NextLevel.Contains(Child))
				{
					NextLevel.Add(Child);
				}
			}
		}

		CurrLevel = MoveTemp(NextLevel);
		NextLevel.Reset();
	}

	// Append only Getter nodes that feed into the visited subgraph via their children.
	// This preserves Getter nodes connected through implicit runtime edges, while excluding
	// completely disconnected islands.
	{
		TSet<UInworldNodeAsset*> ExtraChainSet;
		for (const TPair<UInworldNodeAsset*, UEdNode_InworldGraphNode*>& Pair : NodeMap)
		{
			UInworldNodeAsset* Node = Pair.Key;
			if (!Node || Visited.Contains(Node) || !IsValid(Node->RuntimeNode))
			{
				continue;
			}
			if (!Node->RuntimeNode->IsGetter())
			{
				continue;
			}
			TArray<UInworldNodeAsset*> Queue = Node->ChildrenNodes;
			TSet<UInworldNodeAsset*> LocalSeen;
			TMap<UInworldNodeAsset*, UInworldNodeAsset*> ParentMap; 
			TArray<UInworldNodeAsset*> ReachedVisited;
			while (Queue.Num() > 0)
			{
				UInworldNodeAsset* Curr = Queue[0];
				Queue.RemoveAt(0);
				if (!Curr || LocalSeen.Contains(Curr))
				{
					continue;
				}
				LocalSeen.Add(Curr);
				/* if (!ParentMap.Contains(Curr))
				{
					// First time we see Curr; parent is whichever node enqueued it last
					// Parent will be set by the enqueuer; for immediate children of getter leave as getter
				} */
				if (Visited.Contains(Curr))
				{
					ReachedVisited.Add(Curr);
					continue;
				}
				for (UInworldNodeAsset* Child : Curr->ChildrenNodes)
				{
					if (Child && !ParentMap.Contains(Child))
					{
						ParentMap.Add(Child, Curr);
					}
					Queue.Add(Child);
				}
			}

			if (ReachedVisited.Num() > 0)
			{
				ExtraChainSet.Add(Node);
				for (UInworldNodeAsset* End : ReachedVisited)
				{
					UInworldNodeAsset* Curr = ParentMap.FindRef(End);
					while (Curr && Curr != Node)
					{
						ExtraChainSet.Add(Curr);
						Curr = ParentMap.FindRef(Curr);
					}
				}
			}
		}
		TArray<UInworldNodeAsset*> ExtraNodesArray = ExtraChainSet.Array();
		ExtraNodesArray.Sort(Comp);
		SortedNodes.Append(ExtraNodesArray);
	}

	UInworldGraphAsset* GraphAsset = GetInworldGraph();
	if (GraphAsset)
	{
		GraphAsset->AllNodes = MoveTemp(SortedNodes);
	}
}

void UEdGraph_InworldGraph::PostEditUndo()
{
	Super::PostEditUndo();

	NotifyGraphChanged();
}

void UEdGraph_InworldGraph::PreSave(FObjectPreSaveContext SaveContext)
{
	if (!IsRunningCommandlet())
	{
		RebuildEditorGraph();
		RebuildInworldGraph();

		RefreshOpenedAssets();

		if (UInworldGraphAsset* GraphAsset = GetInworldGraph())
		{
			UPackage* ThisPackage = GraphAsset->GetOutermost();
			if (ThisPackage)
			{
				const FName ThisPackageName = ThisPackage->GetFName();
				if (UEdGraph_InworldGraphSavePropagationActive)
				{
					Super::PreSave(SaveContext);
					return;
				}

				// Set propagation state
				const bool bWasActive = UEdGraph_InworldGraphSavePropagationActive;
				if (!UEdGraph_InworldGraphSavePropagationActive)
				{
					UEdGraph_InworldGraphSavePropagationActive = true;
				}
				IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();

				TSet<FName> Visited;
				TArray<FName> Queue;

				auto EnqueueReferencers = [&](const FName& PkgName)
				{
					TArray<FName> NextHardArray;
					AssetRegistry.GetReferencers(PkgName, NextHardArray, UE::AssetRegistry::EDependencyCategory::Package,
						UE::AssetRegistry::EDependencyQuery::Hard);
					TArray<FName> NextSoftArray;
					AssetRegistry.GetReferencers(PkgName, NextSoftArray, UE::AssetRegistry::EDependencyCategory::Package,
						UE::AssetRegistry::EDependencyQuery::Soft);
					for (const FName& NextHard : NextHardArray)
					{
						if (NextHard != ThisPackageName && !Visited.Contains(NextHard))
						{
							Visited.Add(NextHard);
							Queue.Add(NextHard);
						}
					}
					for (const FName& NextSoft : NextSoftArray)
					{
						if (NextSoft != ThisPackageName && !Visited.Contains(NextSoft))
						{
							Visited.Add(NextSoft);
							Queue.Add(NextSoft);
						}
					}
				};

				EnqueueReferencers(ThisPackageName);
				for (int32 Index = 0; Index < Queue.Num(); ++Index)
				{
					EnqueueReferencers(Queue[Index]);
				}

				if (Queue.Num() > 0)
				{
					TArray<FName> ParentGraphs;
					for (const FName& RefPkgName : Queue)
					{
						bool bGraphAssetAsSubgraph = false;
						{
							TArray<FAssetData> AssetsInPackage;
							AssetRegistry.GetAssetsByPackageName(RefPkgName, AssetsInPackage);
							const FTopLevelAssetPath GraphAssetClassPath = UInworldGraphAsset::StaticClass()->GetClassPathName();
							for (const FAssetData& AD : AssetsInPackage)
							{
								if (AD.AssetClassPath == GraphAssetClassPath)
								{
									ParentGraphs.Add(AD.AssetName);
									bGraphAssetAsSubgraph = true;
									break;
								}
							}
						}
						if (!bGraphAssetAsSubgraph)
						{
							continue;
						}
					}

					if (ParentGraphs.Num() > 0 && !GraphAsset->IsValidAsSubgraph())
					{
						FString FaildMsg = FString::Printf(
							TEXT("Subgraph must contain exactly one Start and one End node.\n\nUsed as Subgraph for:\n"));
						for (const FName& AssetName : ParentGraphs)
						{
							FaildMsg += FString::Printf(TEXT("%s\n"), *AssetName.ToString());
						}
						FNotificationInfo Info(FText::FromString(FaildMsg));
						Info.ExpireDuration = 5.0f;
						Info.bUseThrobber = true;
						Info.bUseSuccessFailIcons = true;
						Info.Image = FInworldGraphEditorStyle::Get()->GetBrush("Inworld.Graph.Warning");
						FSlateNotificationManager::Get().AddNotification(Info);
					}
				}

				// Reset propagation state
				if (!bWasActive)
				{
					UEdGraph_InworldGraphSavePropagationActive = false;
				}
			}
		}
	}
	Super::PreSave(SaveContext);
}