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

#include "Graph/Assets/InworldGraphAsset.h"
#include "GraphEditor/InworldGraphAssetEditor/Colors_InworldGraph.h"
#include "GraphEditor/InworldGraphAssetEditor/EdNode_InworldGraphNode.h"
#include "GraphEditor/InworldGraphAssetEditor/InworldGraphDragConnection.h"
#include "GraphEditor/InworldGraphNodeInputOutputSlot.h"
#include "GraphEditorSettings.h"
#include "InworldRuntimeEditor.h"
#include "SCommentBubble.h"
#include "SGraphPin.h"
#include "SLevelOfDetailBranchNode.h"
#include "SlateOptMacros.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "InworldRuntimeEditorSettings.h"
#include "GraphEditor/NodeDescriptionParser.h"
#include "GraphEditor/InworldGraphAssetEditor/InworldGraphEditorStyle.h"
#include "Graph/Nodes/InworldNode_Custom.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SSpacer.h"
#include "Runtime/Launch/Resources/Version.h"
#include "ScopedTransaction.h"
#include "EdGraph/EdGraph.h"
#include "Graph/Nodes/InworldNode_SubgraphNode.h"
#include "Graph/InworldGraph.h"

#define LOCTEXT_NAMESPACE "EdNode_InworldGraph"

class SInworldGraphPin : public SGraphPin
{
public:
	SLATE_BEGIN_ARGS(SInworldGraphPin)
	{
	}
	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs, UEdGraphPin* InPin, const FInworldNodeInputOutputSlot* InPinTypeStruct = nullptr)
	{
		this->SetCursor(EMouseCursor::Default);
		if (InPinTypeStruct != nullptr)
		{
			PinType = *InPinTypeStruct;
		}
		else
		{
			PinType.Reset();
		}

		bShowLabel = true;

		GraphPinObj = InPin;
		check(GraphPinObj != nullptr);

		const UEdGraphSchema* Schema = GraphPinObj->GetSchema();
		check(Schema);

		TSharedPtr<SWidget> PinContent = SNew(SBorder)
											 .BorderBackgroundColor(this, &SInworldGraphPin::GetPinColor)
											 .HAlign(HAlign_Center)
											 .VAlign(VAlign_Center)
											 .ToolTipText(this, &SInworldGraphPin::GetPinToolTip)
											 .Padding(0.0f)
											 [
												 SNew(STextBlock)
												 .Text(this, &SInworldGraphPin::GetPinName)
												 .ColorAndOpacity(FLinearColor::White)
												 .TextStyle(FAppStyle::Get(), "Graph.Node.PinName")
											 ];

		SBorder::Construct(SBorder::FArguments()
							   .BorderImage(this, &SInworldGraphPin::GetPinBorder)
							   .BorderBackgroundColor(this, &SInworldGraphPin::GetPinColor)
							   .OnMouseButtonDown(this, &SInworldGraphPin::OnPinMouseDown)
							   .Cursor(this, &SInworldGraphPin::GetPinCursor)
							   .Padding(FMargin(0.0f))
							   [
								   PinContent.ToSharedRef()
							   ]);
	}

