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

#pragma once
#include "Audio/InworldAudioCapture.h"
#include "Containers/List.h"
#include "Conversation/InworldConversationTarget.h"

#include "InworldBasePlayerComponent.generated.h"

class UInworldBasePlayerComponent;
struct FInworldData_DataStream_AudioChunk;

// Forward declarations
struct FInworldData_Audio;
class UInworldAudioCapture;

/**
 * Delegate fired when the player component is ready and initialized.
 * Signals that the player is ready to interact with conversation targets.
 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnInworldPlayerReady, const UInworldBasePlayerComponent*, PlayerComponent);

/** Native delegate fired when player component is ready and initialized */
DECLARE_MULTICAST_DELEGATE_OneParam(FOnInworldPlayerReadyNative, const UInworldBasePlayerComponent* /*PlayerComponent*/);

/**
 * Delegate fired when audio is captured from the player.
 * Provides the player component, captured audio data, and Voice Activity Detection state.
 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnInworldPlayerAudioCapture, const UInworldBasePlayerComponent*, PlayerComponent,
	const FInworldData_Audio&, AudioData, bool, VAD);

/** Native delegate fired when audio is captured from the player */
DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnInworldPlayerAudioCaptureNative, const UInworldBasePlayerComponent* /*PlayerComponent*/,
	const FInworldData_Audio& /*AudioData*/, bool /*VAD*/);

/**
 * Delegate fired when a player sends a message.
 * Provides the player component, conversation target, and message data.
 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnInworldPlayerMessage, const UInworldBasePlayerComponent*, PlayerComponent,
	TScriptInterface<IInworldConversationTarget>, ConversationTarget, const FInworldDataHandle&, Message);

/** Native delegate fired when a player sends a message */
DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnInworldPlayerMessageNative, const UInworldBasePlayerComponent* /*PlayerComponent*/,
	TScriptInterface<IInworldConversationTarget> /*ConversationTarget*/, const FInworldDataHandle& /*Message*/);

/**
 * Delegate fired when the conversation target is updated.
 * Signals a change in which character or group the player is talking to.
 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInworldConversationTargetUpdate, const UInworldBasePlayerComponent*,
	PlayerComponent, const TScriptInterface<IInworldConversationTarget>&, ConversationTarget);

/** Native delegate fired when conversation target is updated */
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnInworldConversationTargetUpdateNative,
	const UInworldBasePlayerComponent* /*PlayerComponent*/,
	const TScriptInterface<IInworldConversationTarget>& /*ConversationTarget*/);

/**
 * Base abstract class for Inworld player components.
 *
 * This component provides core functionality for player interaction with Inworld characters,
 * including text messaging, audio capture, conversation management, and microphone control.
 * It serves as the foundation for more specialized player component implementations.
 *
 * Key Features:
 * - Text and audio message sending
 * - Conversation target management
 * - Microphone and voice activity detection
 * - Audio capture and processing
 * - Event delegation system
 */
UCLASS(ClassGroup = (Inworld), BlueprintType, Blueprintable, Abstract)
class INWORLDCHARACTER_API UInworldBasePlayerComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	/**
	 * Sends a message to the current conversation target.
	 * @param Message The message to send
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld")
	virtual void SendMessage(const FInworldDataHandle& Message);

	/**
	 * Sets the current conversation target for this player.
	 * @param NewConversationTarget The new conversation target to set
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld")
	virtual void SetConversationTarget(TScriptInterface<IInworldConversationTarget> NewConversationTarget);

	/**
	 * Gets the current conversation target.
	 * @return The current conversation target interface
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld")
	TScriptInterface<IInworldConversationTarget> GetConversationTarget();

	/**
	 * Removes the current conversation target.
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld")
	void RemoveConversationTarget();

	/**
	 * Enables or disables the microphone for audio capture.
	 * @param Enabled True to enable microphone, false to disable
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld")
	void SetMicrophone(bool Enabled);

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

	/**
	 * Called when audio capture is created and ready.
	 * @param AudioCapture The created audio capture instance
	 */
	UFUNCTION()
	virtual void OnAudioCaptureCreated(UInworldAudioCapture* AudioCapture);

	/**
	 * Called when audio is captured from the microphone.
	 * @param AudioCapture The audio capture instance
	 * @param AudioData The captured audio data
	 * @param VAD Voice Activity Detection state
	 */
	UFUNCTION()
	virtual void OnAudioCapture(const UInworldAudioCapture* AudioCapture, const FInworldData_Audio& AudioData, bool VAD);

	/**
	 * Evaluates whether the player is speaking based on if the microphone is enabled and the player has a valid conversation
	 * target. Manages enabling and disabling microphone audio capture.
	 */
	virtual void EvaluateTalking();

	/**
	 * Processes captured audio data.
	 * @param AudioData The audio data to process
	 * @param VAD Voice Activity Detection state
	 */
	UFUNCTION()
	virtual void ProcessAudioData(const FInworldData_Audio& AudioData, bool VAD);

	/** Called when the player has finished speaking */
	UFUNCTION()
	virtual void OnFinishedSpeaking();

	/**
	 * Attempts to signal that the player 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 OnPlayerReady and OnPlayerReadyNative events.
	 */
	virtual void TrySignalReady();

