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

#pragma once

#include "Components/InworldCharacterComponent.h"
#include "Components/InworldSimplePlayerComponent.h"
#include "Conversation/InworldConversationTarget.h"
#include "CoreMinimal.h"
#include "Graph/RuntimeData/InworldGraphRuntimeData_Memory.h"

#include "InworldConversationGroup.generated.h"

class USpeakerRuleDirector;
class UInworldConversationGroupComponent;
// Forward declarations
class UInworldConversationGroup;
class UInworldBasePlayerComponent;
class UInworldGraphComponent;
struct FInworldDataHandle;
struct FInworldData_Audio;
struct FInworldData_Text;
struct FInworldConversationState;
struct FInworldCharacterResponse;

/** Delegate fired when a conversation group is created */
DECLARE_DYNAMIC_DELEGATE_TwoParams(
	FOnInworldConversationGroupCreated, class UInworldConversationGroup*, ConversationGroup, bool, bSuccess);
DECLARE_DELEGATE_TwoParams(
	FOnInworldConversationGroupCreatedNative, class UInworldConversationGroup* /*ConversationGroup*/, bool /*bSuccess*/);

/** Delegate fired when a character is added to this conversation group */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInworldCharacterAddedToConversation, class UInworldConversationGroup*,
	ConversationGroup, UInworldCharacterComponent*, InworldCharacter);
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnInworldCharacterAddedToConversationNative,
	class UInworldConversationGroup* /*ConversationGroup*/, UInworldCharacterComponent* /*InworldCharacter*/);

/** Delegate fired when a character is removed from this conversation group */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInworldCharacterRemovedFromConversation, class UInworldConversationGroup*,
	ConversationGroup, UInworldCharacterComponent*, InworldCharacter);
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnInworldCharacterRemovedFromConversationNative,
	class UInworldConversationGroup* /*ConversationGroup*/, UInworldCharacterComponent* /*InworldCharacter*/);

/** Internal delegate for character response selection */
DECLARE_DELEGATE_TwoParams(
	FOnInworldCharacterToRespond, class UInworldCharacterComponent* /*CharacterToRespond*/, bool /*bSuccess*/);

/**
 * Manages multi-character conversations in the Inworld system.
 *
 * This class orchestrates conversations between multiple AI characters and a player,
 * handling message routing, speaker selection, conversation state management, and
 * event history. It implements the IInworldConversationTarget interface to act as
 * a unified conversation endpoint.
 *
 * Key Features:
 * - Multi-character conversation management
 * - Intelligent speaker selection system
 * - Message broadcasting to all participants
 * - Conversation state and history tracking
 * - Player target integration
 * - Event system for conversation monitoring
 * - Dynamic participant management
 *
 * Usage:
 * - Create via static CreateConversation methods
 * - Add characters using AddCharacter
 * - Set player target for interaction
 * - Messages sent to the group are intelligently routed to appropriate characters
 * - Participants respond based on context and speaker selection algorithms
 */
UCLASS(ClassGroup = (Inworld), BlueprintType)
class INWORLDCHARACTER_API UInworldConversationGroup : public UObject, public IInworldConversationTarget
{
	GENERATED_BODY()

public:
	/**
	 * Creates a new conversation group asynchronously (Blueprint version).
	 *
	 * @param WorldContextObject World context for the conversation
	 * @param Owner The object that owns this conversation group
	 * @param Callback Delegate called when creation is complete
	 * @param SpeakerSelectionGraphAsset (optional) The graph asset for the speaker selection graph
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld", meta = (WorldContext = "WorldContextObject"))
	static void CreateConversation(UObject* WorldContextObject, UObject* Owner, FOnInworldConversationGroupCreated Callback,
		UInworldGraphAsset* SpeakerSelectionGraphAsset = nullptr);

	/**
	 * Creates a new conversation group asynchronously (C++ version).
	 *
	 * @param WorldContextObject World context for the conversation
	 * @param Owner The object that owns this conversation group
	 * @param NativeCallback Native delegate called when creation is complete
	 * @param SpeakerSelectionGraphAsset (optional) The graph asset for the speaker selection graph
	 */
	static void CreateConversation(UObject* WorldContextObject, UObject* Owner,
		FOnInworldConversationGroupCreatedNative NativeCallback, UInworldGraphAsset* SpeakerSelectionGraphAsset = nullptr);