protected:
	TOptional<FInworldNodeInputOutputSlot> PinType;

	virtual FSlateColor GetPinColor() const override
	{
		if (IsHovered())
		{
			return InworldGraphColors::Pin::Hover;
		}
		else
		{
			FLinearColor PinColor = InworldGraphColors::Pin::Default;
			if (IsValid(GetDefault<UInworldRuntimeEditorSettings>()))
			{
				if (GetDefault<UInworldRuntimeEditorSettings>()->PinsStyle == EInworldGraphEditorPinsStyle::Label)
				{
					return PinColor;
				}
			}
			
			if (const FInworldNodeInputOutputSlot* PinTypePtr = PinType.GetPtrOrNull())
			{
				if (PinTypePtr->ValidTypes.Num() == 1 && PinTypePtr->GetFirstType() != nullptr)
				{
					return GetDefault<UInworldRuntimeEditorSettings>()->GetDataTypeEditorInfo(PinTypePtr->GetFirstType()).Color;
				}
			}

			return PinColor;
		}
	}

	FText GetPinName() const
	{
		const bool bFriendlyLabels = GetDefault<UInworldRuntimeEditorSettings>()->bUseFriendlyNamesForDataTypes;
		FString InLabel{};
		if (IsValid(GetDefault<UInworldRuntimeEditorSettings>()))
		{
			if (GetDefault<UInworldRuntimeEditorSettings>()->PinsStyle == EInworldGraphEditorPinsStyle::Label ||
				GetDefault<UInworldRuntimeEditorSettings>()->PinsStyle == EInworldGraphEditorPinsStyle::LabelAndColor)
			{
				if (GraphPinObj->PinName != "In" && GraphPinObj->PinName != "Out")
				{
					InLabel = GraphPinObj->PinName.ToString();
					if (bFriendlyLabels)
					{
						InLabel.RemoveFromStart("InworldData_");
					}
				}
				else if (const FInworldNodeInputOutputSlot* PinTypePtr = PinType.GetPtrOrNull())
				{
					if (GetDefault<UInworldRuntimeEditorSettings>()->PinsLabelStyle == EInworldGraphEditorPinsLabelStyle::PinName)
					{
						InLabel = PinTypePtr->Name + ((PinTypePtr->bOptional) ? "*" : "");
					}
					else if (PinTypePtr->GetFirstType())
					{
						InLabel = PinTypePtr->GetFirstType()->GetName();
						if (InLabel.Equals("InworldData"))
						{
							InLabel.Empty();
						}
						else if (bFriendlyLabels)
						{

							InLabel.RemoveFromStart("InworldData_");
						}
						InLabel += (PinTypePtr->bOptional) ? "*" : "";
					}
				}
			}
		}
		return FText::FromString(InLabel);
	}

	FText GetPinToolTip() const
	{
		if (const FInworldNodeInputOutputSlot* PinTypePtr = PinType.GetPtrOrNull())
		{
			return FText::FromString(PinTypePtr->GetDescription());
		}
		return FText();
	}

	virtual TSharedRef<SWidget> GetDefaultValueWidget() override
	{
		return SNew(STextBlock);
	}

	const FSlateBrush* GetPinBorder() const
	{
		return FAppStyle::GetBrush(TEXT("Graph.StateNode.Body"));
	}

	virtual TSharedRef<FDragDropOperation> SpawnPinDragEvent(
		const TSharedRef<class SGraphPanel>& InGraphPanel, const TArray<TSharedRef<SGraphPin>>& InStartingPins) override
	{
		FInworldGraphDragConnection::FDraggedPinTable PinHandles;
		PinHandles.Reserve(InStartingPins.Num());
		// since the graph can be refreshed and pins can be reconstructed/replaced
		// behind the scenes, the DragDropOperation holds onto FGraphPinHandles
		// instead of direct widgets/graph-pins
		for (const TSharedRef<SGraphPin>& PinWidget : InStartingPins)
		{
			PinHandles.Add(PinWidget->GetPinObj());
		}

		return FInworldGraphDragConnection::New(InGraphPanel, PinHandles);
	}
};

