/**
 * Copyright 2022-2025 Theai, Inc. dba Inworld AI
 */

#include "Components/InworldCharacterComponent.h"

#include "Conversation/InworldConversationGroup.h"
#include "Graph/InworldData.h"
#include "Graph/RuntimeData/InworldGraphRuntimeData_EmotionState.h"
#include "Graph/RuntimeData/InworldGraphRuntimeData_Goals.h"
#include "Graph/RuntimeData/InworldGraphRuntimeData_Intent.h"
#include "Graph/RuntimeData/InworldGraphRuntimeData_Knowledge.h"
#include "Graph/RuntimeData/InworldGraphRuntimeData_RelationState.h"
#include "InworldBlueprintFunctionLibrary.h"
#include "InworldCharacter.h"
#include "InworldCharacterBFL.h"

void UInworldCharacterComponent::BeginPlay()
{
	Super::BeginPlay();

	if (Knowledge == nullptr)
	{
		Knowledge = NewObject<UInworldGraphRuntimeData_Knowledge>(this);
	}
	if (KnowledgeFilter == nullptr)
	{
		KnowledgeFilter = NewObject<UInworldGraphRuntimeData_KnowledgeFilter>(this);
	}
	if (Intents == nullptr)
	{
		Intents = NewObject<UInworldGraphRuntimeData_Intent>(this);
	}
	if (Goals == nullptr)
	{
		Goals = NewObject<UInworldGraphRuntimeData_Goals>(this);
	}
	if (EmotionState == nullptr)
	{
		EmotionState = NewObject<UInworldGraphRuntimeData_EmotionState>(this);
	}
	if (RelationState == nullptr)
	{
		RelationState = NewObject<UInworldGraphRuntimeData_RelationState>(this);
	}

	if (IsValid(EmotionGraphAsset))
	{
		EmotionGraphAsset->GetGraphInstance(FOnGraphCompiledNative::CreateWeakLambda(this,
			[this](UInworldGraph* Graph, bool bSuccess)
			{
				if (bSuccess)
				{
					EmotionGraph = Cast<UInworldGraph>(Graph);
					TrySignalReady();
				}
				else
				{
					UE_LOG(LogInworldCharacter, Error, TEXT("UInworldPlayerComponent::BeginPlay - Failed to get Emotion graph."));
				}
			}));
	}
	if (IsValid(RelationGraphAsset))
	{
		RelationGraphAsset->GetGraphInstance(FOnGraphCompiledNative::CreateWeakLambda(this,
			[this](UInworldGraph* Graph, bool bSuccess)
			{
				if (bSuccess)
				{
					RelationGraph = Cast<UInworldGraph>(Graph);
					TrySignalReady();
				}
				else
				{
					UE_LOG(LogInworldCharacter, Error, TEXT("UInworldPlayerComponent::BeginPlay - Failed to get Relation graph."));
				}
			}));
	}
}

void UInworldCharacterComponent::TrySendNextMessage()
{
	if (!bIsReady || MessageQueue.IsEmpty() || CurrentMessage.IsValid())
	{
		return;
	}

	MessageQueue.Dequeue(CurrentMessage);

	UpdateConversationState();

	TMap<FName, FInworldDataHandle> RuntimeData;
	GetRuntimeData(RuntimeData);
	for (auto& KeyValuePair : CurrentMessage.RuntimeData)
	{
		RuntimeData.Emplace(KeyValuePair.Key, KeyValuePair.Value);
	}

	CurrentMessage.TimeSent = FDateTime::Now();
	CurrentMessage.ExecutionId = DialogueGenerationGraph->Execute(this, CurrentMessage.DataHandle, RuntimeData,
		FOnGraphResultNative::CreateWeakLambda(this,
			[this](const FString& ExecutionId, const FString& NodeId, const FInworldDataHandle& Data)
			{
				OnDialogueGenerationResult(ExecutionId, NodeId, Data);
			}),
		FOnGraphStatusNative::CreateWeakLambda(this,
			[this](const FString& ExecutionId, EInworldGraphExecutionStatus Status)
			{
				OnDialogueGenerationStatus(ExecutionId, Status);
			}));
}

