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

#include "Components/InworldConversationGroupComponent.h"

#include "Director/SpeakerRuleDirector.h"
#include "InworldCharacter.h"

void UInworldConversationGroupComponent::AddCharacter(UInworldCharacterComponent* CharacterComponent)
{
	if (IsValid(CharacterComponent))
	{
		AddCharacters({CharacterComponent});
	}
}

void UInworldConversationGroupComponent::RemoveCharacter(FString CharacterId)
{
	if (CharacterId.IsEmpty())
	{
		UE_LOG(LogInworldCharacter, Warning, TEXT("UInworldConversationGroupComponent::RemoveCharacter - CharacterId is empty."));
		return;
	}

	UInworldCharacterComponent* CharComp = nullptr;

	if (ConversationGroup)
	{
		TArray<UInworldCharacterComponent*> Participants = ConversationGroup->GetParticipants();
		UInworldCharacterComponent** FoundComp = Participants.FindByPredicate(
			[&CharacterId](const UInworldCharacterComponent* Comp)
			{
				return Comp && Comp->GetId() == CharacterId;
			});

		if (FoundComp)
		{
			CharComp = *FoundComp;
		}
	}

	if (CharComp == nullptr)
	{
		TObjectPtr<UInworldCharacterComponent>* FoundInBatch = CurrentBatchBeingAdded.FindByPredicate(
			[&CharacterId](const TObjectPtr<UInworldCharacterComponent>& Comp)
			{
				return Comp && Comp->GetId() == CharacterId;
			});

		if (FoundInBatch && *FoundInBatch)
		{
			CharComp = (*FoundInBatch).Get();
		}
	}

	if (CharComp)
	{
		RemoveCharacters({CharComp});
	}
	else
	{
		UE_LOG(LogInworldCharacter, Warning,
			TEXT(
				"UInworldConversationGroupComponent::RemoveCharacter - Character with ID '%s' not found in group or pending batch."),
			*CharacterId);
	}
}

void UInworldConversationGroupComponent::AddCharacters(const TArray<UInworldCharacterComponent*>& CharactersToAdd)
{
	if (ConversationGroup == nullptr)
	{
		UE_LOG(LogInworldCharacter, Error, TEXT("UInworldConversationGroupComponent::AddCharacters - Conversation group is null!"));
		return;
	}

	if (CharactersToAdd.Num() == 0)
	{
		UE_LOG(LogInworldCharacter, Log, TEXT("AddCharacters called with empty array. Firing delegate immediately."));
		OnCharactersAdded.Broadcast(CharactersToAdd);
		return;
	}

	// Store the batch and reset the counter
	CurrentBatchBeingAdded = CharactersToAdd;
	PendingReadyCharacterCount = 0;

	for (UInworldCharacterComponent* CharComp : CharactersToAdd)
	{
		if (!IsValid(CharComp))
		{
			UE_LOG(LogInworldCharacter, Warning, TEXT("AddCharacters: Skipping an invalid CharacterComponent in the batch."));
			continue;
		}

		if (CharComp->IsReady())
		{
			ConversationGroup->AddCharacter(CharComp);
		}
		else
		{
			PendingReadyCharacterCount++;
			CharComp->OnConversationTargetReadyNative.AddUObject(
				this, &UInworldConversationGroupComponent::OnCharacterFromBatchReady);
		}
	}

	if (PendingReadyCharacterCount == 0)
	{
		UE_LOG(LogInworldCharacter, Log, TEXT("AddCharacters: All %d characters were already ready. Firing delegate immediately."),
			CurrentBatchBeingAdded.Num());
		OnCharactersAdded.Broadcast(CurrentBatchBeingAdded);
		CurrentBatchBeingAdded.Empty();
	}
	else
	{
		UE_LOG(LogInworldCharacter, Log, TEXT("AddCharacters: Waiting for %d characters to become ready..."),
			PendingReadyCharacterCount);
	}
}

