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

#include "Director/SpeakerRuleDirector.h"

#include "InworldCharacter.h"

void USpeakerRuleDirector::SetActiveRule(FName RuleID)
{
	// Check if rule exsts
	if (RuleID != NAME_None && !Rules.FindByPredicate(
								   [RuleID](const FSpeakerRule& Rule)
								   {
									   return Rule.RuleID == RuleID;
								   }))
	{
		UE_LOG(LogInworldCharacter, Warning,
			TEXT("USpeakerRuleDirector::SetActiveRule: RuleID '%s' not found in Rules array. Clearing active rule."),
			*RuleID.ToString());
		ActiveRuleID = NAME_None;
		return;
	}

	ClearActiveRule();
	CurrentSequenceIndex = INDEX_NONE;
	ActiveRuleID = RuleID;
	UE_LOG(LogInworldCharacter, Log, TEXT("USpeakerRuleDirector: Active rule set to '%s'."), *RuleID.ToString());
}

void USpeakerRuleDirector::ClearActiveRule()
{
	UE_LOG(LogInworldCharacter, Log, TEXT("USpeakerRuleDirector: Active rule cleared (was '%s'). Reverting to AI selection."),
		*ActiveRuleID.ToString());
	CurrentSequenceIndex = INDEX_NONE;
	ActiveRuleID = NAME_None;
}

TOptional<FString> USpeakerRuleDirector::GetNextSpeaker(const FString& LastSpeakerID, const TArray<FString>& ParticipantIDs)
{
	if (ActiveRuleID == NAME_None)
	{
		return TOptional<FString>();
	}

	FSpeakerRule* ActiveRule = Rules.FindByPredicate(
		[this](const FSpeakerRule& Rule)
		{
			return Rule.RuleID == ActiveRuleID;
		});

	if (!ActiveRule || ActiveRule->SpeakerSequence.Num() == 0)
	{
		UE_LOG(LogInworldCharacter, Warning,
			TEXT("USpeakerRuleDirector::GetNextSpeaker: Active rule '%s' is invalid or has empty sequence. Clearing rule."),
			*ActiveRuleID.ToString());
		ClearActiveRule();
		return TOptional<FString>();
	}

	int32 NextSpeakerIndex = CurrentSequenceIndex + 1;

	for (int32 i = 0; i < ActiveRule->SpeakerSequence.Num(); ++i)
	{
		int32 CurrentIndex = (NextSpeakerIndex + i) % ActiveRule->SpeakerSequence.Num();

		if (!ActiveRule->bLoop && (NextSpeakerIndex + i) >= ActiveRule->SpeakerSequence.Num())
		{
			UE_LOG(
				LogInworldCharacter, Log, TEXT("USpeakerRuleDirector: Non-looping rule '%s' finished."), *ActiveRuleID.ToString());
			ClearActiveRule();
			return TOptional<FString>();
		}

		const FString& NextSpeakerCandidateID = ActiveRule->SpeakerSequence[CurrentIndex];

		if (ParticipantIDs.Contains(NextSpeakerCandidateID))
		{
			CurrentSequenceIndex = CurrentIndex;
			return TOptional<FString>(NextSpeakerCandidateID);
		}
	}

	UE_LOG(LogInworldCharacter, Warning,
		TEXT(
			"USpeakerRuleDirector::GetNextSpeaker: Active rule '%s' has no valid participants in the current conversation. Clearing rule."),
		*ActiveRuleID.ToString());
	ClearActiveRule();
	return TOptional<FString>();
}
