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

#pragma once

#include "Audio/InworldVoiceAudioComponent.h"
#include "Graph/InworldCharacterData.h"
#include "Graph/InworldGraphComponent.h"
#include "InworldConversationTargetComponent.h"
#include "InworldRuntimeTypes.h"

#include "InworldBaseCharacterComponent.generated.h"

// Forward declarations
class UInworldBaseCharacterComponent;
class UInworldBasePlayerComponent;
class UInworldVoiceAudioComponent;
struct FInworldDataHandle;
struct FInworldData_Audio;
struct FInworldData_Text;
struct FInworldCharacterAction;
struct FInworldVisemeBlends;

/**
 * Delegate fired during character speech playback with viseme blending data.
 * Provides character component, viseme blend values, playback duration, and current time.
 * Useful for driving lip-sync animations in real-time.
 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnInworldCharacterUtterancePlayback, const UInworldBaseCharacterComponent*,
	CharacterComponent, const FInworldVisemeBlends&, VisemeBlends, float, PlaybackDuration, float, PlaybackCurrentTime);

/** Native delegate fired during character speech playback with viseme data */
DECLARE_MULTICAST_DELEGATE_FourParams(FOnInworldCharacterUtterancePlaybackNative,
	const UInworldBaseCharacterComponent* /*CharacterComponent*/, const FInworldVisemeBlends& /*VisemeBlends*/,
	float /*PlaybackDuration*/, float /*PlaybackCurrentTime*/);

/**
 * Delegate fired when character starts speaking.
 * Provides character component and the text being spoken.
 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
	FOnInworldCharacterUtteranceStart, const UInworldBaseCharacterComponent*, CharacterComponent, const FString&, VoiceText);

/** Native delegate fired when character starts speaking */
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnInworldCharacterUtteranceStartNative,
	const UInworldBaseCharacterComponent* /*CharacterComponent*/, const FString& /*VoiceText*/);

/**
 * Delegate fired when character speech is interrupted.
 * Provides character component and the text that was being spoken.
 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
	FOnInworldCharacterUtteranceInterrupt, const UInworldBaseCharacterComponent*, CharacterComponent, const FString&, VoiceText);

/** Native delegate fired when character speech is interrupted */
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnInworldCharacterUtteranceInterruptNative,
	const UInworldBaseCharacterComponent* /*CharacterComponent*/, const FString& /*VoiceText*/);

/**
 * Delegate fired when character finishes speaking.
 * Provides character component and the complete text that was spoken.
 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
	FOnInworldCharacterUtteranceComplete, const UInworldBaseCharacterComponent*, CharacterComponent, const FString&, VoiceText);

/** Native delegate fired when character finishes speaking */
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnInworldCharacterUtteranceCompleteNative,
	const UInworldBaseCharacterComponent* /*CharacterComponent*/, const FString& /*VoiceText*/);

/**
 * A structure to hold a runtime data object alongside its UClass type.
 * This ensures that even if the object instance is null, we still know its intended type.
 */
USTRUCT(BlueprintType)
struct FRuntimeDataInfo
{
	GENERATED_BODY()

	/** The runtime data object instance, which can be null. */
	UPROPERTY(BlueprintReadOnly, Category = "Inworld|Runtime Data",
		Meta = (ToolTip = "The runtime data object instance, which can be null"))
	TObjectPtr<UInworldGraphRuntimeData> DataObject;

	/** The UClass of the data object, this should always be valid. */
	UPROPERTY(BlueprintReadOnly, Category = "Inworld|Runtime Data",
		Meta = (ToolTip = "The UClass of the data object, this should always be valid"))
	TSubclassOf<UInworldGraphRuntimeData> DataType;
};

/**
 * Base abstract class for Inworld character components.
 *
 * This component serves as the foundation for AI-driven characters in the Inworld system.
 * It provides core functionality for character communication, audio playback, conversation
 * management, and integration with the Inworld graph system. Characters can receive and
 * respond to messages, manage conversation targets, and handle speech synthesis.
 *
 * Key Features:
 * - Conversation target implementation (IInworldConversationTarget)
 * - Audio and text message handling
 * - Speech synthesis and viseme blending
 * - Player target management
 * - Character graph integration
 * - Comprehensive event system for speech and conversation events
 *
 * This is an abstract base class that must be inherited to create concrete character implementations.
 */
