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

#pragma once

#include "Components/InworldConversationTargetComponent.h"
#include "Conversation/InworldConversationGroup.h"
#include "CoreMinimal.h"
#include "Subsystems/WorldSubsystem.h"

#include "InworldCharacterSubsystem.generated.h"

struct FInworldCharacterConfig;

class APlayerController;

/** Blueprint delegate called when the list of conversation targets changes */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(
	FOnInworldConversationTargetListUpdated, const TArray<UInworldConversationTargetComponent*>&, ConversationTargets);

/** Native delegate called when the list of conversation targets changes */
DECLARE_MULTICAST_DELEGATE_OneParam(
	FOnInworldConversationTargetListUpdatedNative, const TArray<UInworldConversationTargetComponent*>& /*ConversationTargets*/);

/**
 * @class UInworldCharacterSubsystem
 * @brief World subsystem for managing Inworld character interactions and conversations.
 *
 * This subsystem provides centralized management of Inworld conversation targets,
 * character registration, and spatial queries for finding conversation partners.
 * It tracks all active conversation targets in the world and provides utilities
 * for distance and angle-based character selection.
 */
UCLASS(BlueprintType)
class INWORLDCHARACTER_API UInworldCharacterSubsystem : public UWorldSubsystem
{
	GENERATED_BODY()

public:
	/**
	 * @brief Initializes the character subsystem.
	 *
	 * This function sets up delegate bindings and prepares the subsystem for
	 * managing conversation targets and character interactions.
	 *
	 * @param Collection The subsystem collection being initialized
	 */
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;

	/**
	 * @brief Deinitializes the character subsystem.
	 *
	 * This function cleans up delegate bindings and releases resources used
	 * by the subsystem.
	 */
	virtual void Deinitialize() override;

	/**
	 * @brief Finds the closest conversation target to a given transform.
	 *
	 * This function searches for the nearest conversation target within the specified
	 * distance and angle tolerance from the provided transform. Useful for finding
	 * characters to interact with from a specific world position and orientation.
	 *
	 * @param Transform The transform to search from (position and rotation)
	 * @param MaxDistance Maximum distance to search for targets (default: 1000 units)
	 * @param ToleranceAngle Maximum angle deviation in degrees (default: 360 = all directions)
	 * @return The closest conversation target, or nullptr if none found within constraints
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld|Character")
	UInworldConversationTargetComponent* GetClosestConversationTarget(
		const FTransform& Transform, float MaxDistance = 1000.f, float ToleranceAngle = 360.f) const;

	/**
	 * @brief Finds the closest conversation target to another conversation target.
	 *
	 * This function searches for the nearest conversation target to a specified
	 * character, useful for character-to-character interactions and group conversations.
	 *
	 * @param TargetCharacter The reference character to search from
	 * @param MaxDistance Maximum distance to search for targets (default: 1000 units)
	 * @param ToleranceAngle Maximum angle deviation in degrees (default: 360 = all directions)
	 * @return The closest conversation target, or nullptr if none found within constraints
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld|Character")
	UInworldConversationTargetComponent* GetClosestConversationTargetToConversationTarget(
		UInworldConversationTargetComponent* TargetCharacter, float MaxDistance = 1000.f, float ToleranceAngle = 360.f) const;

	/**
	 * @brief Finds the conversation target closest to the player's reticle/crosshair.
	 *
	 * This function performs a screen-space query to find the conversation target
	 * that the player is looking at, useful for aim-to-target interaction systems.
	 *
	 * @param PlayerController The player controller to use for reticle calculation
	 * @param MaxDistance Maximum distance to search for targets (default: 1000 units)
	 * @return The conversation target under the reticle, or nullptr if none found
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld|Character")
	UInworldConversationTargetComponent* GetClosestConversationTargetToReticle(
		APlayerController* PlayerController, float MaxDistance = 1000.f);

	/**
	 * @brief Retrieves a conversation target by its unique identifier.
	 *
	 * This function looks up a specific conversation target using its ID string,
	 * useful for direct character referencing and scripted interactions.
	 *
	 * @param Id The unique identifier of the conversation target to find
	 * @return The conversation target with the specified ID, or nullptr if not found
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld|Character")
	UInworldConversationTargetComponent* GetConversationTarget(const FString& Id);

	/** Blueprint delegate called when the conversation target list is updated */
	UPROPERTY(BlueprintAssignable, Category = "Inworld")
	FOnInworldConversationTargetListUpdated OnConversationTargetListUpdated;

