/**
 * Copyright 2022-2025 Theai, Inc. dba Inworld AI
 */
#include "Graph/Assets/InworldGraphAsset.h"

#include "Engine/Engine.h"
#include "Graph/InworldGraph.h"
#include "Graph/Nodes/InworldNode_GetRuntimeData.h"
#include "Graph/Nodes/InworldNode_SubgraphNode.h"
#include "InworldRuntime.h"
#include "InworldRuntimeSubsystem.h"

#define LOCTEXT_NAMESPACE "InworldGraph"

UInworldGraphAsset::UInworldGraphAsset()
{
#if WITH_EDITORONLY_DATA
	EdGraph = nullptr;
	bCanBeCyclical = true;
#endif
}

UInworldGraphAsset::~UInworldGraphAsset()
{
}

void UInworldGraphAsset::ClearGraph()
{
	for (int i = 0; i < AllNodes.Num(); ++i)
	{
		UInworldNodeAsset* Node = AllNodes[i];
		if (Node)
		{
			Node->ParentNodes.Empty();
			Node->ChildrenNodes.Empty();
			Node->Edges.Empty();
		}
	}

	StartNodes.Empty();
	EndNodes.Empty();
	AllNodes.Empty();
	AllEdges.Empty();

	if (IsValid(RuntimeGraph))
	{
		RuntimeGraph->StartNodes.Empty();
		RuntimeGraph->EndNodes.Empty();
		RuntimeGraph->Edges.Empty();
		RuntimeGraph->Nodes.Empty();
		RuntimeGraph->GraphConfig.Empty();
	}
}

void UInworldGraphAsset::CreateRuntimeGraph(UClass* InGraphType)
{
	if (!RuntimeGraph && !HasAnyFlags(RF_ClassDefaultObject))
	{
		RuntimeGraph = NewObject<UInworldGraph>(this, InGraphType);
		RuntimeGraph->SetGraphId(GetName());
		UE_LOG(LogInworldRuntime, Log, TEXT("GraphEditor: Created runtime graph for InworldGraphAssets: %s"), *GetName());
	}
}

#if WITH_EDITOR
void UInworldGraphAsset::PostRename(UObject* OldOuter, const FName OldName)
{
	RuntimeGraph->SetGraphId(GetName());
	Super::PostRename(OldOuter, OldName);
}

void UInworldGraphAsset::PostDuplicate(bool bDuplicateForPIE)
{
	if (!bDuplicateForPIE)
	{
		RuntimeGraph->SetGraphId(GetName());
	}
	Super::PostDuplicate(bDuplicateForPIE);
}
#endif

const FString& UInworldGraphAsset::GetGraphConfig()
{
	TArray<FString> Errors;
	if (!RuntimeGraph->GenerateConfig(Errors))
	{
		UE_LOG(LogInworldGraph, Error, TEXT("UInworldGraphAsset::GetGraphConfig - Failed to generate JSON for %s"),
			*RuntimeGraph->GraphId);
	}
	return RuntimeGraph->GraphConfig;
}

void UInworldGraphAsset::GetGraphInstance(FOnGraphCompiled Callback)
{
	FOnGraphCompiledNative NativeCallback;
	if (Callback.IsBound())
	{
		NativeCallback.BindUFunction(Callback.GetUObject(), Callback.GetFunctionName());
	}
	GetGraphInstance(NativeCallback);
}
void UInworldGraphAsset::GetGraphInstance(FOnGraphCompiledNative Callback)
{
	UInworldGraph* RegisteredGraph = UInworldRuntimeSubsystem::Get()->RegisterGraph(RuntimeGraph, this);
	if (IsValid(RegisteredGraph))
	{
		RegisteredGraph->EnableRemoteConfig = EnableRemoteConfig;
		RegisteredGraph->Compile(Callback);
	}
	else
	{
		Callback.ExecuteIfBound(nullptr, false);
	}
}

TArray<FInworldGraphRuntimeDataItem> UInworldGraphAsset::GetRuntimeDataList()
{
	return GetRuntimeDataList(nullptr);
}

TArray<FInworldGraphRuntimeDataItem> UInworldGraphAsset::GetRuntimeDataList(UInworldNodeAsset* OverwriteNode)
{
	TArray<FInworldGraphRuntimeDataItem> _RuntimeDataList;
	for (UInworldNodeAsset* Node : AllNodes)
	{
		if (Node->RuntimeNode)
		{
			if (UInworldNode_GetRuntimeData* GetRuntimeDataNode = Cast<UInworldNode_GetRuntimeData>(Node->RuntimeNode))
			{
				if (OverwriteNode)
				{
					_RuntimeDataList.Add({GetRuntimeDataNode->RuntimeDataKey.ToString(),
						GetRuntimeDataNode->GetRuntimeDataClassName(), OverwriteNode});
				}
				else
				{
					_RuntimeDataList.Add(
						{GetRuntimeDataNode->RuntimeDataKey.ToString(), GetRuntimeDataNode->GetRuntimeDataClassName(), Node});
				}
			}
			else if (UInworldNode_SubgraphNode* SubgraphNode = Cast<UInworldNode_SubgraphNode>(Node->RuntimeNode))
			{
				if (SubgraphNode->GetGraphAsset())
				{
					if (OverwriteNode)
					{
						_RuntimeDataList.Append(SubgraphNode->GetGraphAsset()->GetRuntimeDataList(OverwriteNode));
					}
					else
					{
						_RuntimeDataList.Append(SubgraphNode->GetGraphAsset()->GetRuntimeDataList(Node));
					}
				}
			}
		}
	}
	_RuntimeDataList.Sort(
		[](const FInworldGraphRuntimeDataItem& A, const FInworldGraphRuntimeDataItem& B)
		{
			return A.Key.Compare(B.Key, ESearchCase::IgnoreCase) < 0;
		});
	return _RuntimeDataList;
}

