// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — BPC_InventorySystem (31)
// Core inventory grid. Add/remove/sort/stack/weight management.
// In C++, TArray operations with lambdas are natively fast — no BP array node overhead.

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameplayTagContainer.h"
#include "BPC_InventorySystem.generated.h"

class UDA_ItemData;

/**
 * Single inventory slot entry.
 */
USTRUCT(BlueprintType)
struct PG_FRAMEWORK_API FInventorySlot
{
	GENERATED_BODY()

	/** The item in this slot. nullptr = empty slot. */
	UPROPERTY(BlueprintReadOnly)
	TObjectPtr<UDA_ItemData> Item = nullptr;

	/** How many of this item are stacked here. */
	UPROPERTY(BlueprintReadOnly)
	int32 Quantity = 0;

	/** Grid position for UI layout. */
	UPROPERTY(BlueprintReadOnly)
	int32 GridX = 0;

	/** Grid position for UI layout. */
	UPROPERTY(BlueprintReadOnly)
	int32 GridY = 0;

	bool IsEmpty() const { return Item == nullptr || Quantity <= 0; }

	void Clear()
	{
		Item = nullptr;
		Quantity = 0;
	}

	bool operator==(const FInventorySlot& Other) const
	{
		return Item == Other.Item && Quantity == Other.Quantity && GridX == Other.GridX && GridY == Other.GridY;
	}
};

// Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnInventoryChanged);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnItemAdded, UDA_ItemData*, Item, int32, Quantity);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnItemRemoved, UDA_ItemData*, Item, int32, Quantity);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnWeightChanged, float, CurrentWeight, float, MaxWeight);

/**
 * BPC_InventorySystem — Core Inventory Grid.
 *
 * Manages the player's carried items: add, remove, sort, stack, weight tracking.
 * C++ TArray operations (FindByPredicate, Sort, Filter) are natively compiled —
 * no BP interpretive array node overhead.
 */
UCLASS(Blueprintable, ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_InventorySystem : public UActorComponent
{
	GENERATED_BODY()

public:
	UBPC_InventorySystem();

	// ========================================================================
	// Configuration
	// ========================================================================

	/** Grid width (columns). */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Inventory|Config")
	int32 GridWidth = 8;

	/** Grid height (rows). */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Inventory|Config")
	int32 GridHeight = 5;

	/** Maximum carry weight. Items exceeding this cannot be picked up. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Inventory|Config")
	float MaxWeight = 50.0f;

	// ========================================================================
	// Inventory State
	// ========================================================================

	/** All inventory slots (GridWidth × GridHeight). */
	UPROPERTY(BlueprintReadOnly, Category = "Framework|Inventory")
	TArray<FInventorySlot> Slots;

	/** Current total weight carried. */
	UPROPERTY(BlueprintReadOnly, Category = "Framework|Inventory")
	float CurrentWeight = 0.0f;

	/** Whether the inventory has been modified since last save. */
	UPROPERTY(BlueprintReadOnly, Category = "Framework|Inventory")
	bool bDirty = false;

	// ========================================================================
	// Core Operations
	// ========================================================================

	/**
	 * Add an item to the inventory. Stacks if possible, finds empty slot otherwise.
	 * Returns the quantity actually added (may be less than requested if full).
	 */
	UFUNCTION(BlueprintCallable, Category = "Framework|Inventory")
	int32 AddItem(UDA_ItemData* Item, int32 Quantity = 1);

	/**
	 * Remove an item from the inventory.
	 * Returns the quantity actually removed.
	 */
	UFUNCTION(BlueprintCallable, Category = "Framework|Inventory")
	int32 RemoveItem(UDA_ItemData* Item, int32 Quantity = 1);

	/**
	 * Remove an item from a specific slot.
	 */
	UFUNCTION(BlueprintCallable, Category = "Framework|Inventory")
	int32 RemoveItemFromSlot(int32 SlotIndex, int32 Quantity = 1);

	/**
	 * Check if an item can be added (enough space and weight capacity).
	 */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
	bool CanAddItem(UDA_ItemData* Item, int32 Quantity = 1) const;

	// ========================================================================
	// Query
	// ========================================================================

	/** Returns the total quantity of an item across all stacks. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
	int32 GetItemCount(UDA_ItemData* Item) const;

	/** Returns whether the inventory contains at least this many of an item. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
	bool HasItem(UDA_ItemData* Item, int32 Quantity = 1) const;

	/** Finds the first slot containing the given item. Returns -1 if not found. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
	int32 FindItemSlot(UDA_ItemData* Item) const;

	/** Returns all unique items currently in the inventory. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
	TArray<UDA_ItemData*> GetAllItems() const;

	/** Returns the number of empty slots available. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
	int32 GetEmptySlotCount() const;

	/** Returns the number of free weight units remaining. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Inventory")
	float GetRemainingWeight() const;

	// ========================================================================
	// Organization
	// ========================================================================

	/** Sort inventory by ItemType, then by DisplayName. */
	UFUNCTION(BlueprintCallable, Category = "Framework|Inventory")
	void SortInventory();

	/** Auto-merge all partial stacks of the same item. */
	UFUNCTION(BlueprintCallable, Category = "Framework|Inventory")
	void ConsolidateStacks();

	// ========================================================================
	// Event Dispatchers
	// ========================================================================

	UPROPERTY(BlueprintAssignable, Category = "Framework|Inventory|Events")
	FOnInventoryChanged OnInventoryChanged;

	UPROPERTY(BlueprintAssignable, Category = "Framework|Inventory|Events")
	FOnItemAdded OnItemAdded;

	UPROPERTY(BlueprintAssignable, Category = "Framework|Inventory|Events")
	FOnItemRemoved OnItemRemoved;

	UPROPERTY(BlueprintAssignable, Category = "Framework|Inventory|Events")
	FOnWeightChanged OnWeightChanged;

	// ========================================================================
	// Overrides
	// ========================================================================

	virtual void BeginPlay() override;

protected:
	/** Recalculates total weight from all slots. */
	void RecalculateWeight();

	/** Finds an existing stack for an item (not at max stack limit). Returns -1 if none found. */
	int32 FindExistingStack(UDA_ItemData* Item) const;

	/** Finds the first empty slot. Returns -1 if inventory is full. */
	int32 FindEmptySlot() const;

	/** Marks inventory as modified and broadcasts change dispatchers. */
	void MarkDirty();
};