UCLASS(ClassGroup = (Inworld), Abstract, BlueprintType, Blueprintable)
class INWORLDCHARACTER_API UInworldBaseCharacterComponent : public UInworldConversationTargetComponent
{
	GENERATED_BODY()

public:
	/**
	 * @brief Constructor for the base character component.
	 * @param ObjectInitializer Unreal Engine object initialization parameters
	 */
	UInworldBaseCharacterComponent(const FObjectInitializer& ObjectInitializer);

protected:
	/**
	 * Cancels the current message, clears the message queue, and stops the character's speech.
	 */
	virtual void Interrupt_Implementation() override;

	/**
	 * Sends a message to this character.
	 *
	 * 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 character 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 this character 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;

	/** Called when the component begins play */
	virtual void BeginPlay() override;

	/**
	 * Attempts to send the next message in the queue for processing.
	 * If the message queue is empty or there is already an active message being processed,
	 * the method exits without taking any action. Otherwise, it dequeues the next message,
	 * initiates its execution on the dialogue generation graph, and sets up callbacks
	 * for handling the result and monitoring execution status.
	 */
	virtual void TrySendNextMessage();

	/**
	 * Event triggered to handle a result from dialogue generation.
	 * @param ExecutionId The unique identifier for the execution of dialogue generation
	 * @param DataHandle The data handle containing information related to the generated dialogue
	 */
	UFUNCTION(BlueprintNativeEvent, Category = "Inworld")
	void OnDialogueGenerationResult(const FString& ExecutionId, const FString& NodeId, const FInworldDataHandle& DataHandle);
	/**
	 * Pure virtual function to handle the result of dialogue generation.
	 * This function must be implemented by derived classes.
	 *
	 * @param ExecutionId The unique identifier associated with the execution context
	 * @param DataHandle The data handle containing information about the dialogue generation result
	 */
	virtual void OnDialogueGenerationResult_Implementation(const FString& ExecutionId, const FString& NodeId,
		const FInworldDataHandle& DataHandle) PURE_VIRTUAL(UInworldGraphComponent::OnDialogueGenerationResult_Implementation);

	/**
	 * Event triggered to monitor the status of dialogue generation.
	 *
	 * @param ExecutionId The unique identifier associated with the execution context
	 * @param Status The current status of the dialogue generation process
	 */
	UFUNCTION(BlueprintNativeEvent, Category = "Inworld")
	void OnDialogueGenerationStatus(const FString& ExecutionId, EInworldGraphExecutionStatus Status);
	virtual void OnDialogueGenerationStatus_Implementation(const FString& ExecutionId, EInworldGraphExecutionStatus Status);

	/**
	 * Retrieves an map of runtime data objects associated with the character's behavior or state.
	 * This method is expected to be overridden by derived classes to return specific runtime data.
	 *
	 * @return A map of FInworldDataHandle representing the runtime data.
	 */
	virtual void GetRuntimeData(TMap<FName, FInworldDataHandle>& OutRuntimeData) const
	{
	}

	/**
	 * Attempts to signal that the character component is ready and initialized.
	 * If the component is already marked as ready, this method exits without further action.
	 * When readiness is achieved, it triggers the OnCharacterReady and OnCharacterReadyNative events.
	 */
	virtual void TrySignalReady() override;

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

	/**
	 * Gets all runtime data and its associated type for this character.
	 * @return Array of FRuntimeDataInfo structures.
	 */
	UFUNCTION(BlueprintPure, Category = "Inworld|Graph", meta = (DisplayName = "Get Runtime Data with Type"))
	virtual TArray<FRuntimeDataInfo> GetRuntimeDataAndType() const
	{
		return {};
	}

	/**
	 * A virtual function intended to be overridden by child classes to apply data from a DataTable.
	 * @param Data The character data to apply.
	 */
	virtual void ApplyCharacterData(const FInworldCharacterConfig& Data)
	{
	}

	/**
	 * Returns the specific type of this component (e.g., Simple or Full).
	 * Must be overridden by child classes.
	 * @return The component type enum.
	 */
	virtual EInworldComponentType GetComponentType() const
	{
		return EInworldComponentType::Full;
	}