void UInworldConversationGroupComponent::RemoveCharacters(const TArray<UInworldCharacterComponent*>& CharactersToRemove)
{
	if (ConversationGroup == nullptr)
	{
		UE_LOG(
			LogInworldCharacter, Error, TEXT("UInworldConversationGroupComponent::RemoveCharacters - Conversation group is null!"));
		return;
	}

	bool bRemovedPendingCharacter = false;

	for (UInworldCharacterComponent* CharComp : CharactersToRemove)
	{
		if (CharComp)
		{
			ConversationGroup->RemoveCharacter(CharComp->GetId());

			if (CurrentBatchBeingAdded.Contains(CharComp))
			{
				if (!CharComp->IsReady())
				{
					PendingReadyCharacterCount--;
					bRemovedPendingCharacter = true;

					CharComp->OnConversationTargetReadyNative.RemoveAll(this);
				}
			}
		}
	}

	if (bRemovedPendingCharacter && PendingReadyCharacterCount <= 0 && CurrentBatchBeingAdded.Num() > 0)
	{
		UE_LOG(LogInworldCharacter, Log,
			TEXT(
				"RemoveCharacters: Removed last pending character. Firing OnBatchCharactersAdded delegate for original batch of %d characters."),
			CurrentBatchBeingAdded.Num());

		OnCharactersAdded.Broadcast(CurrentBatchBeingAdded);

		CurrentBatchBeingAdded.Empty();
		PendingReadyCharacterCount = 0;
	}

	OnCharactersRemoved.Broadcast(CharactersToRemove);
}

void UInworldConversationGroupComponent::InvokeNextResponse(const FString& CharacterId)
{
	if (ConversationGroup == nullptr)
	{
		UE_LOG(LogInworldCharacter, Error,
			TEXT("UInworldConversationGroupComponent::InvokeNextResponse - Conversation group is null!"));
		return;
	}
	ConversationGroup->InvokeNextResponse(CharacterId);
}

TArray<UInworldCharacterComponent*> UInworldConversationGroupComponent::GetParticipants()
{
	if (ConversationGroup == nullptr)
	{
		UE_LOG(
			LogInworldCharacter, Error, TEXT("UInworldConversationGroupComponent::GetParticipants - Conversation group is null!"));
		return {};
	}
	return ConversationGroup->GetParticipants();
}

UInworldBasePlayerComponent* UInworldConversationGroupComponent::GetPlayerTarget()
{
	if (ConversationGroup == nullptr)
	{
		UE_LOG(
			LogInworldCharacter, Error, TEXT("UInworldConversationGroupComponent::GetPlayerTarget - Conversation group is null!"));
		return {};
	}
	return ConversationGroup->GetPlayerTarget();
}

FInworldConversationState UInworldConversationGroupComponent::GetConversationState()
{
	if (ConversationGroup == nullptr)
	{
		UE_LOG(LogInworldCharacter, Error,
			TEXT("UInworldConversationGroupComponent::GetConversationState - Conversation group is null!"));
		return {};
	}
	return ConversationGroup->GetConversationState();
}

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

	UInworldConversationGroup::CreateConversation(GetWorld(), this,
		FOnInworldConversationGroupCreatedNative::CreateWeakLambda(this,
			[this](UInworldConversationGroup* ConversationGroupObj, bool bSuccess)
			{
				if (bSuccess && ConversationGroupObj != nullptr)
				{
					ConversationGroup = ConversationGroupObj;

					if (IsValid(RuleDirector))
					{
						ConversationGroup->SetRuleDirector(RuleDirector);
					}

					ConversationGroup->OnCharacterMessageNative.AddWeakLambda(this,
						[this](TScriptInterface<IInworldConversationTarget> ConversationTarget,
							UInworldSimpleCharacterComponent* CharacterComponent, const FString& MessageId, const FString& Message)
						{
							OnCharacterMessage.Broadcast(ConversationTarget, CharacterComponent, MessageId, Message);
							OnCharacterMessageNative.Broadcast(ConversationTarget, CharacterComponent, MessageId, Message);
						});
					ConversationGroup->OnPlayerMessageNative.AddWeakLambda(this,
						[this](TScriptInterface<IInworldConversationTarget> ConversationTarget,
							const FInworldPlayerProfile& PlayerProfile, const FString& MessageId, const FInworldDataHandle& Message)
						{
							OnPlayerMessage.Broadcast(ConversationTarget, PlayerProfile, MessageId, Message);
							OnPlayerMessageNative.Broadcast(ConversationTarget, PlayerProfile, MessageId, Message);
						});
					ConversationGroup->OnCharacterAddedNative.AddWeakLambda(this,
						[this](UInworldConversationGroup* Self, UInworldCharacterComponent* CharacterComponent)
						{
							OnCharacterAdded.Broadcast(Self, CharacterComponent);
							OnCharacterAddedNative.Broadcast(Self, CharacterComponent);
						});
					ConversationGroup->OnCharacterRemovedNative.AddWeakLambda(this,
						[this](UInworldConversationGroup* Self, UInworldCharacterComponent* CharacterComponent)
						{
							OnCharacterRemoved.Broadcast(Self, CharacterComponent);
							OnCharacterRemovedNative.Broadcast(Self, CharacterComponent);
						});

					TrySignalReady();
				}
				else
				{
					UE_LOG(LogInworldCharacter, Error, TEXT("Failed to create conversation group."));
				}
			}),
		SpeakerSelectionGraphAsset);
}

