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

#include "Engine/Engine.h"
#include "Graph/InworldGraph.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->Compile(Callback);
	}
	else
	{
		Callback.ExecuteIfBound(nullptr, false);
	}
}

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);
}

#undef LOCTEXT_NAMESPACE
