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

#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetToolsModule.h"
#include "ContentBrowserModule.h"
#include "EdGraphUtilities.h"
#include "Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h"
#include "Framework/Commands/GenericCommands.h"
#include "Graph/InworldGraph.h"
#include "Graph/Edges/InworldEdge_WithCondition.h"
#include "Graph/Nodes/InworldNode_Custom.h"
#include "Graph/Nodes/InworldNode_SubgraphNode.h"
#include "Graph/Assets/InworldNodeAsset.h"
#include "GraphEditor/InworldGraphAssetEditor/AssetEditorToolbar_InworldGraph.h"
#include "GraphEditor/InworldGraphAssetEditor/AssetGraphSchema_InworldGraph.h"
#include "GraphEditor/InworldGraphAssetEditor/EdGraph_InworldGraph.h"
#include "GraphEditor/InworldGraphAssetEditor/EdNode_InworldGraphEdge.h"
#include "GraphEditor/InworldGraphAssetEditor/EdNode_InworldGraphNode.h"
#include "GraphEditor/InworldGraphAssetEditor/EditorCommands_InworldGraph.h"
#include "GraphEditorActions.h"
#include "HAL/PlatformApplicationMisc.h"
#include "IContentBrowserSingleton.h"
#include "IDetailsView.h"
#include "InworldRuntimeEditor.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Misc/FileHelper.h"
#include "PropertyEditorModule.h"
#include "SourceCodeNavigation.h"
#include "Widgets/Docking/SDockTab.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "EdGraph/EdGraphSchema.h"
#include "EdGraphSchema_K2_Actions.h"
#include "InworldRuntimeUtils.h"

#define LOCTEXT_NAMESPACE "AssetEditor_InworldGraph"

const FName InworldGraphEditorAppName = FName(TEXT("InworldGraphEditorApp"));

struct FInworldGraphAssetEditorTabs
{
	// Tab identifiers
	static const FName ViewportID;
	static const FName InworldGraphPropertyID;
	static const FName InworldGraphSettingsID;
};

//////////////////////////////////////////////////////////////////////////

const FName FInworldGraphAssetEditorTabs::ViewportID(TEXT("Viewport"));
const FName FInworldGraphAssetEditorTabs::InworldGraphPropertyID(TEXT("InworldGraphProperty"));
const FName FInworldGraphAssetEditorTabs::InworldGraphSettingsID(TEXT("InworldGraphEditorSettings"));

//////////////////////////////////////////////////////////////////////////

FAssetEditor_InworldGraph::FAssetEditor_InworldGraph()
{
	EditingGraph = nullptr;
}

FAssetEditor_InworldGraph::~FAssetEditor_InworldGraph()
{
	if (OnPackageSavedDelegateHandle.IsValid())
	{
		UPackage::PackageSavedWithContextEvent.Remove(OnPackageSavedDelegateHandle);
		OnPackageSavedDelegateHandle.Reset();
	}
}

void FAssetEditor_InworldGraph::InitGraphAssetEditor(
	const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, UInworldGraphAsset* Graph)
{
	OnPackageSavedDelegateHandle = UPackage::PackageSavedWithContextEvent.AddRaw(this, &FAssetEditor_InworldGraph::OnPackageSaved);

	EditingGraph = Graph;
	CreateEdGraph();

	FGenericCommands::Register();
	FGraphEditorCommands::Register();
	FEditorCommands_InworldGraph::Register();

	if (!ToolbarBuilder.IsValid())
	{
		ToolbarBuilder = MakeShareable(new FAssetEditorToolbar_InworldGraph(SharedThis(this)));
	}

	BindCommands();

	CreateInternalWidgets();

	TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);

	ToolbarBuilder->AddGraphToolbar(ToolbarExtender);

	// Layout
	const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout =
		FTabManager::NewLayout("InworldGraphEditor_Layout_v1")
			->AddArea(FTabManager::NewPrimaryArea()
					->SetOrientation(Orient_Vertical)
					->Split(FTabManager::NewSplitter()
							->SetOrientation(Orient_Horizontal)
							->SetSizeCoefficient(0.9f)
							->Split(FTabManager::NewStack()
									->SetSizeCoefficient(0.65f)
									->AddTab(FInworldGraphAssetEditorTabs::ViewportID, ETabState::OpenedTab)
									->SetHideTabWell(true))
							->Split(FTabManager::NewSplitter()
									->SetOrientation(Orient_Vertical)
									->Split(FTabManager::NewStack()
											->SetSizeCoefficient(0.7f)
											->AddTab(FInworldGraphAssetEditorTabs::InworldGraphPropertyID, ETabState::OpenedTab)
											->SetHideTabWell(true))
									->Split(FTabManager::NewStack()->SetSizeCoefficient(0.3f)->AddTab(
										FInworldGraphAssetEditorTabs::InworldGraphSettingsID, ETabState::OpenedTab)))));

	const bool bCreateDefaultStandaloneMenu = true;
	const bool bCreateDefaultToolbar = true;
	FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, InworldGraphEditorAppName, StandaloneDefaultLayout,
		bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, EditingGraph, false);

	RegenerateMenusAndToolbars();
}