void UInworldCharacterComponent::ApplyCharacterData(const FInworldCharacterConfig& Data)
{
	Super::ApplyCharacterData(Data);

	if (EmotionState)
	{
		EmotionState->SetEmotionLabel(Data.InitialEmotion);
	}

	if (RelationState)
	{
		RelationState->UpdateRelation(Data.InitialRelation);
	}

	if (Knowledge)
	{
		Knowledge->Set(Data.InitialKnowledge);
	}

	if (KnowledgeFilter)
	{
		KnowledgeFilter->Set(Data.KnowledgeFilter);
	}

	if (Goals)
	{
		Goals->Set(Data.GoalTable);
	}

	if (Intents)
	{
		Intents->Set(Data.InitialIntents);
	}
}

EInworldComponentType UInworldCharacterComponent::GetComponentType() const
{
	return EInworldComponentType::Full;
}

void UInworldCharacterComponent::OnDialogueGenerationResult_Implementation(
	const FString& ExecutionId, const FString& NodeId, const FInworldDataHandle& DataHandle)
{
	Super::OnDialogueGenerationResult_Implementation(ExecutionId, NodeId, DataHandle);

	if (FInworldData_Struct* Struct = DataHandle.Unwrap<FInworldData_Struct>())
	{
		if (FInworldData_SafetyResult SafetyResult; Struct->GetData(SafetyResult))
		{
			CurrentResponse.UnSafe = !SafetyResult.bIsSafe;
			if (SafetyResult.bIsSafe)
			{
				if (NodeId == "PlayerOut")
				{
					CurrentResponse.Query = SafetyResult.Text;
				}
				if (NodeId == "ResponseOut")
				{
					CurrentResponse.Result = SafetyResult.Text;
				}
			}
			return;
		}

		if (FInworldCharacterAction Action; Struct->GetData(Action) && !Action.GoalName.IsNone())
		{
			if (Goals)
			{
				Goals->CompleteGoal(Action.GoalName);
			}
			OnGoalComplete.Broadcast(this, Action.GoalName);
			OnGoalCompleteNative.Broadcast(this, Action.GoalName);
		}
	}
}

void UInworldCharacterComponent::OnDialogueGenerationStatus_Implementation(
	const FString& ExecutionId, EInworldGraphExecutionStatus Status)
{
	if (ExecutionId != CurrentMessage.ExecutionId)
	{
		return;
	}

	FInworldPlayerProfile PlayerProfile;
	bool HasPlayerProfile = CurrentMessage.GetPlayerProfileRuntimeData(PlayerProfileRuntimeDataKey, PlayerProfile);

	Super::OnDialogueGenerationStatus_Implementation(ExecutionId, Status);
	// Note: The current message has now been reset.

	if (Status == EInworldGraphExecutionStatus::Completed)
	{
		UpdateEmotionState();

		if (HasPlayerProfile)
		{
			UpdateRelationState(PlayerProfile);
		}
		else
		{
			UE_LOG(LogInworldCharacter, Warning, TEXT("Failed to update relation state, missing Player Profile."));
		}
	}
}

void UInworldCharacterComponent::GetRuntimeData(TMap<FName, FInworldDataHandle>& OutRuntimeData) const
{
	Super::GetRuntimeData(OutRuntimeData);	  // CharacterProfile, Voice, EventHistory
	if (EmotionState)
	{
		OutRuntimeData.Add(EmotionState->GetDataPair());
	}
	if (RelationState)
	{
		OutRuntimeData.Add(RelationState->GetDataPair());
	}
	if (Knowledge)
	{
		OutRuntimeData.Add(Knowledge->GetDataPair());
	}
	if (KnowledgeFilter)
	{
		OutRuntimeData.Add(KnowledgeFilter->GetDataPair());
	}
	if (Goals)
	{
		OutRuntimeData.Add(Goals->GetDataPair());
	}
	if (Intents)
	{
		OutRuntimeData.Add(Intents->GetDataPair());
	}
	OutRuntimeData.Emplace(ConversationStateRuntimeDataKey, FInworldData_Struct(ConversationState));
}

