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

#pragma once

#include "Async/Async.h"
#include "Async/TaskGraphInterfaces.h"
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "Graph/InworldData.h"
#include "Graph/InworldGraph.h"
#include "HAL/CriticalSection.h"
#include "HAL/PlatformAtomics.h"
#include "HAL/ThreadSafeBool.h"
#include "Logging/MessageLog.h"
#include "Misc/ScopeRWLock.h"
#include "Misc/TVariant.h"
#include "Subsystems/EngineSubsystem.h"
#include "Tasks/Task.h"
#include "Templates/SharedPointer.h"
#include "Tickable.h"
#include "UObject/ObjectKey.h"

#include "InworldRuntimeSubsystem.generated.h"

namespace inworld
{
class BaseData;
class AECFilterInterface;
class VADInterface;
}	 // namespace inworld

class UInworldNode_Custom;
class UInworldEdge_WithCondition;
class UInworldEdge;
class UInworldGraphAsset;
class UInworldTextEmbedder;

/**
 * @brief The subsystem that manages instancing graphs and their execution.
 */
UCLASS()
class INWORLDRUNTIME_API UInworldRuntimeSubsystem : public UEngineSubsystem, public FTickableGameObject
{
	GENERATED_BODY()

public:
	//~UEngineSubsystem interface
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	virtual void Deinitialize() override;
	//~End of UEngineSubsystem interface

	// Begin FTickableGameObject
	virtual void Tick(float DeltaTime) override;
	virtual ETickableTickType GetTickableTickType() const override
	{
		return (IsTemplate() ? ETickableTickType::Never : ETickableTickType::Always);
	}
	// This is tickable in editor to capture any editor time graph executions
	virtual bool IsTickableInEditor() const override
	{
		return true;
	}
	virtual TStatId GetStatId() const override;
	// End FTickableGameObject

	/**
	 * @brief Gets the singleton instance of the runtime subsystem.
	 * @return Pointer to the runtime subsystem instance.
	 */
	static UInworldRuntimeSubsystem* Get();

	/**
	 * @brief Records user feedback with OpenTelemetry for analytics and improvement.
	 * @param Feedback The feedback data including graph ID, execution ID, rating, and description.
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld|Data")
	void SendGraphFeedback(const FInworldGraphFeedback& Feedback);

	/**
	 * @brief Clears the stored graph execution history.
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld|Data")
	void ClearGraphExecutionHistory();

	/**
	 * @brief Gets the Acoustic Echo Cancellation (AEC) filter interface.
	 * @return Reference to the AEC filter interface.
	 */
	inworld::AECFilterInterface& GetAEC();

	/**
	 * @brief Gets the Voice Activity Detection (VAD) interface.
	 * @return Reference to the VAD interface.
	 */
	inworld::VADInterface& GetVAD();

	/**
	 * @brief Registers a custom data converter (thread-safe).
	 * @param TypeId The type identifier for the converter.
	 * @param Converter The converter function to register.
	 */
	void SetCustomDataConverter(const FString& TypeId, const TFunction<FInworldDataHandle(void*)>& Converter);

	bool RegisterNodeType(const FString& Type);
	bool RegisterCustomEdgeConditionType(const FString& Type, UInworldEdge_WithCondition* Edge);
	bool RegisterCreationConfigType(const FString& Type);
	bool UnregisterComponent(const FString& Type);

	void RegisterProcessContext(const FString& ExecutionId, TWeakObjectPtr<class UInworldProcessContext> ProcessContext);
	void UnregisterProcessContext(const FString& ExecutionId);
	class UInworldProcessContext* GetProcessContext(const FString& ExecutionId) const;

	bool IsNodeValid(UInworldNode* Node) const;

	FInworldDataHandle Convert(const inworld::BaseData& Data);

#if WITH_EDITOR
	/**
	 * Gets a cached TextEmbedder instance for the given ConfigId, or creates a new one if it doesn't exist.
	 * @param ConfigId The identifier for the desired configuration from InworldRuntimeSettings.
	 * @return A pointer to the UInworldTextEmbedder instance, or nullptr if the config is invalid or creation fails.
	 */
	UInworldTextEmbedder* GetOrCreateTextEmbedder(const FString& ConfigId);
#endif