void FAssetEditor_InworldGraph::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
	WorkspaceMenuCategory =
		InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_InworldGraphEditor", "Inworld Graph Editor"));
	auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();

	FAssetEditorToolkit::RegisterTabSpawners(InTabManager);

	InTabManager
		->RegisterTabSpawner(
			FInworldGraphAssetEditorTabs::ViewportID, FOnSpawnTab::CreateSP(this, &FAssetEditor_InworldGraph::SpawnTab_Viewport))
		.SetDisplayName(LOCTEXT("GraphCanvasTab", "Viewport"))
		.SetGroup(WorkspaceMenuCategoryRef)
		.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "GraphEditor.EventGraph_16x"));

	InTabManager
		->RegisterTabSpawner(FInworldGraphAssetEditorTabs::InworldGraphPropertyID,
			FOnSpawnTab::CreateSP(this, &FAssetEditor_InworldGraph::SpawnTab_Details))
		.SetDisplayName(LOCTEXT("DetailsTab", "Inworld Graph Property"))
		.SetGroup(WorkspaceMenuCategoryRef)
		.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
	// add graph detailes
	InTabManager
		->RegisterTabSpawner(FInworldGraphAssetEditorTabs::InworldGraphSettingsID,
			FOnSpawnTab::CreateSP(this, &FAssetEditor_InworldGraph::SpawnTab_GraphDetails))
		.SetDisplayName(LOCTEXT("EditorSettingsTab", "Inworld Graph Setttings"))
		.SetGroup(WorkspaceMenuCategoryRef)
		.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
}

void FAssetEditor_InworldGraph::UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
	FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);

	InTabManager->UnregisterTabSpawner(FInworldGraphAssetEditorTabs::ViewportID);
	InTabManager->UnregisterTabSpawner(FInworldGraphAssetEditorTabs::InworldGraphPropertyID);
	InTabManager->UnregisterTabSpawner(FInworldGraphAssetEditorTabs::InworldGraphSettingsID);
}

FName FAssetEditor_InworldGraph::GetToolkitFName() const
{
	return FName("FInworldGraphEditor");
}

FText FAssetEditor_InworldGraph::GetBaseToolkitName() const
{
	return LOCTEXT("InworldGraphEditorAppLabel", "Inworld Graph Editor");
}

FText FAssetEditor_InworldGraph::GetToolkitName() const
{
	const bool bDirtyState = EditingGraph->GetOutermost()->IsDirty();

	FFormatNamedArguments Args;
	Args.Add(TEXT("InworldGraphName"), FText::FromString(EditingGraph->GetName()));
	Args.Add(TEXT("DirtyState"), bDirtyState ? FText::FromString(TEXT("*")) : FText::GetEmpty());
	return FText::Format(LOCTEXT("InworldGraphEditorToolkitName", "{InworldGraphName}{DirtyState}"), Args);
}

FText FAssetEditor_InworldGraph::GetToolkitToolTipText() const
{
	return FAssetEditorToolkit::GetToolTipTextForObject(EditingGraph);
}

FLinearColor FAssetEditor_InworldGraph::GetWorldCentricTabColorScale() const
{
	return FLinearColor::White;
}

FString FAssetEditor_InworldGraph::GetWorldCentricTabPrefix() const
{
	return TEXT("InworldGraphEditor");
}

FString FAssetEditor_InworldGraph::GetDocumentationLink() const
{
	return TEXT("");
}

void FAssetEditor_InworldGraph::SaveAsset_Execute()
{
	FAssetEditorToolkit::SaveAsset_Execute();
}

void FAssetEditor_InworldGraph::AddReferencedObjects(FReferenceCollector& Collector)
{
	Collector.AddReferencedObject(EditingGraph);
	Collector.AddReferencedObject(EditingGraph->EdGraph);
}

TSharedRef<SDockTab> FAssetEditor_InworldGraph::SpawnTab_Viewport(const FSpawnTabArgs& Args)
{
	check(Args.GetTabId() == FInworldGraphAssetEditorTabs::ViewportID);

	TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab).Label(LOCTEXT("ViewportTab_Title", "Viewport"));

	if (ViewportWidget.IsValid())
	{
		SpawnedTab->SetContent(ViewportWidget.ToSharedRef());
	}

	return SpawnedTab;
}