public:
	/** Configuration settings for audio capture */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audio", Meta = (ToolTip = "Configuration settings for audio capture"))
	FInworldAudioCaptureConfig AudioCaptureConfig;

	/** Time buffer for Voice Activity Detection in seconds */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audio",
		Meta = (ToolTip = "Time buffer for Voice Activity Detection in seconds"))
	float VADBufferTime{0.6f};

	/** Identifier for the Streaming STT Creation Config settings for the Streaming STT component used for converting speech to text
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audio",
		Meta = (GetOptions = "InworldRuntimeSettings.GetStreamingSTTIds",
			ToolTip =
				"Identifier for the Streaming STT Creation Config settings for the Streaming STT component used for converting speech to text"))
	FString StreamingSTTComponentId{"Default"};

	/** Whether to start with microphone enabled */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audio", Meta = (ToolTip = "Whether to start with microphone enabled"))
	bool bStartMicEnabled{true};

	/** Event fired when player component is ready */
	UPROPERTY(BlueprintAssignable, Category = "Inworld")
	FOnInworldPlayerReady OnPlayerReady;
	FOnInworldPlayerReadyNative OnPlayerReadyNative;

	/** Event fired when player sends a message */
	UPROPERTY(BlueprintAssignable, Category = "Inworld")
	FOnInworldPlayerMessage OnPlayerMessage;
	FOnInworldPlayerMessageNative OnPlayerMessageNative;

	/** Event fired when audio is captured from player */
	UPROPERTY(BlueprintAssignable, Category = "Inworld")
	FOnInworldPlayerAudioCapture OnPlayerAudioCapture;
	FOnInworldPlayerAudioCaptureNative OnPlayerAudioCaptureNative;

	/** Event fired when conversation target is updated */
	UPROPERTY(BlueprintAssignable, Category = "Inworld")
	FOnInworldConversationTargetUpdate OnConversationTargetUpdate;
	FOnInworldConversationTargetUpdateNative OnConversationTargetUpdateNative;

protected:
	/** Audio capture component for recording player voice */
	UPROPERTY(BlueprintReadOnly, Category = "Audio", Meta = (ToolTip = "Audio capture component for recording player voice"))
	TObjectPtr<UInworldAudioCapture> InworldAudioCapture;

	/** Audio buffer for managing captured audio data */
	UPROPERTY(BlueprintReadOnly, Category = "Audio", Meta = (ToolTip = "Indicates whether the microphone is currently enabled"))
	bool bMicEnabled{false};

	/** Indicates whether the player is currently speaking based on microphone activity and conversation status */
	UPROPERTY(BlueprintReadOnly, Category = "Audio",
		Meta =
			(ToolTip = "Indicates whether the player is currently speaking based on microphone activity and conversation status"))
	bool bTalking{false};

	/** Shared pointer managing audio data streamed during player interaction */
	TSharedPtr<FInworldData_DataStream_AudioChunk> InworldAudioStream;

	/** Current conversation target interface */
	UPROPERTY(EditInstanceOnly, BlueprintReadOnly, Category = "Inworld", Meta = (ToolTip = "Current conversation target interface"))
	TScriptInterface<IInworldConversationTarget> ConversationTarget;

	/** Holds the Speech-to-Text (STT) graph for managing audio transcription and processing in the Inworld system */
	UPROPERTY(BlueprintReadOnly, Category = "Inworld",
		Meta = (ToolTip =
					"Holds the Speech-to-Text (STT) graph for managing audio transcription and processing in the Inworld system"))
	TObjectPtr<UInworldGraph> STTGraph;

	/** Whether the player is ready and initialized */
	UPROPERTY(BlueprintReadOnly, Category = "Inworld", Meta = (ToolTip = "Whether the player is ready and initialized"))
	bool bIsReady;

	/** 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"};

private:
	/** Buffer of pre-VAD audio data to prepend to audio stream */
	TDoubleLinkedList<FInworldData_Audio> PrefixAudioDataFrames{};

	/** Total number of frames to allow VAD forgiveness (silence tolerance) */
	int32 NumVADForgivenessFrames{0};

	/** Current frame counter for VAD forgiveness tracking */
	int32 CurrentVADForgivenessFrame{0};

#if WITH_EDITOR && !UE_BUILD_SHIPPING
	bool bDebugDumpAudio{false};
	FString DebugDumpPlayerUtteranceAudioFile{};
#endif
};