	/**
	 * @brief Retrieves all execution history entries for a specific owner object.
	 * @param HistoryOwner The owner object to filter history by.
	 * @param OwnerHistoryList Output array of history entries for the specified owner.
	 */
	void GetAllExecutionHistory(UObject* HistoryOwner, TArray<const FInworldGraphExecutionHistory*>& OwnerHistoryList) const;

	/**
	 * @brief Retrieves all unique owner objects from the execution history for debugging.
	 * @param OwnerList Output array of unique owner objects.
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld|Data")
	void GetAllUniqueOwner(TArray<UObject*>& OwnerList) const;

	/**
	 * @brief Gets the complete execution history list (primarily for editor utility widgets).
	 * @return Reference to the array of all graph execution history entries.
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld|Data")
	const TArray<FInworldGraphExecutionHistory>& GetHistoryList() const
	{
		return GraphHistoryList;
	}

	/**
	 * @brief Extracts and formats the output data from a node history entry.
	 * @param Node The node history entry to extract output from.
	 * @return Formatted string representation of the node's output.
	 */
	UFUNCTION(BlueprintCallable, Category = "Inworld|Data")
	FString GetNodeHistoryOuput(const FInworldNodeHistory& Node);

private:
	friend class UInworldGraph;
	friend class UInworldEdge;
	friend class UInworldNode;
	friend class UInworldGraphAsset;
	// friend class UInworldEdge_WithCondition;
	friend class inworld::NodeWrapper;

	static TAtomic<UInworldRuntimeSubsystem*> RuntimeSubsystemInstance;
	FThreadSafeBool bInitialized = false;

	// Adds a HistoryEntry. Called from UInworldGraph::Execute
	FInworldGraphExecutionHistory& AddGraphExecutionHistory();
	void AddExecutionHistoryError(const FString& ExecutionId, const FString& ErrorMessage);

	// Graph Global Execution Management
	bool CanStartExecution() const;
	void IncrementExecutionCount();
	void DecrementExecutionCount(int32 EndCount = 1);
	int32 GetCurrentExecutions() const;
	int32 GetMaxSimultaneousExecutions() const;
	void SetMaxSimultaneousExecutions(int32 MaxCount);

	mutable FCriticalSection ExecutionsLock;
	int32 MaxSimultaneousExecutions = 10;
	int32 CurrentExecutions = 0;

	// Nodes live mangement
	void RegisterNode(UInworldNode* Node);
	void UnregisterNode(UInworldNode* Node);

	mutable FRWLock NodeRegistryLock;
	TSet<TWeakObjectPtr<UInworldNode>> NodeRegistry;

	mutable FRWLock ProcessContextRegistryLock;
	TMap<FString, TWeakObjectPtr<class UInworldProcessContext>> ProcessContextRegistry;

	mutable FCriticalSection ComponentRegistryMutex;
	TMap<FString, int32> ComponentRefCounts;

	// Data converters registry
	void ClearDataConverters();

	mutable FRWLock ConverterRegistryLock;
	TMap<FString, TFunction<FInworldDataHandle(const inworld::BaseData&)>> IdentifiedDataAsFunctionsMap;
	TMap<FString, TFunction<FInworldDataHandle(void*)>> CustomDataAsFunctionsMap;

	// Graphs registry
	UInworldGraph* RegisterGraph(UInworldGraph* NativeGraph, UInworldGraphAsset* GraphAsset = nullptr);
	mutable FRWLock GraphRegistryLock;
	TMap<FString, TWeakObjectPtr<UInworldGraph>> GraphRegistry;

#if WITH_EDITORONLY_DATA
	UPROPERTY(Transient)
	TMap<FString, TObjectPtr<UInworldTextEmbedder>> TextEmbedderInstances;
#endif

	FDelegateHandle PIEStartedHandle;

	UPROPERTY(Transient)
	TArray<FInworldGraphExecutionHistory> GraphHistoryList;

	void UpdateGraphHistory();

	UFUNCTION()
	void OnPIEStarted(UGameInstance* InGameInstance);
};