TSharedRef<SDockTab> FAssetEditor_InworldGraph::SpawnTab_Details(const FSpawnTabArgs& Args)
{
	check(Args.GetTabId() == FInworldGraphAssetEditorTabs::InworldGraphPropertyID);

	return SNew(SDockTab).Label(LOCTEXT("Details_Title", "Details"))[PropertyWidget.ToSharedRef()];
}

TSharedRef<SDockTab> FAssetEditor_InworldGraph::SpawnTab_GraphDetails(const FSpawnTabArgs& Args)
{
	check(Args.GetTabId() == FInworldGraphAssetEditorTabs::InworldGraphSettingsID);

	return SNew(SDockTab).Label(LOCTEXT("GraphDetails_Title", "Graph Details"))[GraphDetailsWidget.ToSharedRef()];
}

void FAssetEditor_InworldGraph::CreateInternalWidgets()
{
	ViewportWidget = CreateViewportWidget();

	FDetailsViewArgs Args;
	Args.bHideSelectionTip = true;
	Args.NotifyHook = this;

	FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
	PropertyWidget = PropertyModule.CreateDetailView(Args);
	PropertyWidget->SetObject(nullptr);
	PropertyWidget->OnFinishedChangingProperties().AddSP(this, &FAssetEditor_InworldGraph::OnFinishedChangingProperties);

	GraphDetailsWidget = PropertyModule.CreateDetailView(Args);
	GraphDetailsWidget->SetObject(EditingGraph);
	GraphDetailsWidget->OnFinishedChangingProperties().AddSP(this, &FAssetEditor_InworldGraph::OnFinishedChangingProperties);
}

TSharedRef<SGraphEditor> FAssetEditor_InworldGraph::CreateViewportWidget()
{
	FGraphAppearanceInfo AppearanceInfo;
	AppearanceInfo.CornerText = FText::FromString("INWORLD GRAPH");

	CreateCommandList();

	SGraphEditor::FGraphEditorEvents InEvents;
	InEvents.OnSelectionChanged =
		SGraphEditor::FOnSelectionChanged::CreateSP(this, &FAssetEditor_InworldGraph::OnSelectedNodesChanged);
	InEvents.OnNodeDoubleClicked = FSingleNodeEvent::CreateSP(this, &FAssetEditor_InworldGraph::OnNodeDoubleClicked);
	InEvents.OnTextCommitted = FOnNodeTextCommitted::CreateSP(this, &FAssetEditor_InworldGraph::OnNodeTextCommitted);

	return SNew(SGraphEditor)
		.AdditionalCommands(GraphEditorCommands)
		.IsEditable(true)
		.Appearance(AppearanceInfo)
		.GraphToEdit(EditingGraph->EdGraph)
		.GraphEvents(InEvents)
		.AutoExpandActionMenu(true)
		.ShowGraphStateOverlay(false);
}

void FAssetEditor_InworldGraph::BindCommands()
{
	ToolkitCommands->MapAction(FEditorCommands_InworldGraph::Get().CopyJSON,
		FExecuteAction::CreateSP(this, &FAssetEditor_InworldGraph::CopyJSON),
		FCanExecuteAction::CreateSP(this, &FAssetEditor_InworldGraph::CanGenerateJSON));

	ToolkitCommands->MapAction(FEditorCommands_InworldGraph::Get().CreateCustomNode,
		FExecuteAction::CreateSP(this, &FAssetEditor_InworldGraph::CreateCustomNode),
		FCanExecuteAction::CreateSP(this, &FAssetEditor_InworldGraph::CanCreateCustom));

	ToolkitCommands->MapAction(FEditorCommands_InworldGraph::Get().CreateCustomEdge,
		FExecuteAction::CreateSP(this, &FAssetEditor_InworldGraph::CreateCustomEdge),
		FCanExecuteAction::CreateSP(this, &FAssetEditor_InworldGraph::CanCreateCustom));
}

void FAssetEditor_InworldGraph::CreateEdGraph()
{
	if (EditingGraph->EdGraph == nullptr)
	{
		EditingGraph->EdGraph = CastChecked<UEdGraph_InworldGraph>(FBlueprintEditorUtils::CreateNewGraph(
			EditingGraph, NAME_None, UEdGraph_InworldGraph::StaticClass(), UAssetGraphSchema_InworldGraph::StaticClass()));
		EditingGraph->EdGraph->bAllowDeletion = false;

		// Give the schema a chance to fill out any required nodes (like the results node)
		const UEdGraphSchema* Schema = EditingGraph->EdGraph->GetSchema();
		Schema->CreateDefaultNodesForGraph(*EditingGraph->EdGraph);
	}
	if (UEdGraph_InworldGraph* EdInworldGraph = Cast<UEdGraph_InworldGraph>(EditingGraph->EdGraph))
	{
		EdInworldGraph->RebuildEditorGraph();
		EdInworldGraph->ValidateGraph();
	}
}

