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

#include "EdGraphSchema_K2.h"
#include "Graph/Assets/InworldEdgeAsset.h"
#include "GraphEditor/InworldGraphAssetEditor/ConnectionDrawingPolicy_InworldGraph.h"
#include "GraphEditor/InworldGraphAssetEditor/EdNode_InworldGraphEdge.h"
#include "GraphEditor/InworldGraphAssetEditor/EdNode_InworldGraphNode.h"
#include "GraphEditor/InworldGraphNodeInputOutputSlot.h"
#include "SGraphPanel.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SToolTip.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "GraphEditor/InworldGraphAssetEditor/InworldGraphEditorStyle.h"
#include "Graph/InworldNode.h"
#include "InworldRuntimeSettings.h"

#define LOCTEXT_NAMESPACE "SInworldGraphEdge"

void SEdNode_InworldGraphEdge::Construct(const FArguments& InArgs, UEdNode_InworldGraphEdge* InNode)
{
	this->GraphNode = InNode;
	this->UpdateGraphNode();
}

bool SEdNode_InworldGraphEdge::RequiresSecondPassLayout() const
{
	return true;
}

void SEdNode_InworldGraphEdge::PerformSecondPassLayout(const TMap<UObject*, TSharedRef<SNode>>& NodeToWidgetLookup) const
{
	UEdNode_InworldGraphEdge* EdgeNode = CastChecked<UEdNode_InworldGraphEdge>(GraphNode);


	UEdNode_InworldGraphNode* Start = EdgeNode->GetStartNode();
	UEdNode_InworldGraphNode* End = EdgeNode->GetEndNode();
	if (Start != nullptr && End != nullptr)
	{
		FGeometry StartGeom;
		FGeometry EndGeom;
		const TSharedRef<SNode>* pFromWidget = NodeToWidgetLookup.Find(Start);
		const TSharedRef<SNode>* pToWidget = NodeToWidgetLookup.Find(End);
		if (pFromWidget != nullptr && pToWidget != nullptr)
		{
			const TSharedRef<SNode>& FromWidget = *pFromWidget;
			const TSharedRef<SNode>& ToWidget = *pToWidget;

			const FVector2D StartGeomPosition(Start->NodePosX, Start->NodePosY + FromWidget->GetDesiredSize().Y - 20.f);
			const FVector2D StartGeomSize(FromWidget->GetDesiredSize().X, 20.f);
			StartGeom = FGeometry(StartGeomPosition, FVector2D::ZeroVector, StartGeomSize, 1.0f);
			EndGeom = FGeometry(FVector2D(End->NodePosX, End->NodePosY), FVector2D::ZeroVector, ToWidget->GetDesiredSize(), 1.0f);
		}
	
		const FVector2D StartCenter = FGeometryHelper::CenterOf(StartGeom);
		const FVector2D EndCenter = FGeometryHelper::CenterOf(EndGeom);

		const FVector2D EndSize = EndGeom.GetLocalSize();
	
		int PinIndex = EdgeNode->InworldGraphEdge->EndPinIndex - 1;
		float PinSize = EndSize.X / (End->Pins.Num() - 1);
		float EndX = EndGeom.Position.X + (PinSize / 2.f) + (PinSize * PinIndex);

		const FVector2D EndAnchorPoint = FVector2D(EndX, EndCenter.Y - EndSize.Y * 0.6f);
		const FVector2D SeedPoint = (StartCenter + EndAnchorPoint) * 0.5f;
		const FVector2D StartAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(StartGeom, SeedPoint);

		FVector2D DeltaPos(EndAnchorPoint - StartAnchorPoint);

		const FVector2D NewCenter = StartAnchorPoint + (0.5f * DeltaPos);
		const FVector2D DesiredNodeSize = GetDesiredSize();
		const FVector2D NewPosition = NewCenter - (DesiredNodeSize * 0.5f);

		GraphNode->NodePosX = NewPosition.X;
		GraphNode->NodePosY = NewPosition.Y;
	}
}