//////////////////////////////////////////////////////////////////////////
void SEdNode_InworldGraphNode::Construct(const FArguments& InArgs, UEdNode_InworldGraphNode* InNode)
{
	GraphNode = InNode;
	UpdateGraphNode();
	InNode->SEdNode = this;
}

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SEdNode_InworldGraphNode::UpdateGraphNode()
{
	const FMargin NodePadding = FMargin(5);
	const FMargin NamePadding = FMargin(2);

	InputPins.Empty();
	OutputPins.Empty();

	// Reset variables that are going to be exposed, in case we are refreshing an already setup node.
	RightNodeBox.Reset();
	LeftNodeBox.Reset();
	InputPinsBox.Reset();
	OutputPinsBox.Reset();

	GraphNode->ErrorMsg.Empty();

	FLinearColor TitleShadowColor(0.6f, 0.6f, 0.6f);
	TSharedPtr<SErrorText> ErrorText;
	TSharedPtr<SVerticalBox> NodeBody;
	TSharedPtr<SNodeTitle> NodeTitle = SNew(SNodeTitle, GraphNode);

	this->ContentScale.Bind(this, &SGraphNode::GetContentScale);

	this->GetOrAddSlot(ENodeZone::TopLeft)
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6
		.SlotOffset(TAttribute<FVector2D>(this, &SEdNode_InworldGraphNode::GetStartSlotOffset))
		.SlotSize(FVector2D(60.0f, 20.0f))
#else
		.SlotOffset2f(TAttribute<FVector2f>(this, &SEdNode_InworldGraphNode::GetStartSlotOffset2f))
		.SlotSize2f(FVector2f(60.0f, 20.0f))
#endif
			[SNew(SImage)
					.RenderTransform(FSlateRenderTransform(FScale2D(-1.f, 1.f)))
					.Image(FInworldGraphEditorStyle::Get()->GetBrush("Inworld.Graph.InOut"))
					.Visibility(this, &SEdNode_InworldGraphNode::GetStartVisibility)
					.ColorAndOpacity(FColor::FromHex("#00A9F4"))];

	this->GetOrAddSlot(ENodeZone::BottomLeft)

#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6
		.SlotOffset(TAttribute<FVector2D>(this, &SEdNode_InworldGraphNode::GetEndSlotOffset))
		.SlotSize(FVector2D(60.0f, 20.0f))
#else
		.SlotOffset2f(TAttribute<FVector2f>(this, &SEdNode_InworldGraphNode::GetEndSlotOffset2f))
		.SlotSize2f(FVector2f(60.0f, 20.0f))
#endif
			[SNew(SImage)
					.Image(FInworldGraphEditorStyle::Get()->GetBrush("Inworld.Graph.InOut"))
					.Visibility(this, &SEdNode_InworldGraphNode::GetEndVisibility)
					.ColorAndOpacity(FColor::Green)];
	this->GetOrAddSlot(ENodeZone::Center)
		.HAlign(HAlign_Fill)
		.VAlign(VAlign_Center)
		[
			SNew(SBorder)
			.BorderImage(FAppStyle::GetBrush("Graph.StateNode.Body"))
			.Padding(FMargin(3.0f, 10.0f))
			.BorderBackgroundColor(this, &SEdNode_InworldGraphNode::GetBackgroundColor)
			.ToolTipText(this, &SEdNode_InworldGraphNode::GetNodeToolTip)
			[
				SNew(SOverlay)

				+SOverlay::Slot()
				.HAlign(HAlign_Fill)
				.VAlign(VAlign_Fill)
				[
					SNew(SVerticalBox)

					// Input Pin Area
					+SVerticalBox::Slot()
					.AutoHeight()
					.HAlign(HAlign_Fill)
					.Padding(0.0f, -10.0f, 0.0f, 0.0f)
					[
						SAssignNew(InputPinsBox, SHorizontalBox)
					]

					+SVerticalBox::Slot()
					.FillHeight(1)
					[
						SNew(SSpacer)
					]
					// Output Pin Area
					+SVerticalBox::Slot()
					.AutoHeight()
					.HAlign(HAlign_Fill)
					.Padding(0.0f, 0.0f, 0.0f, -10.0f)
					[
						SAssignNew(OutputPinsBox, SHorizontalBox)
					]
				]

				+SOverlay::Slot()
				.HAlign(HAlign_Fill)
				.VAlign(VAlign_Center)
				.Padding(8.0f)
				[
					SNew(SBorder)
					.BorderImage(FAppStyle::GetBrush("Graph.StateNode.ColorSpill"))
					.BorderBackgroundColor(TitleShadowColor)
					.HAlign(HAlign_Center)
					.VAlign(VAlign_Center)
					.Visibility(EVisibility::SelfHitTestInvisible)
					.Padding(6.0f)
					[
						SAssignNew(NodeBody, SVerticalBox)

						// Title
						+SVerticalBox::Slot().AutoHeight()
						[
							SNew(SHorizontalBox)

							// Icon
							+SHorizontalBox::Slot()
							.AutoWidth()
							.VAlign(VAlign_Center)
							[
								SNew(SImage)
								.Image(this, &SEdNode_InworldGraphNode::GetNodeIcon)
							]

							// Node Title
							+SHorizontalBox::Slot()
							.Padding(FMargin(4.0f, 0.0f, 4.0f, 0.0f))
							.VAlign(VAlign_Center)
							[
								SNew(SVerticalBox)

								+SVerticalBox::Slot()
								.AutoHeight()
								[
									SAssignNew(InlineEditableText, SInlineEditableTextBlock)
									.Style(FAppStyle::Get(),"Graph.StateNode." "NodeTitleInlineEditableText")
									.Text(NodeTitle.Get(), &SNodeTitle::GetHeadTitle)
									.OnVerifyTextChanged(this,  &SEdNode_InworldGraphNode::OnVerifyNameTextChanged)
									.OnTextCommitted(this,  &SEdNode_InworldGraphNode::OnNameTextCommited)
									.IsReadOnly(this, &SEdNode_InworldGraphNode::IsNameReadOnly)
									.IsSelected(this, &SEdNode_InworldGraphNode::IsSelectedExclusively)
								]

								+SVerticalBox::Slot()
								.AutoHeight()
								[
									NodeTitle.ToSharedRef()
								]
								
								+SVerticalBox::Slot()
								.AutoHeight()
								[
									SNew(STextBlock)
									.Font(FAppStyle::Get().GetFontStyle("SmallFont"))
									.Visibility(this, &SEdNode_InworldGraphNode::GetNativeNodeIdVisibility)
									.Text(this, &SEdNode_InworldGraphNode::GetNativeNodeIdLabel)
								]
							]
						]

						// Title
						+SVerticalBox::Slot().AutoHeight()
						[
							SNew(SHorizontalBox)
							
							// Error message
							+SHorizontalBox::Slot().AutoWidth()
							[
								SAssignNew(ErrorText, SErrorText)
								.BackgroundColor(this, &SEdNode_InworldGraphNode::GetErrorColor)
								.ToolTipText(this, &SEdNode_InworldGraphNode::GetErrorMsgToolTip)
							]
						]
					]
				]
			]
		];

	// Create comment bubble
	TSharedPtr<SCommentBubble> CommentBubble;
	const FSlateColor CommentColor = GetDefault<UGraphEditorSettings>()->DefaultCommentNodeTitleColor;

	SAssignNew(CommentBubble, SCommentBubble)
		.GraphNode(GraphNode)
		.Text(this, &SGraphNode::GetNodeComment)
		.OnTextCommitted(this, &SGraphNode::OnCommentTextCommitted)
		.ColorAndOpacity(CommentColor)
		.AllowPinning(true)
		.EnableTitleBarBubble(true)
		.EnableBubbleCtrls(true)
		.GraphLOD(this, &SGraphNode::GetCurrentLOD)
		.IsGraphNodeHovered(this, &SGraphNode::IsHovered);

	GetOrAddSlot(ENodeZone::TopCenter)
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6
		.SlotOffset(TAttribute<FVector2D>(CommentBubble.Get(), &SCommentBubble::GetOffset))
		.SlotSize(TAttribute<FVector2D>(CommentBubble.Get(), &SCommentBubble::GetSize))
#else
		.SlotOffset2f(TAttribute<FVector2f>(CommentBubble.Get(), &SCommentBubble::GetOffset2f))
		.SlotSize2f(TAttribute<FVector2f>(CommentBubble.Get(), &SCommentBubble::GetSize2f))
#endif
		.AllowScaling(TAttribute<bool>(CommentBubble.Get(), &SCommentBubble::IsScalingAllowed))
		.VAlign(VAlign_Top)[CommentBubble.ToSharedRef()];

	ErrorColor = FLinearColor::Red;
	ErrorReporting = ErrorText;
	ErrorReporting->SetError(GraphNode->ErrorMsg);

	CreatePinWidgets();
}

