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

#pragma once

#include "InworldRuntimeTypes.h"
#include "Logging/LogMacros.h"
#include "Templates/PimplPtr.h"

namespace inworld
{
class LLMInterface;
}

#include "InworldLLMTypes.generated.h"

INWORLDRUNTIME_API DECLARE_LOG_CATEGORY_EXTERN(LogInworldLLM, Log, All);

/**
 * @struct FInworldLocalLLMCreationConfig
 * @brief Configuration for creating local LLM instances.
 *
 * This structure defines the configuration parameters required for creating
 * local Large Language Model instances, including compute host selection
 * and model specification.
 */
USTRUCT(BlueprintType)
struct FInworldLocalLLMCreationConfig
{
	GENERATED_BODY()

	/** The local compute device to use for LLM processing */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inworld|LLM",
		Meta = (ToolTip = "The local compute device to use for LLM processing"))
	EInworldLocalComputeHost LocalComputeHost{EInworldLocalComputeHost::CPU};

	/** The LLM model to use for text generation */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inworld|LLM",
		Meta = (GetOptions = "InworldRuntimeSettings.GetLocalLLMModels", NoResetToDefault,
			ToolTip = "The LLM model to use for text generation"))
	FString Model{"Meta-Llama-3.1-8B-Instruct-Q8_0.gguf"};
};

/**
 * @struct FInworldRemoteLLMCreationConfig
 * @brief Configuration for creating remote LLM instances.
 *
 * This structure defines the configuration parameters required for creating
 * remote Large Language Model instances, including provider and model selection.
 */
USTRUCT(BlueprintType)
struct FInworldRemoteLLMCreationConfig
{
	GENERATED_BODY()

	/** The remote LLM provider to use for text generation */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inworld|LLM",
		Meta = (GetOptions = "InworldRuntimeSettings.GetRemoteLLMProviders", NoResetToDefault,
			ToolTip = "The remote LLM provider to use for text generation"))
	FString Provider{"google"};

	/** The LLM model to use for text generation */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inworld|LLM",
		Meta = (GetOptions = "InworldRuntimeSettings.GetRemoteLLMModels", NoResetToDefault,
			ToolTip = "The LLM model to use for text generation"))
	FString Model{"gemini-2.5-flash-lite"};
};

/**
 * @struct FInworldLLMCreationConfig
 * @brief Main configuration structure for creating LLM instances.
 *
 * This structure defines whether to use local or remote LLM processing
 * and contains the appropriate sub-configuration for the selected compute host.
 */
USTRUCT(BlueprintType)
struct FInworldLLMCreationConfig
{
	GENERATED_BODY()

	/** Whether to use local or remote compute host for LLM processing */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inworld|LLM",
		Meta = (ToolTip = "Whether to use local or remote compute host for LLM processing"))
	EInworldComputeHost ComputeHost{EInworldComputeHost::REMOTE};

	/** Configuration for local LLM processing */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inworld|LLM",
		meta = (EditCondition = "ComputeHost == EInworldComputeHost::LOCAL", EditConditionHides,
			ToolTip = "Configuration for local LLM processing"))
	FInworldLocalLLMCreationConfig LocalCreationConfig{};

	/** Configuration for remote LLM processing */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inworld|LLM",
		meta = (EditCondition = "ComputeHost == EInworldComputeHost::REMOTE", EditConditionHides,
			ToolTip = "Configuration for remote LLM processing"))
	FInworldRemoteLLMCreationConfig RemoteCreationConfig{};
};

/**
 * @struct FInworldLLMTextGenerationConfig
 * @brief Configuration for LLM text generation parameters.
 *
 * This structure defines the parameters that control how the LLM generates text,
 * including token limits, randomness settings, penalties, and stop sequences.
 */
USTRUCT(BlueprintType, Blueprintable)
struct FInworldLLMTextGenerationConfig
{
	GENERATED_BODY()