void UInworldConversationGroupComponent::Interrupt_Implementation()
{
	if (ConversationGroup == nullptr)
	{
		UE_LOG(LogInworldCharacter, Error,
			TEXT("UInworldConversationGroupComponent::Interrupt_Implementation - Conversation group is null!"));
		return;
	}
	Execute_Interrupt(ConversationGroup);
}

FString UInworldConversationGroupComponent::SendMessage_Implementation(const FInworldDataHandle& Message)
{
	if (ConversationGroup == nullptr)
	{
		UE_LOG(LogInworldCharacter, Error,
			TEXT("UInworldConversationGroupComponent::SendMessage_Implementation - Conversation group is null!"));
		return "";
	}
	return Execute_SendMessage(ConversationGroup, Message);
}

FString UInworldConversationGroupComponent::SendPlayerMessage_Implementation(
	const FInworldDataHandle& Message, const FInworldPlayerProfile& PlayerProfile)
{
	if (ConversationGroup == nullptr)
	{
		UE_LOG(LogInworldCharacter, Error,
			TEXT("UInworldConversationGroupComponent::SendPlayerMessage_Implementation - Conversation group is null!"));
		return "";
	}
	return Execute_SendPlayerMessage(ConversationGroup, Message, PlayerProfile);
}

FString UInworldConversationGroupComponent::SendDataMessage_Implementation(
	const FInworldDataHandle& Message, const TMap<FName, FInworldDataHandle>& RuntimeData)
{
	if (ConversationGroup == nullptr)
	{
		UE_LOG(LogInworldCharacter, Error,
			TEXT("UInworldConversationGroupComponent::SendDataMessage_Implementation - Conversation group is null!"));
		return "";
	}
	return Execute_SendDataMessage(ConversationGroup, Message, RuntimeData);
}

void UInworldConversationGroupComponent::OnCharacterFromBatchReady(UInworldConversationTargetComponent* ReadyComponent)
{
	if (ReadyComponent)
	{
		ReadyComponent->OnConversationTargetReadyNative.RemoveAll(this);
	}

	UInworldCharacterComponent* CharComp = Cast<UInworldCharacterComponent>(ReadyComponent);
	if (!IsValid(CharComp))
	{
		PendingReadyCharacterCount--;
		UE_LOG(LogInworldCharacter, Warning,
			TEXT("OnCharacterFromBatchReady: ReadyComponent was invalid or not a UInworldCharacterComponent."));
		return;
	}

	if (ConversationGroup == nullptr)
	{
		UE_LOG(LogInworldCharacter, Error,
			TEXT("OnCharacterFromBatchReady: ConversationGroup became null while waiting for character %s."), *CharComp->GetId());
		PendingReadyCharacterCount--;
		return;
	}

	ConversationGroup->AddCharacter(CharComp);
	UE_LOG(LogInworldCharacter, Log, TEXT("OnCharacterFromBatchReady: Character %s is now ready and added to the group."),
		*CharComp->GetId());

	PendingReadyCharacterCount--;

	if (PendingReadyCharacterCount <= 0)
	{
		UE_LOG(LogInworldCharacter, Log,
			TEXT(
				"OnCharacterFromBatchReady: All %d characters in the batch are now ready. Firing OnBatchCharactersAdded delegate."),
			CurrentBatchBeingAdded.Num());

		OnCharactersAdded.Broadcast(CurrentBatchBeingAdded);

		CurrentBatchBeingAdded.Empty();
		PendingReadyCharacterCount = 0;
	}
}

void UInworldConversationGroupComponent::ClearHistory(bool IncludeCharacters) const
{
	if (ConversationGroup == nullptr)
	{
		UE_LOG(LogInworldCharacter, Error, TEXT("UInworldConversationGroupComponent::ClearHistory - Conversation group is null!"));
		return;
	}
	ConversationGroup->ClearHistory(IncludeCharacters);
}