EVisibility SEdNode_InworldGraphNode::GetStartVisibility() const
{
	UEdNode_InworldGraphNode* StateNode = CastChecked<UEdNode_InworldGraphNode>(GraphNode);
	if (StateNode->InworldGraphNode->NodeAs == EInworldNodeType::START_NODE || 
		StateNode->InworldGraphNode->NodeAs == EInworldNodeType::START_END_NODE)
	{
		return EVisibility::Visible;
	}
	return EVisibility::Collapsed;
}

EVisibility SEdNode_InworldGraphNode::GetEndVisibility() const
{
	UEdNode_InworldGraphNode* StateNode = CastChecked<UEdNode_InworldGraphNode>(GraphNode);
	if (StateNode->InworldGraphNode->NodeAs == EInworldNodeType::END_NODE ||
		StateNode->InworldGraphNode->NodeAs == EInworldNodeType::START_END_NODE)
	{
		return EVisibility::Visible;
	}
	return EVisibility::Collapsed;
}

FText SEdNode_InworldGraphNode::GetNodeToolTip() const
{
	if (UEdNode_InworldGraphNode* MyNode = CastChecked<UEdNode_InworldGraphNode>(GraphNode))
	{
		if (MyNode->InworldGraphNode->RuntimeNode && MyNode->InworldGraphNode->NodeClass)
		{
			FNodeDescriptionParser Parser(MyNode->InworldGraphNode->RuntimeNode);
			return FText::FromString(Parser.GetMainDescription());
		}
	}
	return FText();
}