	/** The maximum number of tokens to generate. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld|LLM", config,
		Meta = (ToolTip = "The maximum number of tokens to generate"))
	int MaxNewTokens = 160;

	/**   The maximum length of the prompt in tokens.
	 *     Different models may have different token limit. `max_prompt_length` must not be greater than the model's token limit. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld|LLM", config,
		Meta = (ToolTip =
					"The maximum length of the prompt in tokens. Different models may have different token limit. Must not be greater than the model's token limit"))
	int MaxPromptLength = 8000;

	/**   Allows to control the randomness of the generated text. ranges from 0 to 1. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld|LLM", config,
		meta = (ClampMin = "0.0", ClampMax = "1.0",
			ToolTip = "Allows to control the randomness of the generated text. Ranges from 0 to 1"))
	float Temperature = 0.5f;

	/**   The probability |top_p| for most probable tokens sampling. It is
	 *     another way to control the randomness of the generated text. ranges from 0 to 1. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld|LLM", config,
		meta = (ClampMin = "0.0", ClampMax = "1.0",
			ToolTip =
				"The probability for most probable tokens sampling. Another way to control the randomness of the generated text. Ranges from 0 to 1"))
	float TopP = 0.95f;

	/**   Repetition penalty lowers the probability scores of tokens that have already been generated, making it less likely for the
	 *     model to repeat them. ranges from [-2.0, 2.0]. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld|LLM", config,
		meta = (ClampMin = "-2.0", ClampMax = "2.0",
			ToolTip =
				"Repetition penalty lowers the probability scores of tokens that have already been generated, making it less likely for the model to repeat them. Ranges from -2.0 to 2.0"))
	float RepetitionPenalty = 1.f;

	/**   Penalize generated tokens based on their frequency in the previously generated text. Higher number means more unique
	 * tokens in generation. ranges from [-2.0, 2.0]. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld|LLM", config,
		meta = (ClampMin = "-2.0", ClampMax = "2.0",
			ToolTip =
				"Penalize generated tokens based on their frequency in the previously generated text. Higher number means more unique tokens in generation. Ranges from -2.0 to 2.0"))
	float FrequencyPenalty = 0.f;

	/**   Penalize generated tokens based on their presence in the previously generated text. Higher number means more unique tokens
	 * in generation. ranges from [-2.0, 2.0]. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld|LLM", config,
		meta = (ClampMin = "-2.0", ClampMax = "2.0",
			ToolTip =
				"Penalize generated tokens based on their presence in the previously generated text. Higher number means more unique tokens in generation. Ranges from -2.0 to 2.0"))
	float PresencePenalty = 0.f;

	/**   List of sequences to stop the generation. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld|LLM", config,
		Meta = (ToolTip = "List of sequences that stop the text generation when encountered"))
	TArray<FString> StopSequences{};

	/** Response replacement text on the event that an error occurs */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inworld|LLM", config,
		Meta = (ToolTip = "Response replacement text on the event that an error occurs"))
	FText ErrorText{};
};

/**
 * @struct FInworldLLMChatNodeExecutionConfig
 * @brief Runtime execution configuration for LLM chat nodes.
 *
 * This structure defines the runtime parameters for LLM chat node execution,
 * including the specific LLM to use, generation settings, and streaming options.
 */
USTRUCT(BlueprintType)
struct FInworldLLMChatNodeExecutionConfig
{
	GENERATED_BODY()

	/** The LLM model to use */
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Inworld|LLM",
		Meta = (GetOptions = "InworldRuntimeSettings.GetLLMIds", DisplayName = "LLM Model",
			ToolTip =
				"The LLM model to use for text generation. \n Not seeing the model you want? You can add addtional models in Project Settings > Inworld > Primitives > LLM Creation Config"))
	FString LLMComponentId{"Gemini 2.5 Flash Lite"};

	/** The text generation settings */
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Inworld|LLM",
		Meta = (ToolTip = "The text generation settings for this LLM"))
	FInworldLLMTextGenerationConfig TextGenerationConfig{};

	/** When true, will immediately output tokens as they are received. When false, will output after LLM Response is complete. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Inworld|LLM",
		Meta = (ToolTip =
					"When true, will immediately output tokens as they are received. When false, will output after LLM Response is complete"))
	bool bStream{true};
};

/**
 * @enum EInworldLLMMessageRole
 * @brief Enumeration for LLM chat message roles.
 */
