/**
 * Copyright 2022-2025 Theai, Inc. dba Inworld AI
 */
#include "GraphEditor/InworldGraphAssetEditor/EdNode_InworldGraphNode.h"
#include "GraphEditor/InworldGraphAssetEditor/EdGraph_InworldGraph.h"
#include "GraphEditor/InworldGraphAssetEditor/EdNode_InworldGraphEdge.h"
#include "GraphEditor/InworldGraphNodeInputOutputSlot.h"
#include "Graph/InworldNode.h"
#include "Graph/InworldGraph.h"
#include "Graph/Assets/InworldGraphAsset.h"
#include "Graph/Nodes/InworldNode_Custom.h"
#include "Graph/Nodes/InworldNode_SubgraphNode.h"
#include "Editor.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/Kismet2NameValidators.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "InworldRuntimeUtils.h"

#define LOCTEXT_NAMESPACE "EdNode_InworldGraph"

UEdNode_InworldGraphNode::UEdNode_InworldGraphNode()
{
	bCanRenameNode = true;
}

UEdNode_InworldGraphNode::~UEdNode_InworldGraphNode()
{
}

void UEdNode_InworldGraphNode::AllocateDefaultPins()
{
	CreatePin(EGPD_Output, "InworldGraphNode", FName(), TEXT("Out"));
	if (CachedInputSlots.Num() > 0)
	{
		for (const FInworldNodeInputOutputSlot& Slot : CachedInputSlots)
		{
			CreatePin(EGPD_Input, "InworldGraphNode", FName(), "In");
		}
	}
	else
	{
		CreatePin(EGPD_Input, "InworldGraphNode", FName(), "In");
	}
}

FText UEdNode_InworldGraphNode::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	if (InworldGraphNode == nullptr)
	{
		return Super::GetNodeTitle(TitleType);
	}
	else
	{
		return InworldGraphNode->GetNodeTitle();
	}
}

void UEdNode_InworldGraphNode::PrepareForCopying()
{
	InworldGraphNode->Rename(nullptr, this, REN_DontCreateRedirectors | REN_DoNotDirty);
	if (InworldGraphNode->RuntimeNode)
	{
		InworldGraphNode->SerializedNode = FInworldRuntimeUtils::SerializeInstance(InworldGraphNode->RuntimeNode);
	}
}

void UEdNode_InworldGraphNode::AutowireNewNode(UEdGraphPin* FromPin)
{
	Super::AutowireNewNode(FromPin);

	if (FromPin != nullptr)
	{
		UEdNode_InworldGraphNode* FromNodeEd = Cast<UEdNode_InworldGraphNode>(FromPin->GetOwningNode());
		if (FromNodeEd && InworldGraphNode && FromNodeEd->InworldGraphNode)
		{
			if (FromPin->Direction == EGPD_Output)
			{
				UEdGraphPin* CompatibleInputPin = nullptr;
				for (int32 slotIndex = 0; slotIndex < CachedInputSlots.Num(); ++slotIndex)
				{
					const FInworldNodeInputOutputSlot& TargetInSlot = CachedInputSlots[slotIndex];
					if (FromNodeEd->CachedOutputSlot.AreCompatibleWith(TargetInSlot))
					{
						const int32 pinIndexOnNode = 1 + slotIndex; // Pins[0] is output
						if (Pins.IsValidIndex(pinIndexOnNode))
						{
							CompatibleInputPin = Pins[pinIndexOnNode];
						}
						break;
					}
				}

				if (!CompatibleInputPin)
				{
					CompatibleInputPin = GetInputPin();
				}

				if (CompatibleInputPin && GetSchema()->TryCreateConnection(FromPin, CompatibleInputPin))
				{
					FromPin->GetOwningNode()->NodeConnectionListChanged();
				}
			}
			else
			{
				const int32 pinIndexOnSource = FromNodeEd->Pins.Find(FromPin);
				const int32 sourceInputSlotIndex = (pinIndexOnSource > 0) ? (pinIndexOnSource - 1) : INDEX_NONE;
				bool bConnected = false;
				if (FromNodeEd->CachedInputSlots.IsValidIndex(sourceInputSlotIndex))
				{
					const FInworldNodeInputOutputSlot& FromInSlot = FromNodeEd->CachedInputSlots[sourceInputSlotIndex];
					if (CachedOutputSlot.AreCompatibleWith(FromInSlot))
					{
						if (GetSchema()->TryCreateConnection(GetOutputPin(), FromPin))
						{
							FromPin->GetOwningNode()->NodeConnectionListChanged();
							bConnected = true;
						}
					}
				}

				if (!bConnected)
				{
					if (GetSchema()->TryCreateConnection(FromPin, GetInputPin()))
					{
						FromPin->GetOwningNode()->NodeConnectionListChanged();
					}
				}
			}
		}
	}
}

void UEdNode_InworldGraphNode::SetInworldGraphNode(UInworldNodeAsset* InNode)
{
	InworldGraphNode = InNode;
}

UInworldGraphAsset* UEdNode_InworldGraphNode::GetInworldGraph() const
{
	if (UEdGraph_InworldGraph* EdInworldGraph = Cast<UEdGraph_InworldGraph>(GetGraph()))
	{
		return EdInworldGraph->GetInworldGraph();
	}
	return nullptr;
}

UEdGraphPin* UEdNode_InworldGraphNode::GetInputPin(int PinIndex) const
{
	return Pins[PinIndex];
}

UEdGraphPin* UEdNode_InworldGraphNode::GetOutputPin() const
{
	return Pins[0];
}