EVisibility SEdNode_InworldGraphNode::GetNativeNodeIdVisibility() const
{
	EInworldGraphEditorNodeIdVisibility NativeNodeIdVisibility =
		GetDefault<UInworldRuntimeEditorSettings>()->NativeNodeIdVisibility;
	
	UEdNode_InworldGraphNode* StateNode = CastChecked<UEdNode_InworldGraphNode>(GraphNode);
	if (NativeNodeIdVisibility == EInworldGraphEditorNodeIdVisibility::ShowAllNodes ||
		(NativeNodeIdVisibility == EInworldGraphEditorNodeIdVisibility::ShowOnlyEndNodes &&
		 (StateNode->InworldGraphNode->NodeAs == EInworldNodeType::END_NODE ||
		  StateNode->InworldGraphNode->NodeAs == EInworldNodeType::START_END_NODE))) 
	{
		return EVisibility::Visible;
	}
	return EVisibility::Collapsed;
}

FText SEdNode_InworldGraphNode::GetNativeNodeIdLabel() const
{
	if (UEdNode_InworldGraphNode* StateNode = Cast<UEdNode_InworldGraphNode>(GraphNode))
	{
		if (UInworldNode* RuntimeNode = StateNode->InworldGraphNode->RuntimeNode)
		{
			return FText::FromString(FString::Printf(TEXT("Node Id: %s"), 
				*RuntimeNode->GetNodeNameNative()));
		}
	}
	return FText();
}


void SEdNode_InworldGraphNode::ForceUpdateGraphNode()
{
	bForceRebuildFlag = true;
	UpdateGraphNode();
}