UInworldGraph* UInworldGraphAsset::CreateGraph(UObject* Owner)
{
	UInworldGraph* InstancedGraph = DuplicateObject<UInworldGraph>(RuntimeGraph, Owner);
	InstancedGraph->SetExecutionMode(ExecutionMode);
	TArray<FString> Failed;
	if (!InstancedGraph->GenerateConfig(Failed))
	{
		UE_LOG(LogInworldGraph, Error, TEXT("UInworldGraphAsset::CreateGraph - Failed to generate JSON for %s"),
			*InstancedGraph->GraphId);
		for (const FString& Error : Failed)
		{
			UE_LOG(LogInworldGraph, Error, TEXT("UInworldGraphAsset::CreateGraph - %s"), *Error);
		}
		return nullptr;
	}
	UE_LOG(LogInworldGraph, Log, TEXT("%s"), *InstancedGraph->GraphConfig);
	return InstancedGraph;
}

void UInworldGraphAsset::Print(bool ToConsole /*= true*/, bool ToScreen /*= true*/)
{
	int Level = 0;
	TArray<UInworldNodeAsset*> CurrLevelNodes = StartNodes;
	TArray<UInworldNodeAsset*> NextLevelNodes;

	while (CurrLevelNodes.Num() != 0)
	{
		for (int i = 0; i < CurrLevelNodes.Num(); ++i)
		{
			UInworldNodeAsset* Node = CurrLevelNodes[i];
			check(Node != nullptr);

			FString Message = FString::Printf(TEXT("%s, Level %d"), *Node->GetNodeTitle().ToString(), Level);

			if (ToConsole)
			{
				UE_LOG(LogInworldRuntime, Display, TEXT("%s"), *Message);
			}

			if (ToScreen && GEngine != nullptr)
			{
				GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Blue, Message);
			}

			for (int j = 0; j < Node->ChildrenNodes.Num(); ++j)
			{
				NextLevelNodes.Add(Node->ChildrenNodes[j]);
			}
		}

		CurrLevelNodes = NextLevelNodes;
		NextLevelNodes.Reset();
		++Level;
	}
}

int UInworldGraphAsset::GetLevelNum() const
{
	int Level = 0;
	TArray<UInworldNodeAsset*> CurrLevelNodes = StartNodes;
	TArray<UInworldNodeAsset*> NextLevelNodes;

	while (CurrLevelNodes.Num() != 0)
	{
		for (int i = 0; i < CurrLevelNodes.Num(); ++i)
		{
			UInworldNodeAsset* Node = CurrLevelNodes[i];
			check(Node != nullptr);

			for (int j = 0; j < Node->ChildrenNodes.Num(); ++j)
			{
				NextLevelNodes.Add(Node->ChildrenNodes[j]);
			}
		}

		CurrLevelNodes = NextLevelNodes;
		NextLevelNodes.Reset();
		++Level;
	}

	return Level;
}

void UInworldGraphAsset::GetNodesByLevel(int Level, TArray<UInworldNodeAsset*>& Nodes)
{
	int CurrLEvel = 0;
	TArray<UInworldNodeAsset*> NextLevelNodes;

	Nodes = StartNodes;

	while (Nodes.Num() != 0)
	{
		if (CurrLEvel == Level)
			break;

		for (int i = 0; i < Nodes.Num(); ++i)
		{
			UInworldNodeAsset* Node = Nodes[i];
			check(Node != nullptr);

			for (int j = 0; j < Node->ChildrenNodes.Num(); ++j)
			{
				NextLevelNodes.Add(Node->ChildrenNodes[j]);
			}
		}

		Nodes = NextLevelNodes;
		NextLevelNodes.Reset();
		++CurrLEvel;
	}
}

bool UInworldGraphAsset::IsValidAsSubgraph()
{
	return (IsValid(RuntimeGraph) && RuntimeGraph->StartNodes.Num() == 1 && RuntimeGraph->EndNodes.Num() == 1);
}

bool UInworldGraphAsset::ValidateGraph()
{
	bool isValid{true};
	Validations.Empty();

	RuntimeDataList.Empty();
	RuntimeDataList = GetRuntimeDataList();

	if (!IsValid(RuntimeGraph))
	{
		Validations.Add(TEXT("Error"), TEXT("RuntimeGraph is not valid."));
		return false;
	}

	TArray<FString> FailedMsgs;
	if (!RuntimeGraph->GenerateConfig(FailedMsgs))
	{
		isValid = false;
		if (FailedMsgs.Num() > 0)
		{
			for (const FString& FailedMsg : FailedMsgs)
			{
				Validations.Add({TEXT("Error"), FailedMsg});
			}
		}
	}

	if (RuntimeGraph->StartNodes.Num() == 0 || RuntimeGraph->EndNodes.Num() == 0)
	{
		isValid = false;
		Validations.Add(TEXT("Error"), TEXT("Graph must have at least one Start node and one End node."));
	}
	if (RuntimeGraph->StartNodes.Num() > 1 || RuntimeGraph->EndNodes.Num() > 1)
	{
		Validations.Add(TEXT("Can't be used as Subgraph."), TEXT("Has multiple Start or End nodes."));
	}
	else if (RuntimeGraph->StartNodes.Num() == 1 && RuntimeGraph->EndNodes.Num() == 1)
	{
		Validations.Add({TEXT("Can be used as Subgraph."), TEXT("Has only one Start and one End node.")});
	}
	return isValid;
}

#undef LOCTEXT_NAMESPACE