	/**
	 * Attempts to dynamically update the character's configuration from a Data Table at runtime.
	 * @return Returns true if the character was successfully updated, false otherwise.
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld")
	bool UpdateCharacterFromDataTable(const TSoftObjectPtr<UDataTable>& NewCharacterDataTableRef, FName NewCharacterID);

#if WITH_EDITOR
	/** Called when a property on this object has been modified externally */
	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;

private:
	/** Handles validation logic when the CharacterID property is changed in the editor. */
	void ValidateCharacterIDProperty();
#endif

private:
	/** Initializes audio-related callback functions */
	void InitializeAudioCallbacks();

	/** Loads and applies data from the specified table. */
	void InitializeFromDataTable();

public:
	/** Event fired during character speech playback with viseme data */
	UPROPERTY(BlueprintAssignable, Category = "Inworld")
	FOnInworldCharacterUtterancePlayback OnUtterancePlayback;
	FOnInworldCharacterUtterancePlaybackNative OnUtterancePlaybackNative;

	/** Event fired when character starts speaking */
	UPROPERTY(BlueprintAssignable, Category = "Inworld")
	FOnInworldCharacterUtteranceStart OnUtteranceStart;
	FOnInworldCharacterUtteranceStartNative OnUtteranceStartNative;

	/** Event fired when character speech is interrupted */
	UPROPERTY(BlueprintAssignable, Category = "Inworld")
	FOnInworldCharacterUtteranceInterrupt OnUtteranceInterrupt;
	FOnInworldCharacterUtteranceInterruptNative OnUtteranceInterruptNative;

	/** Event fired when character completes speaking */
	UPROPERTY(BlueprintAssignable, Category = "Inworld")
	FOnInworldCharacterUtteranceComplete OnUtteranceComplete;
	FOnInworldCharacterUtteranceCompleteNative OnUtteranceCompleteNative;

	/** If true, the component will initialize its properties from the global DataTable using the Character ID. Otherwise, it will
	 * use the manually configured properties below. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld",
		Meta = (ToolTip =
					"If true, the component will initialize its properties from the global DataTable using the Character ID. Otherwise, it will use the manually configured properties below"))
	bool bUseDataTable = false;

	/** The Data Table containing character definitions for this component */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld",
		meta = (RequiredAssetDataTags = "RowStructure=/Script/InworldCharacter.InworldCharacterConfig",
			EditCondition = bUseDataTable, EditConditionHides,
			ToolTip = "The Data Table containing character definitions for this component"))
	TSoftObjectPtr<UDataTable> CharacterDataTable;

	/** The ID (Row Name) of the character to load from the CharacterDefinitionTable */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld",
		Meta = (ToolTip = "The ID (Row Name) of the character to load from the CharacterDefinitionTable"))
	FName CharacterID;

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

	/** Audio component for character voice playback */
	UPROPERTY(BlueprintReadOnly, Category = "Audio", Meta = (ToolTip = "Audio component for character voice playback"))
	TWeakObjectPtr<UInworldVoiceAudioComponent> InworldCharacterAudioComponent;

	/** Reference to the dialogue generation graph associated with the character, used to generate dialogue responses. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inworld|Graph",
		Meta = (ToolTip =
					"Reference to the dialogue generation graph associated with the character, used to generate dialogue responses"))
	TObjectPtr<UInworldGraphAsset> DialogueGenerationGraphAsset;
	/** Runtime instance of the dialogue generation graph for generating character responses */
	UPROPERTY(BlueprintReadOnly, Category = "Graph",
		Meta = (ToolTip = "Runtime instance of the dialogue generation graph for generating character responses"))
	TObjectPtr<UInworldGraph> DialogueGenerationGraph;

	/** Queue of pending messages waiting to be processed by the character */
	TQueue<FInworldCharacterMessage> MessageQueue;

	/** The message currently being processed by the character */
	FInworldCharacterMessage CurrentMessage;

private:
	/** Current viseme data for lip-sync animation */
	TArray<FInworldVisemeInfo> CurrentVisemeData;

	/** Allow conversation group access to private members */
	friend class UInworldConversationGroup;
};