void SEdNode_InworldGraphNode::CreatePinWidgets()
{
	UEdNode_InworldGraphNode* StateNode = CastChecked<UEdNode_InworldGraphNode>(GraphNode);
	if (!StateNode->InworldGraphNode || !StateNode->InworldGraphNode->NodeClass)
	{
		for (int32 PinIdx = 0; PinIdx < StateNode->Pins.Num(); PinIdx++)
		{
			UEdGraphPin* MyPin = StateNode->Pins[PinIdx];
			if (MyPin && !MyPin->bHidden)
			{
				TSharedPtr<SGraphPin> NewPin = SNew(SInworldGraphPin, MyPin);
				AddPin(NewPin.ToSharedRef());
			}
		}
		return;
	}

	if (bForceRebuildFlag || !StateNode->bCachedSlotsInitialized)
	{
		if (UInworldNode_SubgraphNode* SubgraphRuntimeNode =
				Cast<UInworldNode_SubgraphNode>(StateNode->InworldGraphNode->RuntimeNode))
		{
			const UInworldNode* StartNode = SubgraphRuntimeNode->GetStartNode();
			const UInworldNode* EndNode = SubgraphRuntimeNode->GetEndNode();
			if (StartNode && EndNode)
			{
				StateNode->CachedInputSlots = FInworldNodeInputOutputSlot::GetInputOutputSlotsForNodeInput(StartNode);
				StateNode->CachedOutputSlot = FInworldNodeInputOutputSlot::GetInputOutputSlotForNodeOutput(EndNode);
			}
		}
		else if (StateNode->InworldGraphNode->RuntimeNode)
		{
			StateNode->CachedInputSlots =
				FInworldNodeInputOutputSlot::GetInputOutputSlotsForNodeInput(StateNode->InworldGraphNode->RuntimeNode);
			StateNode->CachedOutputSlot =
				FInworldNodeInputOutputSlot::GetInputOutputSlotForNodeOutput(StateNode->InworldGraphNode->RuntimeNode);
		}
		StateNode->bCachedSlotsInitialized = true;
	}
	if (bForceRebuildFlag)
	{
		StateNode->ForceSyncPinsFromCachedSlots();
		bForceRebuildFlag = false;
	}

	TArray<FInworldNodeInputOutputSlot> NewInputSlots;
	FInworldNodeInputOutputSlot NewOutputSlot;

	if (UInworldNode_SubgraphNode* SubgraphRuntimeNode = Cast<UInworldNode_SubgraphNode>(StateNode->InworldGraphNode->RuntimeNode))
	{
		if (SubgraphRuntimeNode->GetGraphAsset())
		{
			const UInworldNode* StartNode = SubgraphRuntimeNode->GetStartNode();
			const UInworldNode* EndNode = SubgraphRuntimeNode->GetEndNode();
			if (StartNode && EndNode)
			{
				NewInputSlots = FInworldNodeInputOutputSlot::GetInputOutputSlotsForNodeInput(StartNode);
				NewOutputSlot = FInworldNodeInputOutputSlot::GetInputOutputSlotForNodeOutput(EndNode);
			}
			else
			{
				GraphNode->ErrorMsg = TEXT("Subgraph must have\none Start and one End node.");
				ErrorReporting->SetError(GraphNode->ErrorMsg);
			}
		}
		else
		{
			GraphNode->ErrorMsg = TEXT("Not valid GraphAsset.");
			ErrorReporting->SetError(GraphNode->ErrorMsg);
		}
	}
	else if (StateNode->InworldGraphNode->RuntimeNode)
	{
		if (StateNode->InworldGraphNode->RuntimeNode->GetProcessFunctions().IsEmpty())
		{
			GraphNode->ErrorMsg = TEXT("Node has no valid Process functions.\n\nImplement at least one function\nwhose name starts with \"Process\".");
			ErrorReporting->SetError(GraphNode->ErrorMsg);
		}

		NewInputSlots = FInworldNodeInputOutputSlot::GetInputOutputSlotsForNodeInput(StateNode->InworldGraphNode->RuntimeNode);
		NewOutputSlot = FInworldNodeInputOutputSlot::GetInputOutputSlotForNodeOutput(StateNode->InworldGraphNode->RuntimeNode);
	}

	if (StateNode->CachedInputSlots.IsEmpty() && NewInputSlots.Num() == 1)
	{
		StateNode->CachedInputSlots.Add(NewInputSlots[0]);
	}
	if (StateNode->CachedInputSlots.Num() == 1 && NewInputSlots.IsEmpty())
	{
		StateNode->CachedInputSlots.Empty();
	}

	bool bNeedRedraw = false;
	if (StateNode->CachedInputSlots.Num() != NewInputSlots.Num())
	{
		bNeedRedraw = true;
	}
	else
	{
		if (StateNode->CachedOutputSlot != NewOutputSlot)
		{
			bNeedRedraw = true;
		}
		else
		{
			for (int32 i = 0; i < StateNode->CachedInputSlots.Num(); ++i)
			{
				if (StateNode->CachedInputSlots[i] != NewInputSlots[i])
				{
					bNeedRedraw = true;
					break;
				}
			}
		}
	}
	if (bNeedRedraw)
	{
		StateNode->CachedOutputSlot = NewOutputSlot;
		StateNode->CachedInputSlots = NewInputSlots;
		StateNode->GetGraph()->NotifyGraphChanged();
	}

	int CachedPinsNum = 1 /*Output*/ + (StateNode->CachedInputSlots.IsEmpty() ? 1 : StateNode->CachedInputSlots.Num());
	if (CachedPinsNum != StateNode->Pins.Num() && GraphNode->ErrorMsg.IsEmpty())
	{
		GraphNode->ErrorMsg = TEXT("Num of Inputs for node was changed.");
		ErrorReporting->SetError(GraphNode->ErrorMsg);
	}

	for (int32 PinIdx = 0; PinIdx < StateNode->Pins.Num(); PinIdx++)
	{
		UEdGraphPin* MyPin = StateNode->Pins[PinIdx];
		if (MyPin && !MyPin->bHidden)
		{
			TSharedPtr<SGraphPin> NewPin;

			if (MyPin->Direction == EGPD_Output) // EGPD_Output
			{
				NewPin = SNew(SInworldGraphPin, MyPin, &StateNode->CachedOutputSlot);
			}
			else // EGPD_Input
			{
				int32 InputPinIndex = PinIdx - 1;
				if (StateNode->CachedInputSlots.IsValidIndex(InputPinIndex))
				{
					NewPin = SNew(SInworldGraphPin, MyPin, &StateNode->CachedInputSlots[InputPinIndex]);
				}
				else
				{
					NewPin = SNew(SInworldGraphPin, MyPin);
				}
			}

			AddPin(NewPin.ToSharedRef());
		}
	}
}