void SEdNode_InworldGraphEdge::UpdateGraphNode()
{
	InputPins.Reset();
	OutputPins.Reset();

	RightNodeBox.Reset();
	LeftNodeBox.Reset();

	TSharedPtr<SNodeTitle> NodeTitle = SNew(SNodeTitle, GraphNode);

	this->ContentScale.Bind(this, &SGraphNode::GetContentScale);
	this->GetOrAddSlot(ENodeZone::Center)
			.HAlign(HAlign_Center)
			.VAlign(VAlign_Center)
			[
				SNew(SOverlay)

				+SOverlay::Slot()
				[
					SNew(SImage)
					.Image(FAppStyle::GetBrush("Graph.TransitionNode.ColorSpill"))
					.ColorAndOpacity(this, &SEdNode_InworldGraphEdge::GetEdgeColor)
				]

				+SOverlay::Slot()
				[
					SNew(SImage)
					.Image(this, &SEdNode_InworldGraphEdge::GetEdgeImage)
					.Visibility(this, &SEdNode_InworldGraphEdge::GetEdgeImageVisibility)
				]

				+SOverlay::Slot()
				.Padding(FMargin(4.0f, 4.0f, 4.0f,4.0f))
				[
					SNew(SVerticalBox)

					+SVerticalBox::Slot()
					.HAlign(HAlign_Center)
					.AutoHeight()
					[
						SAssignNew(InlineEditableText, SInlineEditableTextBlock)
						.IsReadOnly(true)
						.ColorAndOpacity(FLinearColor::Black)
						.Visibility(this, &SEdNode_InworldGraphEdge::GetEdgeTitleVisbility)
						.Font(FCoreStyle::GetDefaultFontStyle("Regular", 12))
						.Text(NodeTitle.Get(), &SNodeTitle::GetHeadTitle)
						.ToolTipText(this, &SEdNode_InworldGraphEdge::GetEdgeToolTip)
					]

					+SVerticalBox::Slot()
					.AutoHeight()
					[
						NodeTitle.ToSharedRef()
					]
				]
			];

	this->GetOrAddSlot(ENodeZone::Right)
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6
			.SlotOffset(TAttribute<FVector2D>(this, &SEdNode_InworldGraphEdge::GetWarningSlotOffset))
			.SlotSize(FVector2D(25.0f, 25.0f))
#else
			.SlotOffset2f(TAttribute<FVector2f>(this, &SEdNode_InworldGraphEdge::GetWarningSlotOffset2f))
			.SlotSize2f(FVector2f(25.0f, 25.0f))
#endif
			[
				SNew(SImage)
				.Image(this, &SEdNode_InworldGraphEdge::GetWarningEdgeImage)
				.ToolTipText(this, &SEdNode_InworldGraphEdge::GetWarningEdgeToolTip)
				.Visibility(this, &SEdNode_InworldGraphEdge::GetWarningVisibility)
			];
}

#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 6
FVector2D SEdNode_InworldGraphEdge::GetWarningSlotOffset() const
{
	FVector2D NodeSize = GetDesiredSize();
	float CenterY = (NodeSize.Y / 2.0f) - 10.5f;
	return FVector2D(NodeSize.X + 5.f, CenterY);
}
#else
FVector2f SEdNode_InworldGraphEdge::GetWarningSlotOffset2f() const
{
	FVector2f NodeSize = GetDesiredSize();
	float CenterY = (NodeSize.Y / 2.0f) - 10.5f;
	return FVector2f(NodeSize.X + 5.f, CenterY);
}
#endif

FSlateColor SEdNode_InworldGraphEdge::GetEdgeColor() const
{
	UEdNode_InworldGraphEdge* EdgeNode = CastChecked<UEdNode_InworldGraphEdge>(GraphNode);
	if (EdgeNode != nullptr && EdgeNode->InworldGraphEdge != nullptr)
	{
		return EdgeNode->InworldGraphEdge->GetEdgeColor();
	}
	return FLinearColor(0.9f, 0.9f, 0.9f, 1.0f);
}