UENUM(BlueprintType)
enum class EInworldLLMMessageRole : uint8
{
	/** Message from the user/human */
	USER UMETA(DisplayName = "User"),
	/** Message from the AI assistant */
	ASSISTANT UMETA(DisplayName = "Assistant"),
	/** Message from a tool or function call */
	TOOL UMETA(DisplayName = "Tool"),
	/** System instruction or prompt */
	SYSTEM UMETA(DisplayName = "System"),
	/** Developer/debug message */
	DEVELOPER UMETA(DisplayName = "Developer"),
};

/**
 * @enum EInworldLLMResponseFormat
 * @brief Enumeration for LLM response format types.
 */
UENUM(BlueprintType)
enum class EInworldLLMResponseFormat : uint8
{
	/** Plain text response */
	TEXT UMETA(DisplayName = "Text"),
	/** JSON formatted response */
	JSON UMETA(DisplayName = "Json"),
	/** JSON response with schema validation */
	JSONSCHEMA UMETA(DisplayName = "JsonSchema"),
};

/**
 * @struct FInworldLLMMessage
 * @brief Represents a single message in an LLM chat conversation.
 *
 * This structure encapsulates a chat message with its role and content,
 * used for building conversation contexts for LLM processing.
 */
USTRUCT(BlueprintType)
struct FInworldLLMMessage
{
	GENERATED_BODY()

	/** The role of the message sender in the conversation */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Inworld|LLM",
		Meta = (ToolTip = "The role of the message sender in the conversation"))
	EInworldLLMMessageRole Role{EInworldLLMMessageRole::USER};

	/** The text content of the message */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Inworld|LLM", Meta = (ToolTip = "The text content of the message"))
	FString Content{};
};

namespace inworld
{
class LocalLLMConfig;
/**
 * @brief Converts Unreal local LLM creation configuration to internal local LLM configuration.
 * @param In The Unreal local LLM creation configuration to convert
 * @param Out The internal local LLM configuration to populate
 */
void Convert(const FInworldLocalLLMCreationConfig& In, inworld::LocalLLMConfig& Out);

class RemoteLLMConfig;
/**
 * @brief Converts Unreal remote LLM creation configuration to internal remote LLM configuration.
 * @param In The Unreal remote LLM creation configuration to convert
 * @param Out The internal remote LLM configuration to populate
 */
void Convert(const FInworldRemoteLLMCreationConfig& In, inworld::RemoteLLMConfig& Out);

class TextGenerationConfig;
/**
 * @brief Converts Unreal LLM text generation configuration to internal text generation configuration.
 * @param In The Unreal LLM text generation configuration to convert
 * @param Out The internal text generation configuration to populate
 */
void Convert(const FInworldLLMTextGenerationConfig& In, inworld::TextGenerationConfig& Out);

class LLMChatNodeExecutionConfig;
/**
 * @brief Converts Unreal LLM chat node execution configuration to internal LLM chat node execution configuration.
 * @param In The Unreal LLM chat node execution configuration to convert
 * @param Out The internal LLM chat node execution configuration to populate
 */
void Convert(const FInworldLLMChatNodeExecutionConfig& In, inworld::LLMChatNodeExecutionConfig& Out);

class Messages;
/**
 * @brief Converts Unreal LLM message array to internal message collection.
 * @param In The Unreal LLM message array to convert
 * @param Out The internal message collection to populate
 */
void Convert(const TArray<FInworldLLMMessage>& In, inworld::Messages& Out);

}	 // namespace inworld
