// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "Engine/Level.h"

#include "ComboActionDecoratorBase.generated.h"

DECLARE_LOG_CATEGORY_EXTERN(LogComboActionDecoratorBase, Log, All);

#define LOCTEXT_NAMESPACE "NodeDecoratorBase"


UENUM(BlueprintType)
enum class EComboActionDecoratorState : uint8
{
	Uninitialized,
	Initialized
};

/**
 * Combo Action Decorator
 *
 * Decorators are instanced and exist only as "triggers".
 * Could be used to start audio, play animation or do some logic behind the curtains, like triggering Cutscene etc.
 */
UCLASS(Abstract, Blueprintable, BlueprintType, EditInlineNew, ClassGroup=("Combo Input|Action"), AutoExpandCategories=("Combo Input, Action"))
class COMBOINPUT_API UComboActionDecoratorBase : public UObject
{
	GENERATED_BODY()

public:
	FORCEINLINE ULevel *GetLevel() const { return this->GetTypedOuter<ULevel>(); }

public:
	virtual UWorld *GetWorld() const override
	{
		if (this->OwningWorld)
		{
			return this->OwningWorld;
		}
		
		// CDO objects do not belong to a world
		// If the actors outer is destroyed or unreachable we are shutting down and the world should be nullptr
		if (!this->HasAnyFlags(EObjectFlags::RF_ClassDefaultObject) && ensureMsgf(this->GetOuter(), TEXT("Actor: %s has a null OuterPrivate in AActor::GetWorld()"), *this->GetFullName()) &&
			!this->GetOuter()->HasAnyFlags(EObjectFlags::RF_BeginDestroyed) && !this->GetOuter()->IsUnreachable())
		{
			if (ULevel *Level = this->GetLevel())
			{
				return Level->OwningWorld;
			}
		}

		return nullptr;
	}

	UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators")
		FString GetDecoratorDocumentationLink() const;
		virtual FString GetDecoratorDocumentationLink_Implementation() const { return TEXT("This is a place where a link to the documentation will go eventually."); }

public:

	/**
	 * Initializes the Decorator.
	 * In C++ saves the World for later use.
	 * In Blueprints should be used to cache values to avoid overhead in 'ExecuteDecorator'.
	 */
	UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators")
		void InitializeDecorator(UWorld *World);
		virtual void InitializeDecorator_Implementation(UWorld *World)
		{
			this->OwningWorld = World;
			if (World)
			{
				this->DecoratorState = EComboActionDecoratorState::Initialized;
			}
		}

	/**
	 * Cleans up the Decorator.
	 * In Blueprints should be used to reset cached values to avoid blocking garbage collector.
	 */
	UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators")
		void CleanupDecorator();
		virtual void CleanupDecorator_Implementation() { this->DecoratorState = EComboActionDecoratorState::Uninitialized; }

	/**
	 * Validates the Decorator.
	 * Called for each Node it is attached to.
	 * Works as safety measure to avoid calling broken Decorators with invalid references.
	 *
	 * False value stops Dialogue whatsoever.
	 * Validation is called before Context is initialized!
	 */
	UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators")
		bool ValidateDecorator(TArray<FText> &ValidationMessages);
		virtual bool ValidateDecorator_Implementation(TArray<FText> &ValidationMessages);
	
	/**
	 * Evaluates the Decorator.
	 * Called for each Node it is attached to.
	 * Could enhance Node's 'CanStartNode'. Example would be: BP_RequireItem decorator, which would return true if Player has specific Item in inventory. Otherwise it returns false and its Node is not available in Selection of Answers.
	 */
	UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators")
		bool EvaluateDecorator();
		virtual bool EvaluateDecorator_Implementation() { return this->OwningWorld != nullptr; }

	/**
	 * Executes the Decorator.
	 * Useful for triggering special events per Node, for instance, switching dialogue cameras.
	 */
	UFUNCTION(BlueprintNativeEvent, Category="Combo Input|Action|Decorators")
		void ExecuteDecorator();
		virtual bool ExecuteDecorator_Implementation() { return true; }