void FAssetEditor_InworldGraph::CreateCommandList()
{
	if (GraphEditorCommands.IsValid())
	{
		return;
	}

	GraphEditorCommands = MakeShareable(new FUICommandList);

	GraphEditorCommands->MapAction(FEditorCommands_InworldGraph::Get().CreateCustomNode,
		FExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::CreateCustomNode),
		FCanExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::CanCreateCustom));

	GraphEditorCommands->MapAction(FEditorCommands_InworldGraph::Get().CreateCustomEdge,
		FExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::CreateCustomEdge),
		FCanExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::CanCreateCustom));

	GraphEditorCommands->MapAction(FGenericCommands::Get().SelectAll,
		FExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::SelectAllNodes),
		FCanExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::CanSelectAllNodes));

	GraphEditorCommands->MapAction(FGenericCommands::Get().Delete,
		FExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::DeleteSelectedNodes),
		FCanExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::CanDeleteNodes));

	GraphEditorCommands->MapAction(FGenericCommands::Get().Copy,
		FExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::CopySelectedNodes),
		FCanExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::CanCopyNodes));

	GraphEditorCommands->MapAction(FGenericCommands::Get().Cut,
		FExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::CutSelectedNodes),
		FCanExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::CanCutNodes));

	GraphEditorCommands->MapAction(FGenericCommands::Get().Paste,
		FExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::PasteNodes),
		FCanExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::CanPasteNodes));

	GraphEditorCommands->MapAction(FGenericCommands::Get().Duplicate,
		FExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::DuplicateNodes),
		FCanExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::CanDuplicateNodes));

	GraphEditorCommands->MapAction(FGenericCommands::Get().Rename,
		FExecuteAction::CreateSP(this, &FAssetEditor_InworldGraph::OnRenameNode),
		FCanExecuteAction::CreateSP(this, &FAssetEditor_InworldGraph::CanRenameNodes));

	GraphEditorCommands->MapAction(FGraphEditorCommands::Get().CreateComment,
		FExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::OnCreateComment),
		FCanExecuteAction::CreateRaw(this, &FAssetEditor_InworldGraph::CanCreateComment));
}

TSharedPtr<SGraphEditor> FAssetEditor_InworldGraph::GetCurrGraphEditor() const
{
	return ViewportWidget;
}

FGraphPanelSelectionSet FAssetEditor_InworldGraph::GetSelectedNodes() const
{
	FGraphPanelSelectionSet CurrentSelection;
	TSharedPtr<SGraphEditor> FocusedGraphEd = GetCurrGraphEditor();
	if (FocusedGraphEd.IsValid())
	{
		CurrentSelection = FocusedGraphEd->GetSelectedNodes();
	}

	return CurrentSelection;
}

void FAssetEditor_InworldGraph::SanitizeGraphClipboard(FString& Text)
{
	TArray<FString> Lines;
	Text.ParseIntoArrayLines(Lines, false);
	TArray<FString> Kept;
	Kept.Reserve(Lines.Num());

	auto ShouldSkip = [](const FString& Line) -> bool
	{
		// Remove references that can point to private objects in external packages
		const TArray<FString> BadTokens = {
			TEXT("ChildrenNodes"), 
			TEXT("ParentNodes"),
			TEXT("Edges="),
			TEXT("StartNode="), 
			TEXT("EndNode="), 
			TEXT("RuntimeNode="),
			TEXT("RuntimeEdge=")};
		for (const FString& Tok : BadTokens)
		{
			if (Line.Contains(Tok))
			{
				return true;
			}
		}
		return false;
	};

	for (const FString& L : Lines)
	{
		if (!ShouldSkip(L))
		{
			Kept.Add(L);
		}
	}
	Text = FString::Join(Kept, TEXT("\n"));
}

void FAssetEditor_InworldGraph::SelectAllNodes()
{
	TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
	if (CurrentGraphEditor.IsValid())
	{
		CurrentGraphEditor->SelectAllNodes();
	}
}

bool FAssetEditor_InworldGraph::CanSelectAllNodes()
{
	return true;
}

void FAssetEditor_InworldGraph::DeleteSelectedNodes()
{
	TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
	if (!CurrentGraphEditor.IsValid())
	{
		return;
	}

	CurrentGraphEditor->GetCurrentGraph()->Modify();

	const FGraphPanelSelectionSet SelectedNodes = CurrentGraphEditor->GetSelectedNodes();
	CurrentGraphEditor->ClearSelectionSet();

	for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
	{
		UEdGraphNode* EdNode = Cast<UEdGraphNode>(*NodeIt);
		if (EdNode == nullptr || !EdNode->CanUserDeleteNode())
			continue;
		;

		if (UEdNode_InworldGraphNode* EdNode_Node = Cast<UEdNode_InworldGraphNode>(EdNode))
		{
			EdNode_Node->Modify();

			const UEdGraphSchema* Schema = EdNode_Node->GetSchema();
			if (Schema != nullptr)
			{
				Schema->BreakNodeLinks(*EdNode_Node);
			}

			EdNode_Node->DestroyNode();
		}
		else
		{
			EdNode->Modify();
			EdNode->DestroyNode();
		}
	}

	if (UEdGraph_InworldGraph* EdNodeInworldGraph = Cast<UEdGraph_InworldGraph>(CurrentGraphEditor->GetCurrentGraph()))
	{
		EdNodeInworldGraph->RebuildEditorGraph();
	}
}

