﻿// Licensed under the MIT License. See the LICENSE file in the project root for full license information.

#pragma once

#include "Components/ItemComponent.h"
#include "CoreMinimal.h"
#include "Definitions/ItemDefinition.h"
#include "UObject/Object.h"

#include "ItemInstance.generated.h"

struct FInventoryList;

/**
 * @class UItemInstance
 * @see UObject
 * @brief This class represents an instance of an item within the inventory system. It holds a reference
 * to the item definition and provides functionalities related to item management.
 * This class uses a component-based architecture to allow for flexible
 * and efficient item instances. Each instance can have multiple components
 * that add specific functionalities.
 */
UCLASS(BlueprintType, Blueprintable)
class INVENTORYSYSTEMCORE_API UItemInstance : public UObject, public IGameplayTagAssetInterface
{
	GENERATED_BODY()

	friend struct FInventoryList;

public:
	explicit UItemInstance(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

	// UObject
	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
	virtual bool IsSupportedForNetworking() const override { return true; }
	// ~UObject

	// IGameplayTagAssetInterface
	virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override;
	// ~IGameplayTagAssetInterface

	
	virtual void Initialize(UItemDefinition* InDefinition);
	virtual void Uninitialize();

	
	UFUNCTION(BlueprintCallable, Category="Tags")
	void AddTag(const FGameplayTag Tag) { Tags.AddTag(Tag); }

	UFUNCTION(BlueprintCallable, Category="Tags")
	void RemoveTag(const FGameplayTag Tag) { Tags.RemoveTag(Tag); }

	/**
	 * Gets the inventory system component that owns this item instance
	 * @return The owning inventory system component
	 */
	UFUNCTION(BlueprintCallable, Category = "Instance")
	UInventorySystemComponent* GetInventorySystemComponent() const;

	/**
	 * Gets the actor that owns this item instance
	 * @return The owning actor
	 */
	UFUNCTION(BlueprintCallable, Category = "Instance")
	AActor* GetOwningActor() const;
	/**
	 * Gets the actor cast to the specified type
	 * @return The actor cast to type T, or nullptr if cast fails
	 */
	template <typename T>
	const T* GetOwningActor() const { return Cast<T>(GetOwningActor(T::StaticClass())); }

	/**
	 * Gets the item definition class associated with this instance.
	 * @return The item definition class.
	 */
	UFUNCTION(BlueprintCallable)
	TSubclassOf<UItemDefinition> GetDefinitionClass() const;

	/**
	 * Gets the item definition associated with this instance, cached by the Inventory System Component.
	 * @return The item definition default object.
	 */
	UFUNCTION(BlueprintCallable)
	UItemDefinition* GetDefinition() const;

	/**
	 * Try to find fragment of class FragmentClass in this item's definition
	 * @param FragmentClass Class of the ItemFragment to search
	 */
	UFUNCTION(BlueprintCallable, BlueprintPure = false, meta = (DeterminesOutputType = FragmentClass))
	const UItemFragment* FindFragmentByClass(TSubclassOf<UItemFragment> FragmentClass) const;
	/**
	 * Try to find fragment of class FragmentClass in this item's definition
	 */
	template <typename T>
	const T* FindFragmentByClass() const { return Cast<T>(FindFragmentByClass(T::StaticClass())); }

	UFUNCTION(BlueprintCallable, BlueprintPure = false)
	bool HasFragmentByClass(TSubclassOf<UItemFragment> FragmentClass) const;

	/**
	 *	Try to find fragment of class FragmentClass in this item definition
	 *	@param ComponentClass Class of the ItemFragment to search
	 */
	UFUNCTION(BlueprintCallable, BlueprintPure = false, meta = (DeterminesOutputType = FragmentClass))
	UItemComponent* AddComponent(TSubclassOf<UItemComponent> ComponentClass);
	/**
	 *	Try to find fragment of class FragmentClass in this item definition
	 */
	template <typename T>
	T* AddComponent() { return Cast<T>(AddComponent(T::StaticClass())); }

	/**
	 *	Try to find component of class ComponentClass of this item instance
	 *	@param ComponentClass Class of the Item Instance's component to search
	 */
	UFUNCTION(BlueprintCallable, BlueprintPure = false, meta = (DeterminesOutputType = ComponentClass))
	const UItemComponent* FindComponentByClass(TSubclassOf<UItemComponent> ComponentClass) const;
	/**
	 * Try to find a component of type T attached to this item instance
	 * @return The found component cast to type T, or nullptr if not found or cast fails
	 */
	template <typename T>
	const T* FindComponentByClass() const { return Cast<T*>(FindComponentByClass(T::StaticClass())); }

protected:
	
	virtual void PostInitialize();

	
	/**
	 * Sets the item definition class for this instance.
	 * @param InDefinition - The item definition to set.
	 */
	void SetDefinition(UItemDefinition* InDefinition);

	/** The item definition that this instance is based on.
	 * Only replicate the class.
	 */
	UPROPERTY(Replicated, BlueprintReadOnly, Category="Tags")
	TSubclassOf<UItemDefinition> DefinitionClass;

	/** The Item Definition default object, cached by the local InventorySystemComponent cache */
	UPROPERTY(BlueprintReadOnly, Category="Tags")
	TWeakObjectPtr<UItemDefinition> Definition;

	/** Components attached to this item instance providing additional functionality */
	UPROPERTY(Replicated, BlueprintReadOnly, Category="Tags")
	TArray<UItemComponent*> Components;

	/** Tags used to classify or filter this item statically */
	UPROPERTY(BlueprintReadOnly, Category="Tags")
	FGameplayTagContainer Tags;

	/** Cached pointer to owning inventory system component pawn */
	UPROPERTY(Transient)
	mutable AActor* OwningActor;
};