	/** Native delegate called when the conversation target list is updated */
	FOnInworldConversationTargetListUpdatedNative OnConversationTargetListUpdatedNative;

	/**
	 * @brief Retrieves the complete map of all registered conversation targets.
	 *
	 * This function returns a map of all conversation targets currently registered
	 * in the subsystem, indexed by their unique identifier strings. Useful for
	 * enumerating all available conversation partners in the world.
	 *
	 * @return Reference to the map of conversation targets (ID -> Component)
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld|Character")
	const TMap<FString, UInworldConversationTargetComponent*>& GetConversationTargetMap() const;

	/**
	 * @brief Looks up character configuration data from a DataTable.
	 *
	 * This function searches a specified DataTable for character configuration
	 * data using the character ID as the row name. Useful for loading character
	 * profiles, relationships, and other configuration from data tables.
	 *
	 * @param DataTableToSearch The DataTable containing character configurations
	 * @param CharacterID The row name identifying the character data to retrieve
	 * @param OutData Output parameter to store the retrieved character configuration
	 * @return True if the character data was found, false otherwise
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld|Registry")
	bool GetCharacterData(UDataTable* DataTableToSearch, const FName& CharacterID, FInworldCharacterConfig& OutData) const;

protected:
	/**
	 * @brief Measures distance and angle between two transforms for targeting queries.
	 *
	 * This static helper function calculates whether two transforms meet the specified
	 * distance and angle constraints, used internally by conversation target queries.
	 *
	 * @param TransformA The first transform
	 * @param TransformB The second transform
	 * @param MaxDistance Maximum allowed distance (modified to actual distance on return)
	 * @param ToleranceAngle Maximum angle deviation in degrees
	 * @return True if the transforms meet both distance and angle constraints
	 */
	static bool MeasureDistanceAngle(
		const FTransform& TransformA, const FTransform& TransformB, float& MaxDistance, float ToleranceAngle);

	/**
	 * @brief Registers a new conversation target with the subsystem.
	 *
	 * This function adds a conversation target to the tracking system and broadcasts
	 * the updated target list to all listeners.
	 *
	 * @param ConversationTarget The conversation target component to register
	 */
	UFUNCTION()
	void AddConversationTarget(UInworldConversationTargetComponent* ConversationTarget);

	/**
	 * @brief Unregisters a conversation target from the subsystem.
	 *
	 * This function removes a conversation target from the tracking system and
	 * broadcasts the updated target list to all listeners.
	 *
	 * @param ConversationTarget The conversation target component to unregister
	 */
	UFUNCTION()
	void RemoveConversationTarget(UInworldConversationTargetComponent* ConversationTarget);

	/**
	 * @brief Handles a character being added to a conversation group.
	 *
	 * This callback function is invoked when a character joins a conversation,
	 * allowing the subsystem to track conversation state changes.
	 *
	 * @param ConversationGroup The conversation group the character joined
	 * @param InworldCharacter The character component that was added
	 */
	UFUNCTION()
	void OnCharacterAddedToConversation(UInworldConversationGroup* ConversationGroup, UInworldCharacterComponent* InworldCharacter);

	/**
	 * @brief Handles a character being removed from a conversation group.
	 *
	 * This callback function is invoked when a character leaves a conversation,
	 * allowing the subsystem to track conversation state changes.
	 *
	 * @param ConversationGroup The conversation group the character left
	 * @param InworldCharacter The character component that was removed
	 */
	UFUNCTION()
	void OnCharacterRemovedFromConversation(
		UInworldConversationGroup* ConversationGroup, UInworldCharacterComponent* InworldCharacter);

	/** Map of all registered conversation targets indexed by their unique identifier */
	UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
	TMap<FString, UInworldConversationTargetComponent*> ConversationTargetMap;

	/** Map of conversation group delegate handles for tracking conversation changes */
	TMap<FString, FDelegateHandle> ConversationGroupDelegateMap;

	/** Delegate handle for conversation target registration events */
	FDelegateHandle ConversationTargetRegisterDelegateHandle;

	/** Delegate handle for conversation target unregistration events */
	FDelegateHandle ConversationTargetUnregisterDelegateHandle;
};