void SEdNode_InworldGraphNode::AddPin(const TSharedRef<SGraphPin>& PinToAdd)
{
	PinToAdd->SetOwner(SharedThis(this));

	const UEdGraphPin* PinObj = PinToAdd->GetPinObj();
	const bool bAdvancedParameter = PinObj && PinObj->bAdvancedView;
	if (bAdvancedParameter)
	{
		PinToAdd->SetVisibility(TAttribute<EVisibility>(PinToAdd, &SGraphPin::IsPinVisibleAsAdvanced));
	}

	// TSharedPtr<SVerticalBox> PinBox;
	TSharedPtr<SHorizontalBox> PinBox;
	if (PinToAdd->GetDirection() == EEdGraphPinDirection::EGPD_Input)
	{
		PinBox = InputPinsBox;
		InputPins.Add(PinToAdd);
	}
	else	// Direction == EEdGraphPinDirection::EGPD_Output
	{
		PinBox = OutputPinsBox;
		OutputPins.Add(PinToAdd);
	}

	if (PinBox)
	{
		PinBox->AddSlot().HAlign(HAlign_Fill).VAlign(VAlign_Fill).FillWidth(1.0f).Padding(2.0f)[PinToAdd];
	}
}

bool SEdNode_InworldGraphNode::IsNameReadOnly() const
{
	UEdNode_InworldGraphNode* EdNode_Node = Cast<UEdNode_InworldGraphNode>(GraphNode);
	check(EdNode_Node != nullptr);
	return (!EdNode_Node->InworldGraphNode->IsNameEditable()) || SGraphNode::IsNameReadOnly();
}