FText SEdNode_InworldGraphEdge::GetEdgeToolTip() const
{
	UEdNode_InworldGraphEdge* EdgeNode = CastChecked<UEdNode_InworldGraphEdge>(GraphNode);
	if (EdgeNode != nullptr && EdgeNode->InworldGraphEdge != nullptr)
	{
		if (EdgeNode->InworldGraphEdge->EdgeType)
		{
			FString EdgeType = EdgeNode->InworldGraphEdge->EdgeType->GetName();
			EdgeType.RemoveFromEnd("_C");
			return FText::FromString(EdgeType);
		}
		else
		{
			return LOCTEXT("Inworld.Graph.EdgeNotValid", "Runtime Edge class is not valid");
		}
	}
	return FText();
}

const FSlateBrush* SEdNode_InworldGraphEdge::GetEdgeImage() const
{
	return FAppStyle::GetBrush("Graph.TransitionNode.Icon");
}

EVisibility SEdNode_InworldGraphEdge::GetEdgeImageVisibility() const
{
	UEdNode_InworldGraphEdge* EdgeNode = CastChecked<UEdNode_InworldGraphEdge>(GraphNode);
	if (EdgeNode && EdgeNode->InworldGraphEdge && EdgeNode->InworldGraphEdge->bShouldDrawTitle)
		return EVisibility::Hidden;

	return EVisibility::Visible;
}

EVisibility SEdNode_InworldGraphEdge::GetEdgeTitleVisbility() const
{
	UEdNode_InworldGraphEdge* EdgeNode = CastChecked<UEdNode_InworldGraphEdge>(GraphNode);
	if (EdgeNode && EdgeNode->InworldGraphEdge && EdgeNode->InworldGraphEdge->bShouldDrawTitle)
		return EVisibility::Visible;

	return EVisibility::Collapsed;
}

EVisibility SEdNode_InworldGraphEdge::GetWarningVisibility() const
{
	UEdNode_InworldGraphEdge* EdgeNode = CastChecked<UEdNode_InworldGraphEdge>(GraphNode);
	if (!EdgeNode || !EdgeNode->InworldGraphEdge || !EdgeNode->InworldGraphEdge->EdgeType)
	{
		return EVisibility::Visible;
	}

	UEdNode_InworldGraphNode* Start = EdgeNode->GetStartNode();
	UEdNode_InworldGraphNode* End = EdgeNode->GetEndNode();
	if (!Start || !End || !Start->InworldGraphNode || !End->InworldGraphNode)
	{
		return EVisibility::Collapsed;
	}

	const int32 PinIndexOnNode = EdgeNode->InworldGraphEdge->EndPinIndex;
	const int32 InSlotIndex = (PinIndexOnNode > 0) ? (PinIndexOnNode - 1) : INDEX_NONE;
	
	FInworldNodeInputOutputSlot InSlot;
	if (End->CachedInputSlots.Num() != 0 && End->CachedInputSlots.IsValidIndex(InSlotIndex))
	{
		InSlot = End->CachedInputSlots[InSlotIndex];
		if (!Start->CachedOutputSlot.AreCompatibleWith(InSlot))
		{
			return EVisibility::Visible;
		}
	}
	return EVisibility::Collapsed;
}

const FSlateBrush* SEdNode_InworldGraphEdge::GetWarningEdgeImage() const
{
	UEdNode_InworldGraphEdge* EdgeNode = CastChecked<UEdNode_InworldGraphEdge>(GraphNode);
	if (!EdgeNode || !EdgeNode->InworldGraphEdge || !EdgeNode->InworldGraphEdge->EdgeType)
	{
		return FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error"));
	}
	return FInworldGraphEditorStyle::Get()->GetBrush("Inworld.Graph.Warning");
}

FText SEdNode_InworldGraphEdge::GetWarningEdgeToolTip() const
{
	UEdNode_InworldGraphEdge* EdgeNode = CastChecked<UEdNode_InworldGraphEdge>(GraphNode);
	if (!EdgeNode || !EdgeNode->InworldGraphEdge || !EdgeNode->InworldGraphEdge->EdgeType)
	{
		return LOCTEXT("Inworld.Graph.EdgeNotValid", "Runtime Edge class is not valid");
	}
	return LOCTEXT("Inworld.Graph.TypeMismatch", "Type mismatch");
}


#undef LOCTEXT_NAMESPACE