void UInworldCharacterComponent::UpdateEmotionState()
{
	if (EmotionGraph == nullptr)
	{
		return;
	}

	EmotionGraph->Execute(this, FInworldData_Text(), {EventHistory->GetDataPair(), CharacterProfile->GetDataPair()},
		FOnGraphResultNative::CreateWeakLambda(this,
			[this](const FString& ExecutionId, const FString& NodeId, const FInworldDataHandle& DataHandle)
			{
				if (FInworldData_Text* TextData = DataHandle.Unwrap<FInworldData_Text>())
				{
					EmotionState->UpdateEmotion(TextData->Text);
				}
			}));
}

void UInworldCharacterComponent::UpdateRelationState(const FInworldPlayerProfile& PlayerProfile)
{
	if (RelationGraph == nullptr)
	{
		return;
	}

	RelationGraph->Execute(this, FInworldData_Text(),
		{EventHistory->GetDataPair(), CharacterProfile->GetDataPair(),
			{PlayerProfileRuntimeDataKey, FInworldData_Struct(PlayerProfile)}},
		FOnGraphResultNative::CreateWeakLambda(this,
			[this](const FString& ExecutionId, const FString& NodeId, const FInworldDataHandle& DataHandle)
			{
				if (FInworldData_Text* TextData = DataHandle.Unwrap<FInworldData_Text>())
				{
					RelationState->UpdateRelation(TextData->Text);
				}
			}));
}

void UInworldCharacterComponent::UpdateConversationState()
{
	ConversationState = {};
	if (ConversationGroup != nullptr)
	{
		ConversationState = ConversationGroup->GetConversationState();
	}
	else
	{
		ConversationState.IsGroup = false;
		if (CharacterProfile != nullptr)
		{
			ConversationState.Participants.Add({Id, CharacterProfile->Get().Name});
		}
	}

	if (FInworldPlayerProfile PlayerProfile; CurrentMessage.GetPlayerProfileRuntimeData(PlayerProfileRuntimeDataKey, PlayerProfile))
	{
		if (ConversationGroup == nullptr)
		{
			ConversationState.LastSpeakerName = PlayerProfile.Name;
			ConversationState.IsPlayerInConversation = true;
		}
	}
}

void UInworldCharacterComponent::TrySignalReady()
{
	if (EmotionGraph != nullptr && RelationGraph != nullptr && Knowledge != nullptr && Knowledge != nullptr && Intents != nullptr &&
		Goals != nullptr)
	{
		Super::TrySignalReady();
	}
}

TArray<FRuntimeDataInfo> UInworldCharacterComponent::GetRuntimeDataAndType() const
{
	TArray<FRuntimeDataInfo> DataInfoArray;
	DataInfoArray.Add({EventHistory, UInworldGraphRuntimeData_EventHistory::StaticClass()});
	DataInfoArray.Add({EmotionState, UInworldGraphRuntimeData_EmotionState::StaticClass()});
	DataInfoArray.Add({RelationState, UInworldGraphRuntimeData_RelationState::StaticClass()});
	DataInfoArray.Add({Knowledge, UInworldGraphRuntimeData_Knowledge::StaticClass()});
	DataInfoArray.Add({Goals, UInworldGraphRuntimeData_Goals::StaticClass()});
	DataInfoArray.Add({Intents, UInworldGraphRuntimeData_Intent::StaticClass()});
	return DataInfoArray;
}