END_SLATE_FUNCTION_BUILD_OPTIMIZATION

void SEdNode_InworldGraphNode::OnNameTextCommited(const FText& InText, ETextCommit::Type CommitInfo)
{
	if (InText.IsEmpty())
	{
		return;
	}

	SGraphNode::OnNameTextCommited(InText, CommitInfo);

	UEdNode_InworldGraphNode* MyNode = CastChecked<UEdNode_InworldGraphNode>(GraphNode);

	if (MyNode != nullptr && MyNode->InworldGraphNode != nullptr)
	{
		const FScopedTransaction Transaction(LOCTEXT("InworldGraphEditorRenameNode", "Inworld Graph Editor: Rename Node"));
		MyNode->Modify();
		MyNode->InworldGraphNode->Modify();
		MyNode->InworldGraphNode->SetNodeTitle(InText);
		UpdateGraphNode();
	}
}

FSlateColor SEdNode_InworldGraphNode::GetBackgroundColor() const
{
	return InworldGraphColors::NodeBody::Default;
}

EVisibility SEdNode_InworldGraphNode::GetDragOverMarkerVisibility() const
{
	return EVisibility::Visible;
}

const FSlateBrush* SEdNode_InworldGraphNode::GetNodeIcon() const
{
	if (ErrorReporting->HasError())
	{
		return FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error"));
	}
	UEdNode_InworldGraphNode* StateNode = CastChecked<UEdNode_InworldGraphNode>(GraphNode);
	if (StateNode && StateNode->InworldGraphNode && StateNode->InworldGraphNode->NodeClass)
	{
		if (StateNode->InworldGraphNode->NodeClass->ClassGeneratedBy != nullptr)
		{
			return FAppStyle::GetBrush(TEXT("LevelEditor.CreateBlankBlueprintClass"));
		}
		else if (StateNode->InworldGraphNode->NodeClass->IsChildOf(UInworldNode_Custom::StaticClass()))
		{
			return FAppStyle::GetBrush(TEXT("MainFrame.AddCodeToProject"));
		}
		else if (StateNode->InworldGraphNode->NodeClass->IsChildOf(UInworldNode_SubgraphNode::StaticClass()))
		{
			return FInworldGraphEditorStyle::Get()->GetBrush("Inworld.Graph.SubGraph");
		}
		else
		{
			return FInworldGraphEditorStyle::Get()->GetBrush("ClassIcon.InworldGraphData");
		}
		// return FAppStyle::GetBrush(TEXT("BTEditor.Graph.BTNode.Icon"));
	}

	GraphNode->ErrorMsg = TEXT("Runtime class is not valid.\nNode must be re-added!");
	ErrorReporting->SetError(GraphNode->ErrorMsg);
	return FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error"));
}



#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6
FVector2D SEdNode_InworldGraphNode::GetStartSlotOffset() const
{
	FVector2D NodeSize = GetDesiredSize();
	float CenterX = (NodeSize.X / 2.0f) + 30.f;
	return FVector2D(CenterX, -20.0f);
}

FVector2D SEdNode_InworldGraphNode::GetEndSlotOffset() const
{
	FVector2D NodeSize = GetDesiredSize();
	float CenterX = (NodeSize.X / 2.0f) - 30.f;
	return FVector2D(CenterX, NodeSize.Y);
}
#else
FVector2f SEdNode_InworldGraphNode::GetStartSlotOffset2f() const
{
	FVector2f NodeSize = GetDesiredSize();
	float CenterX = (NodeSize.X / 2.0f) + 30.f;
	return FVector2f(CenterX, -20.0f);
}

FVector2f SEdNode_InworldGraphNode::GetEndSlotOffset2f() const
{
	FVector2f NodeSize = GetDesiredSize();
	float CenterX = (NodeSize.X / 2.0f) - 30.f;
	return FVector2f(CenterX, NodeSize.Y);
}
#endif

#undef LOCTEXT_NAMESPACE