	/**
	 * Stores reference to World.
	 * World is needed to perform World affecting tasks.
	 */
	UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Decorators")
		void StoreWorldReference(UWorld *World) { this->OwningWorld = World; }

	/**
	 * Returns Owning World this Decorator belongs to.
	 *
	 * ❗ Should not return Null, but possibly can.
	 */
	UFUNCTION(BlueprintCallable, Category="Combo Input|Action|Decorators", meta=(CompactNodeTitle="World"))
		UWorld *GetOwningWorld() const { return this->OwningWorld; }

	/**
	 * Returns Owning Node of this Decorator.
	 *
	 * ❗ Might return Null if this Decorator is owned by Graph!
	 */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action|Decorators", meta=(CompactNodeTitle="OwningNode"))
		class UComboActionGraphNode *GetOwningNode() const { return this->GetTypedOuter<UComboActionGraphNode>(); }
	/**
	 * Returns Owning Graph of this Decorator.
	 *
	 * ❗ Might return Null if this Decorator is owned by Node!
	 */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action|Decorators", meta=(CompactNodeTitle="OwningGraph"))
		class UComboActionGraph *GetOwningGraph() const { return this->GetTypedOuter<UComboActionGraph>(); }
	/**
	 * Returns Owning Object of this Decorator.
	 */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category="Combo Input|Action|Decorators", meta=(CompactNodeTitle="Owner"))
		UObject *GetOwner() const { return this->GetOuter(); }

	FText GetDecoratorName() const
	{
	#if WITH_EDITORONLY_DATA
		return this->GetClass()->GetDisplayNameText();
	#else
		return FText::FromString(GetName());
	#endif
	}
	
private:

	UPROPERTY()	EComboActionDecoratorState DecoratorState = EComboActionDecoratorState::Uninitialized;

	UPROPERTY()	UWorld *OwningWorld = nullptr;
};


/**
 * Dialogue Decorator Structure.
 * Holds reference to its Instanced Decorator.
 */
USTRUCT(BlueprintType)
struct FComboActionDecorator
{
	GENERATED_BODY()

	FComboActionDecorator() : DecoratorType(nullptr) {};

public:

	void InitializeDecorator(UWorld *World) const
	{
		if (this->DecoratorType)
		{
			this->DecoratorType->InitializeDecorator(World);
		}
		else
		{
			UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[InitializeDecorator] DecoratorType is null (invalid)!"));
		}
	}

	bool ValidateDecorator(TArray<FText>& ValidationMessages) const
	{
		if (this->DecoratorType)
		{
			return this->DecoratorType->ValidateDecorator(ValidationMessages);
		}

		UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[EvaluateDecorator] DecoratorType is null (invalid)!"));
		return false;
	}

	void CleanupDecorator() const
	{
		if (this->DecoratorType)
		{
			this->DecoratorType->CleanupDecorator();
		}
		else
		{
			UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[CleanupDecorator] DecoratorType is null (invalid)!"));
		}
	}

	bool EvaluateDecorator() const
	{
		if (this->DecoratorType)
		{
			return this->DecoratorType->EvaluateDecorator();
		}

		UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[EvaluateDecorator] DecoratorType is null (invalid)!"));
		return false;
	}
	
	void ExecuteDecorator() const
	{
		if (this->DecoratorType)
		{
			this->DecoratorType->ExecuteDecorator();
		}
		else
		{
			UE_LOG(LogComboActionDecoratorBase, Error, TEXT("[ExecuteDecorator] DecoratorType is null (invalid)!"));
		}
	}

public:

	/**
	 * Decorators can help out with enhancing the Dialogue flow.
	 * Those Decorators are instanced and exist only as "triggers".
	 * Could be used to start audio, play animation or do some logic behind the curtains, like triggering Cutscene etc.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced, Category = "Combo Input|Action", meta=(NoResetToDefault, AllowAbstract = "false", BlueprintBaseOnly = "true"))
		UComboActionDecoratorBase *DecoratorType = nullptr;

	bool operator==(const FComboActionDecorator &Other) const { return this->DecoratorType == Other.DecoratorType; }
	
};

#undef LOCTEXT_NAMESPACE