bool FAssetEditor_InworldGraph::CanDeleteNodes()
{
	// If any of the nodes can be deleted then we should allow deleting
	const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
	for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
	{
		UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
		if (Node != nullptr && Node->CanUserDeleteNode())
		{
			return true;
		}
	}

	return false;
}

void FAssetEditor_InworldGraph::DeleteSelectedDuplicatableNodes()
{
	TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
	if (!CurrentGraphEditor.IsValid())
	{
		return;
	}

	const FGraphPanelSelectionSet OldSelectedNodes = CurrentGraphEditor->GetSelectedNodes();
	CurrentGraphEditor->ClearSelectionSet();

	for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter)
	{
		UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
		if (Node && Node->CanDuplicateNode())
		{
			CurrentGraphEditor->SetNodeSelection(Node, true);
		}
	}

	// Delete the duplicatable nodes
	DeleteSelectedNodes();

	CurrentGraphEditor->ClearSelectionSet();

	for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter)
	{
		if (UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter))
		{
			CurrentGraphEditor->SetNodeSelection(Node, true);
		}
	}
}

void FAssetEditor_InworldGraph::CutSelectedNodes()
{
	CopySelectedNodes();
	DeleteSelectedDuplicatableNodes();
}

bool FAssetEditor_InworldGraph::CanCutNodes()
{
	return CanCopyNodes() && CanDeleteNodes();
}