	/**
	 * Adds a character to the conversation group.
	 *
	 * The character will receive messages sent to the group and can participate
	 * in speaker selection for responses.
	 *
	 * @param CharacterComponent The character component to add to the conversation
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld")
	virtual void AddCharacter(UInworldCharacterComponent* CharacterComponent);

	/**
	 * Removes a character from the conversation group.
	 *
	 * @param CharacterId The unique ID of the character to remove
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld")
	virtual void RemoveCharacter(FString CharacterId);

	/**
	 * Manually invokes the next character response in the conversation.
	 *
	 * @param CharacterId Optional specific character ID to respond. If empty, uses speaker selection.
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld")
	virtual void InvokeNextResponse(const FString& CharacterId = "");

	/**
	 * Gets all character participants in the conversation.
	 *
	 * @return Array of character components participating in the conversation
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld")
	TArray<UInworldCharacterComponent*> GetParticipants();

	/**
	 * Sets the player target for this conversation group.
	 *
	 * @param PlayerComponent The player component that will interact with this group
	 */
	virtual void SetPlayerTarget(UInworldBasePlayerComponent* PlayerComponent);

	/**
	 * Removes the current player target from this conversation group.
	 */
	virtual void RemovePlayerTarget();

	/**
	 * Gets the current player target for the conversation.
	 *
	 * @return The player component currently interacting with this conversation
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld")
	UInworldBasePlayerComponent* GetPlayerTarget();

	/**
	 * Gets the current state of the conversation.
	 *
	 * @return The conversation state data
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld")
	FInworldConversationState GetConversationState();

	/**
	 * Clears the group's event history data, resetting all stored interactions.
	 *
	 * This method ensures that the event history runtime data is wiped clean,
	 * removing all previously recorded conversation or event entries. It logs an
	 * error message if the event history data is null or invalid.
	 *
	 * @param IncludeCharacters (optional) If true (false by default), clear the history of all characters in the conversation.
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld|Event History")
	void ClearHistory(bool IncludeCharacters = false) const;

	/**
	 * @brief Sets the director object to be used for rule-based speaker selection.
	 * @param InRuleDirector The director object configured on the component.
	 */
	void SetRuleDirector(USpeakerRuleDirector* InRuleDirector);

protected:
	/** Called when the object is being destroyed - cleans up conversation resources */
	virtual void BeginDestroy() override;

	/**
	 * Cancels the current message, and interrupts all characters in the group.
	 */
	virtual void Interrupt_Implementation() override;

	/**
	 * Sends a message to the conversation group.
	 *
	 * The message contains data that should be processed by the
	 * conversation target's AI system to generate appropriate text or speech responses.
	 *
	 * @param Message A data handle holding the message
	 * @return An FString identifier for the sent message
	 */
	virtual FString SendMessage_Implementation(const FInworldDataHandle& Message) override;

	/**
	 * Sends a message to this conversation group with player information.
	 *
	 * The message contains data that should be processed by the
	 * conversation target's AI system to generate appropriate text or speech responses.
	 *
	 * @param Message A data handle holding the message
	 * @param PlayerProfile Runtime data that holds information about the player that sent the message
	 * @return An FString identifier for the sent message
	 */
	virtual FString SendPlayerMessage_Implementation(
		const FInworldDataHandle& Message, const FInworldPlayerProfile& PlayerProfile) override;

	/**
	 * Sends a message to the conversation group with runtime data.
	 *
	 * The message contains data that should be processed by the
	 * conversation target's AI system to generate appropriate text or speech responses.
	 *
	 * @param Message A data handle holding the message
	 * @param RuntimeData The runtime data
	 * @return An FString identifier for the sent message
	 */
	virtual FString SendDataMessage_Implementation(
		const FInworldDataHandle& Message, const TMap<FName, FInworldDataHandle>& RuntimeData) override;

	/**
	 * Sends a message to one of the participants in the group.
	 *
	 * @param MessageData The message data to send
	 * @param RuntimeData The runtime data to send
	 * @param ResponderId (optional) ID of character that will respond
	 * @return An FString identifier for the sent message
	 */
	virtual FString SendMessageInternal(
		const FInworldDataHandle& MessageData, const TMap<FName, FInworldDataHandle>& RuntimeData, const FString& ResponderId = "");

	/**
	 * Determines which character should respond to a message using speaker selection.
	 *
	 * @param Data The message data to analyze
	 * @param Callback Callback fired with the selected character
	 */
	virtual void GetCharacterToRespond(const FInworldDataHandle& Data, FOnInworldCharacterToRespond Callback);