bool UEdNode_InworldGraphNode::CanJumpToDefinition() const
{
	return (InworldGraphNode != nullptr && InworldGraphNode->RuntimeNode != nullptr);
}

void UEdNode_InworldGraphNode::JumpToDefinition() const
{
	if (IsValid(InworldGraphNode) && IsValid(InworldGraphNode->RuntimeNode))
	{
		if (UInworldNode_SubgraphNode* SubgraphNode = Cast<UInworldNode_SubgraphNode>(InworldGraphNode->RuntimeNode))
		{
			if (SubgraphNode->GetGraphAsset() && GEditor)
			{
				if (UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>())
				{
					AssetEditorSubsystem->OpenEditorForAsset(SubgraphNode->GetGraphAsset());
				}
			}
		}
		else if (UObject* HyperlinkTarget = InworldGraphNode->RuntimeNode->GetClass())
		{
			FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(HyperlinkTarget);
		}
	}
}

void UEdNode_InworldGraphNode::CreateRuntimeNode(UInworldGraphAsset* SubGraphAsset)
{
	if (IsValid(InworldGraphNode))
	{
		InworldGraphNode->CreateRuntimeNode(GetInworldGraph(), SubGraphAsset);
		InworldGraphNode->SetFlags(RF_Transactional);
		
		// Cache Inputs+Outputs
		if (UInworldNode_SubgraphNode* SubgraphRuntimeNode =
				Cast<UInworldNode_SubgraphNode>(InworldGraphNode->RuntimeNode))
		{
			const UInworldNode* StartNode = SubgraphRuntimeNode->GetStartNode();
			const UInworldNode* EndNode = SubgraphRuntimeNode->GetEndNode();
			if (StartNode && EndNode)
			{
				CachedInputSlots = FInworldNodeInputOutputSlot::GetInputOutputSlotsForNodeInput(StartNode);
				CachedOutputSlot = FInworldNodeInputOutputSlot::GetInputOutputSlotForNodeOutput(EndNode);
			}
		}
		else
		{
			CachedInputSlots = FInworldNodeInputOutputSlot::GetInputOutputSlotsForNodeInput(InworldGraphNode->RuntimeNode);
			CachedOutputSlot = FInworldNodeInputOutputSlot::GetInputOutputSlotForNodeOutput(InworldGraphNode->RuntimeNode);
		}
		bCachedSlotsInitialized = true;
	}
}

bool UEdNode_InworldGraphNode::IsLeafNode() const
{
	for (const UEdGraphPin* Pin : Pins)
	{
		if (Pin && Pin->Direction == EEdGraphPinDirection::EGPD_Output && Pin->LinkedTo.Num() > 0)
		{
			return false;
		}
	}
	return true;
}

bool UEdNode_InworldGraphNode::IsRootNode() const
{
	for (const UEdGraphPin* Pin : Pins)
	{
		if (Pin && Pin->Direction == EEdGraphPinDirection::EGPD_Input && Pin->LinkedTo.Num() > 0)
		{
			return false;
		}
	}
	return true;
}

void UEdNode_InworldGraphNode::SetNodeAs(EInworldNodeType InNodeType)
{
	if (InworldGraphNode->NodeAs != InNodeType)
	{
		InworldGraphNode->NodeAs = InNodeType;
	}
	else
	{
		InworldGraphNode->NodeAs = EInworldNodeType::NODE;
	}
	if (UEdGraph* EdGraph = GetGraph())
	{
		EdGraph->Modify();
	}
}

void UEdNode_InworldGraphNode::ForceSyncPinsFromCachedSlots()
{
    TArray<UEdGraphPin*> InputPins;
    for (UEdGraphPin* Pin : Pins)
    {
        if (Pin && Pin->Direction == EGPD_Input)
        {
            InputPins.Add(Pin);
        }
    }

    const int32 CurrentInputPins = InputPins.Num();
    const int32 DesiredInputPins = (CachedInputSlots.IsEmpty() ? 1 : CachedInputSlots.Num());

    bool bChanged = false;
    if (DesiredInputPins > CurrentInputPins)
    {
        Modify();
        for (int32 i = CurrentInputPins; i < DesiredInputPins; ++i)
        {
            CreatePin(EGPD_Input, "InworldGraphNode", FName(), "In");
        }
        bChanged = true;
    }
    else if (DesiredInputPins < CurrentInputPins)
    {
        Modify();
        for (int32 i = CurrentInputPins - 1; i >= DesiredInputPins; --i)
        {
            if (UEdGraphPin* PinToRemove = InputPins[i])
            {
                TArray<UEdGraphPin*> Linked = PinToRemove->LinkedTo;
                for (UEdGraphPin* Other : Linked)
                {
                    if (!Other)
                    {
                        continue;
                    }
                    if (UEdNode_InworldGraphEdge* EdgeNode = Cast<UEdNode_InworldGraphEdge>(Other->GetOwningNode()))
                    {
                        EdgeNode->Modify();
                        EdgeNode->DestroyNode();
                    }
                    else
                    {
                        Other->LinkedTo.Remove(PinToRemove);
                    }
                }
                PinToRemove->LinkedTo.Empty();
                RemovePin(PinToRemove);
            }
        }
        bChanged = true;
    }

    if (bChanged)
    {
        if (UEdGraph* EdGraph = GetGraph())
        {
            EdGraph->NotifyGraphChanged();
        }
    }
}

void UEdNode_InworldGraphNode::PostEditUndo()
{
	UEdGraphNode::PostEditUndo();
}

#undef LOCTEXT_NAMESPACE