void FAssetEditor_InworldGraph::CopySelectedNodes()
{
	// Export the selected nodes and place the text on the clipboard
	FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();

	FString ExportedText;

	for (FGraphPanelSelectionSet::TIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
	{
		UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
		if (Node == nullptr)
		{
			SelectedIter.RemoveCurrent();
			continue;
		}

		if (UEdNode_InworldGraphEdge* EdNode_Edge = Cast<UEdNode_InworldGraphEdge>(*SelectedIter))
		{
			UEdNode_InworldGraphNode* StartNode = EdNode_Edge->GetStartNode();
			UEdNode_InworldGraphNode* EndNode = EdNode_Edge->GetEndNode();

			if (!SelectedNodes.Contains(StartNode) || !SelectedNodes.Contains(EndNode))
			{
				SelectedIter.RemoveCurrent();
				continue;
			}
		}

		Node->PrepareForCopying();
	}

	FEdGraphUtilities::ExportNodesToText(SelectedNodes, ExportedText);
	FPlatformApplicationMisc::ClipboardCopy(*ExportedText);
}

bool FAssetEditor_InworldGraph::CanCopyNodes()
{
	// return false;

	// If any of the nodes can be duplicated then we should allow copying
	const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
	for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
	{
		UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
		if (Node && Node->CanDuplicateNode())
		{
			return true;
		}
	}
	return false;
}

void FAssetEditor_InworldGraph::PasteNodes()
{
	TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
	if (CurrentGraphEditor.IsValid())
	{
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6
		PasteNodesHere(CurrentGraphEditor->GetPasteLocation());
#else
		PasteNodesHere(CurrentGraphEditor->GetPasteLocation2f());
#endif
	}
}

#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6
void FAssetEditor_InworldGraph::PasteNodesHere(const FVector2D& Location)
#else
void FAssetEditor_InworldGraph::PasteNodesHere(const FVector2f& Location)
#endif
{
	// Find the graph editor with focus
	TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
	if (!CurrentGraphEditor.IsValid() || EditingGraph == nullptr)
	{
		return;
	}
	// Select the newly pasted stuff
	UEdGraph* EdGraph = CurrentGraphEditor->GetCurrentGraph();

	{
		EdGraph->Modify();

		// Clear the selection set (newly pasted stuff will be selected)
		CurrentGraphEditor->ClearSelectionSet();

		// Grab the text to paste from the clipboard.
		FString TextToImport;
		FPlatformApplicationMisc::ClipboardPaste(TextToImport);
		SanitizeGraphClipboard(TextToImport);

		// Import the nodes
		TSet<UEdGraphNode*> PastedNodes;
		FEdGraphUtilities::ImportNodesFromText(EdGraph, TextToImport, PastedNodes);

		UEdGraph_InworldGraph* EdInworldGraph = Cast<UEdGraph_InworldGraph>(EdGraph);
		for (UEdGraphNode* EdGraphNode : PastedNodes)
		{
			if (UEdNode_InworldGraphNode* EdNodeInworldGraphNode = Cast<UEdNode_InworldGraphNode>(EdGraphNode))
			{
				if (IsValid(EdNodeInworldGraphNode->InworldGraphNode->RuntimeNode))
				{
					EdNodeInworldGraphNode->InworldGraphNode->RuntimeNode =
						DuplicateObject(EdNodeInworldGraphNode->InworldGraphNode->RuntimeNode, EditingGraph->RuntimeGraph);
				}
				else
				{
					EdNodeInworldGraphNode->InworldGraphNode->CreateRuntimeNode(EditingGraph);
					if (!EdNodeInworldGraphNode->InworldGraphNode->SerializedNode.IsEmpty())
					{
						FInworldRuntimeUtils::DeserializeInstance(EdNodeInworldGraphNode->InworldGraphNode->RuntimeNode,
							EdNodeInworldGraphNode->InworldGraphNode->SerializedNode);
					}
				}
			}

			if (UEdNode_InworldGraphEdge* EdNodeInworldGraphEdge = Cast<UEdNode_InworldGraphEdge>(EdGraphNode))
			{
				UInworldEdgeAsset* InworldEdgeAsset = EdNodeInworldGraphEdge->InworldGraphEdge;
				if (InworldEdgeAsset->RuntimeEdge)
				{
					EdNodeInworldGraphEdge->InworldGraphEdge->RuntimeEdge =
						DuplicateObject(EdNodeInworldGraphEdge->InworldGraphEdge->RuntimeEdge, EditingGraph->RuntimeGraph);
				}
				else
				{
					// create runtime edge
					if (InworldEdgeAsset->EdgeType)
					{
						InworldEdgeAsset->RuntimeEdge = NewObject<UInworldEdge>(EditingGraph, InworldEdgeAsset->EdgeType);
						FInworldRuntimeUtils::DeserializeInstance(InworldEdgeAsset->RuntimeEdge, InworldEdgeAsset->SerializedEdge);
					}
				}
			}
		}
		EdInworldGraph->RebuildEditorGraph();

		// Average position of nodes so we can move them while still maintaining relative distances to each other
		FVector2D AvgNodePosition(0.0f, 0.0f);

		for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
		{
			UEdGraphNode* Node = *It;
			AvgNodePosition.X += Node->NodePosX;
			AvgNodePosition.Y += Node->NodePosY;
		}

		float InvNumNodes = 1.0f / float(PastedNodes.Num());
		AvgNodePosition.X *= InvNumNodes;
		AvgNodePosition.Y *= InvNumNodes;

		for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
		{
			UEdGraphNode* Node = *It;
			CurrentGraphEditor->SetNodeSelection(Node, true);

			Node->NodePosX = (Node->NodePosX - AvgNodePosition.X) + Location.X;
			Node->NodePosY = (Node->NodePosY - AvgNodePosition.Y) + Location.Y;

			Node->SnapToGrid(16);

			// Give new node a different Guid from the old one
			Node->CreateNewGuid();
		}
	}

	// Update UI
	CurrentGraphEditor->NotifyGraphChanged();

	UObject* GraphOwner = EdGraph->GetOuter();
	if (GraphOwner)
	{
		GraphOwner->PostEditChange();
		GraphOwner->MarkPackageDirty();
	}
}

bool FAssetEditor_InworldGraph::CanPasteNodes()
{
	TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
	if (!CurrentGraphEditor.IsValid())
	{
		return false;
	}

	FString ClipboardContent;
	FPlatformApplicationMisc::ClipboardPaste(ClipboardContent);

	return FEdGraphUtilities::CanImportNodesFromText(CurrentGraphEditor->GetCurrentGraph(), ClipboardContent);
}

void FAssetEditor_InworldGraph::DuplicateNodes()
{
	CopySelectedNodes();
	PasteNodes();
}

bool FAssetEditor_InworldGraph::CanDuplicateNodes()
{
	return CanCopyNodes();
}


bool FAssetEditor_InworldGraph::CanCreateComment() const
{
	TSharedPtr<SGraphEditor> FocusedGraphEd = GetCurrGraphEditor();
	return FocusedGraphEd.IsValid();
}

void FAssetEditor_InworldGraph::OnCreateComment()
{
	TSharedPtr<SGraphEditor> FocusedGraphEd = GetCurrGraphEditor();
	if (UEdGraph* EdGraph = FocusedGraphEd.IsValid() ? FocusedGraphEd->GetCurrentGraph() : nullptr)
	{
		TSharedPtr<FEdGraphSchemaAction> Action = EdGraph->GetSchema()->GetCreateCommentAction();
		if (Action.IsValid())
		{
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6
			Action->PerformAction(EdGraph, nullptr, FocusedGraphEd->GetPasteLocation());
#else
			Action->PerformAction(EdGraph, nullptr, FocusedGraphEd->GetPasteLocation2f());
#endif
		}
	}
}



void FAssetEditor_InworldGraph::CopyJSON()
{
	if (EditingGraph != nullptr)
	{
		FPlatformApplicationMisc::ClipboardCopy(*EditingGraph->GetGraphConfig());
		FNotificationInfo Info(FText::FromString("JSON config was copied to clipboard"));
		Info.ExpireDuration = 2.0f;
		Info.bUseThrobber = true;
		Info.bUseSuccessFailIcons = true;
		Info.Image = FCoreStyle::Get().GetBrush(TEXT("Icons.Success"));
		FSlateNotificationManager::Get().AddNotification(Info);
	}
}

void FAssetEditor_InworldGraph::CreateCustomNode()
{
	if (EditingGraph != nullptr)
	{
		HandleCreateModeAsset(UInworldNode_Custom::StaticClass());
	}
}

void FAssetEditor_InworldGraph::CreateCustomEdge()
{
	if (EditingGraph != nullptr)
	{
		HandleCreateModeAsset(UInworldEdge_WithCondition::StaticClass());
	}
}

bool FAssetEditor_InworldGraph::CanGenerateJSON() const
{
	return true;
}

bool FAssetEditor_InworldGraph::CanGraphSettings() const
{
	return true;
}

bool FAssetEditor_InworldGraph::CanCreateCustom() const
{
	return true;
}

void FAssetEditor_InworldGraph::HandleCreateModeAsset(UClass* InClass) const
{
	if (EditingGraph != nullptr)
	{
		const FString ClassName = FBlueprintEditorUtils::GetClassNameWithoutSuffix(InClass);

		FString PathName = EditingGraph->GetOutermost()->GetPathName();
		PathName = FPaths::GetPath(PathName);

		// Now that we've generated some reasonable default locations/names for the package, allow the user to have the final say
		// before we create the package and initialize the blueprint inside of it.
		FSaveAssetDialogConfig SaveAssetDialogConfig;
		SaveAssetDialogConfig.DialogTitleOverride = LOCTEXT("SaveAssetDialogTitle", "Save Asset As");
		SaveAssetDialogConfig.DefaultPath = PathName;
		SaveAssetDialogConfig.DefaultAssetName = FString::Printf(TEXT("%s_New"), *ClassName);
		SaveAssetDialogConfig.ExistingAssetPolicy = ESaveAssetDialogExistingAssetPolicy::Disallow;

		const FContentBrowserModule& ContentBrowserModule =
			FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
		const FString SaveObjectPath = ContentBrowserModule.Get().CreateModalSaveAssetDialog(SaveAssetDialogConfig);
		if (!SaveObjectPath.IsEmpty())
		{
			const FString SavePackageName = FPackageName::ObjectPathToPackageName(SaveObjectPath);
			const FString SavePackagePath = FPaths::GetPath(SavePackageName);
			const FString SaveAssetName = FPaths::GetBaseFilename(SavePackageName);

			UPackage* Package = CreatePackage(*SavePackageName);
			if (ensure(Package))
			{
				// Create and init a new Blueprint
				if (UBlueprint* NewBP = FKismetEditorUtilities::CreateBlueprint(InClass, Package, FName(*SaveAssetName),
						BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass()))
				{
					GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(NewBP);

					// Set default node/edge name as asset
					if (NewBP->GeneratedClass)
					{
						UObject* CDO = NewBP->GeneratedClass->GetDefaultObject();
						if (UInworldNode* DefaultNode = Cast<UInworldNode>(CDO))
						{
							DefaultNode->SetNodeName(SaveAssetName);
						}
						else if (UInworldEdge_WithCondition* DefaultEdge = Cast<UInworldEdge_WithCondition>(CDO))
						{
							DefaultEdge->EdgeTitle = SaveAssetName;
						}
					}

					// Notify the asset registry
					FAssetRegistryModule::AssetCreated(NewBP);

					// Mark the package dirty...
					Package->MarkPackageDirty();
				}
			}
		}
	}
}

void FAssetEditor_InworldGraph::OnRenameNode()
{
	TSharedPtr<SGraphEditor> CurrentGraphEditor = GetCurrGraphEditor();
	if (CurrentGraphEditor.IsValid())
	{
		const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
		for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
		{
			UEdGraphNode* SelectedNode = Cast<UEdGraphNode>(*NodeIt);
			if (SelectedNode != NULL && SelectedNode->bCanRenameNode)
			{
				CurrentGraphEditor->IsNodeTitleVisible(SelectedNode, true);
				break;
			}
		}
	}
}

bool FAssetEditor_InworldGraph::CanRenameNodes() const
{
	return GetSelectedNodes().Num() == 1;
}

void FAssetEditor_InworldGraph::OnNodeTextCommitted(const FText& NewText, ETextCommit::Type CommitInfo, UEdGraphNode* NodeBeingChanged)
{
	if (NodeBeingChanged)
	{
		static const FText TranscationTitle = FText::FromString(FString(TEXT("Rename Node")));
		const FScopedTransaction Transaction(TranscationTitle);
		NodeBeingChanged->Modify();
		NodeBeingChanged->OnRenameNode(NewText.ToString());
	}
}


void FAssetEditor_InworldGraph::OnSelectedNodesChanged(const TSet<class UObject*>& NewSelection)
{
	TArray<UObject*> Selection;

	for (UObject* SelectionEntry : NewSelection)
	{
		Selection.Add(SelectionEntry);
		if (UEdNode_InworldGraphNode* EdInworldNode = Cast<UEdNode_InworldGraphNode>(SelectionEntry))
		{
			if (UInworldNode_SubgraphNode* SubgraphNode = Cast<UInworldNode_SubgraphNode>(EdInworldNode->InworldGraphNode->RuntimeNode))
			{
				SubgraphNode->RefreshDetails();
			}
		}
	}

	if (Selection.Num() == 0)
	{
		PropertyWidget->SetObject(nullptr);
	}
	else
	{
		PropertyWidget->SetObjects(Selection);
	}
}

void FAssetEditor_InworldGraph::OnNodeDoubleClicked(UEdGraphNode* Node)
{
	if (Node && Node->CanJumpToDefinition())
	{
		UClass* ClassToOpen{nullptr};
		if (UEdNode_InworldGraphNode* EdNodeInworldGraphNode = Cast<UEdNode_InworldGraphNode>(Node))
		{
			ClassToOpen = EdNodeInworldGraphNode->InworldGraphNode->NodeClass;
		}
		else if (UEdNode_InworldGraphEdge* EdNodeInworldGraphEdge = Cast<UEdNode_InworldGraphEdge>(Node))
		{
			ClassToOpen = EdNodeInworldGraphEdge->InworldGraphEdge->EdgeType;
		}

		if (ClassToOpen && ClassToOpen->ClassGeneratedBy == nullptr &&
			!ClassToOpen->IsChildOf<UInworldNode_SubgraphNode>() &&
			(ClassToOpen->IsChildOf<UInworldNode_Custom>() || ClassToOpen->IsChildOf<UInworldEdge_WithCondition>()))
		{
			FString ClassHeaderPath;
			if (FSourceCodeNavigation::FindClassHeaderPath(ClassToOpen, ClassHeaderPath))
			{
				const FString AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ClassHeaderPath);

				TArray<FString> Lines;
				if (FFileHelper::LoadFileToStringArray(Lines, *AbsolutePath))
				{
					int32 FoundLine{0};
					const FString ClassName = FString::Printf(TEXT("%s "), *ClassToOpen->GetName());

					for (int32 i = 0; i < Lines.Num(); ++i)
					{
						if (!Lines[i].Contains(TEXT("@class")) && Lines[i].Contains(TEXT("class")) &&
							Lines[i].Contains(ClassName))
						{
							FoundLine = i + 1;
							break;
						}
					}

					const FString AbsoluteHeaderPath =
						IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ClassHeaderPath);
					FSourceCodeNavigation::OpenSourceFile(AbsoluteHeaderPath, FoundLine);
				}
				else
				{
					const FString AbsoluteHeaderPath =
						IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ClassHeaderPath);
					FSourceCodeNavigation::OpenSourceFile(AbsoluteHeaderPath);
				}
			}
		}
		else
		{
			Node->JumpToDefinition();
		}
	}
}

void FAssetEditor_InworldGraph::OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent)
{
	if (EditingGraph == nullptr)
		return;

	EditingGraph->EdGraph->GetSchema()->ForceVisualizationCacheClear();
}

void FAssetEditor_InworldGraph::OnPackageSaved(const FString& Event, UPackage* Package, FObjectPostSaveContext Context)
{
	if (!Package)
	{
		return;
	}

	bool bIsGraphAsset{false};
	ForEachObjectWithPackage(Package,
		[&bIsGraphAsset](UObject* InObject)
		{
			if (InObject && InObject->IsA<UInworldGraphAsset>())
			{
				bIsGraphAsset = true;
				return true;
			}
			return true;
		});

	if (bIsGraphAsset && GraphDetailsWidget.IsValid())
	{
		UEdGraph_InworldGraph::RefreshOpenedAssets();
		GraphDetailsWidget->ForceRefresh();
	}
}

void FAssetEditor_InworldGraph::RegisterToolbarTab(const TSharedRef<class FTabManager>& InTabManager)
{
	FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
}

#undef LOCTEXT_NAMESPACE