	/** Cancels the current message being processed and resets its state. */
	void CancelCurrentMessage();

private:
	/** Handles the response of a character's dialogue, updating history and broadcasting the corresponding events */
	void OnCharacterDialogueResponse(UInworldSimpleCharacterComponent* CharacterComponent, const FString& ExecutionId,
		const FInworldCharacterResponse& Response);

	/**
	 * Updates the conversation group's history based on the current response and responding character.
	 *
	 * @param RespondingCharacter The character that is providing the response in the conversation.
	 * @param Response The response details provided by the responding character.
	 */
	void UpdateHistory(UInworldSimpleCharacterComponent* RespondingCharacter, const FInworldCharacterResponse& Response);

public:
	/**
	 * Instruction text provided to AI for next response generation.
	 * This guides how characters should behave in multi-character conversations.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld",
		Meta = (ToolTip =
					"Instruction text provided to AI for next response generation. This guides how characters should behave in multi-character conversations"))
	FString NextResponseInstruction{"consider all individuals present in the conversation."};

	/** Event fired when a player sends a message in the conversation */
	UPROPERTY(BlueprintAssignable, Category = "Inworld")
	FOnInworldConversationPlayerMessage OnPlayerMessage;
	FOnInworldConversationPlayerMessageNative OnPlayerMessageNative;

	/** Event fired when a character sends a message in the conversation */
	UPROPERTY(BlueprintAssignable, Category = "Inworld")
	FOnInworldConversationCharacterMessage OnCharacterMessage;
	FOnInworldConversationCharacterMessageNative OnCharacterMessageNative;

	/** Event fired when a character is added to the conversation */
	UPROPERTY(BlueprintAssignable, Category = "Inworld")
	FOnInworldCharacterAddedToConversation OnCharacterAdded;
	FOnInworldCharacterAddedToConversationNative OnCharacterAddedNative;

	/** Event fired when a character is removed from the conversation */
	UPROPERTY(BlueprintAssignable, Category = "Inworld")
	FOnInworldCharacterRemovedFromConversation OnCharacterRemoved;
	FOnInworldCharacterRemovedFromConversationNative OnCharacterRemovedNative;

protected:
	/** Map of character IDs to character components participating in the conversation */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inworld",
		Meta = (ToolTip = "Map of character IDs to character components participating in the conversation"))
	TMap<FString, TObjectPtr<UInworldCharacterComponent>> ConversationParticipants;

	/** The player component currently interacting with this conversation group */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inworld",
		Meta = (ToolTip = "The player component currently interacting with this conversation group"))
	TWeakObjectPtr<UInworldSimplePlayerComponent> PlayerTarget;

	/** The graph used for intelligent speaker selection in multi-character conversations */
	UPROPERTY(BlueprintReadOnly, Category = "Graph",
		Meta = (ToolTip = "The graph used for intelligent speaker selection in multi-character conversations"))
	TObjectPtr<UInworldGraph> SpeakerSelectionGraph;

	/** Event history runtime data for tracking conversation context */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld",
		Meta = (ToolTip = "Event history runtime data for tracking conversation context"))
	TObjectPtr<UInworldGraphRuntimeData_EventHistory> EventHistory;

	/** Long-term conversation history */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld",
		Meta = (ToolTip = "Memory runtime data for summarizing long conversation histories"))
	TObjectPtr<UInworldGraphRuntimeData_Memory> Memory;

	/** Runtime data key for accessing conversation state information during message processing */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inworld",
		Meta = (ToolTip = "Runtime data key for accessing conversation state information during message processing"))
	FName ConversationStateRuntimeDataKey{"ConversationState"};

	/** Runtime data key for accessing player profile information during message processing */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inworld",
		Meta = (ToolTip = "Runtime data key for accessing player profile information during message processing"))
	FName PlayerProfileRuntimeDataKey{"PlayerProfile"};

	/** Holds a weak reference to the RuleDirector, passed from the component. */
	TWeakObjectPtr<USpeakerRuleDirector> RuleDirectorPtr;

	/** Holds the current message to the group */
	FInworldConversationMessage CurrentMessage;

	/** ID of the last character that spoke in the conversation */
	FString LastSpeaker;

	/** ID of the current speaker selection graph execution */
	FString CurrentSpeakerSelectionGraphExecutionId;

	friend UInworldConversationGroupComponent;

private:
	/** Delegate handle for character graph result callbacks */
	FDelegateHandle OnCharacterGraphResultHandle;
};
