// Copyright ExByte Studios. Item Interaction Ecosystem.

#include "InventoryComponent.h"
#include "ItemInventoryLog.h"
#include "ItemDatabaseSubsystem.h"
#include "ItemDefinitionRow.h"
#include "Net/UnrealNetwork.h"
#include "Engine/ActorChannel.h"

UInventoryComponent::UInventoryComponent()
{
	PrimaryComponentTick.bCanEverTick = false;
	bWantsInitializeComponent = true;
	SetIsReplicatedByDefault(true);
}

void UInventoryComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	if (bOwnerOnlyReplication)
	{
		DOREPLIFETIME_CONDITION(UInventoryComponent, ReplicatedItems, COND_OwnerOnly);
		DOREPLIFETIME_CONDITION(UInventoryComponent, Containers, COND_OwnerOnly);
	}
	else
	{
		DOREPLIFETIME(UInventoryComponent, ReplicatedItems);
		DOREPLIFETIME(UInventoryComponent, Containers);
	}
}

void UInventoryComponent::OnRegister()
{
	Super::OnRegister();
	ReplicatedItems.OwnerComponent = this;
}

void UInventoryComponent::InitializeComponent()
{
	Super::InitializeComponent();
	ReplicatedItems.OwnerComponent = this;

	if (GetOwner() && GetOwner()->HasAuthority())
	{
		for (const FInventoryContainer& Template : DefaultContainers)
		{
			AddContainer(Template);
		}
	}
}

bool UInventoryComponent::HasAuthorityChecked(const TCHAR* Op) const
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		return true;
	}
	UE_LOG(LogItemInventory, Warning, TEXT("%s called without authority on %s - ignored."),
		Op, *GetNameSafe(GetOwner()));
	return false;
}

// --- Containers ---

FGuid UInventoryComponent::AddContainer(FInventoryContainer Container)
{
	if (!HasAuthorityChecked(TEXT("AddContainer")))
	{
		return FGuid();
	}
	if (!Container.ContainerId.IsValid())
	{
		Container.ContainerId = FGuid::NewGuid();
	}
	Containers.Add(Container);
	NotifyInventoryChanged();
	return Container.ContainerId;
}

FGuid UInventoryComponent::AddContainerByTag(FGameplayTag ContainerTag)
{
	if (!HasAuthorityChecked(TEXT("AddContainerByTag")))
	{
		return FGuid();
	}
	const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
	FContainerDefRow Def;
	if (!DB || !DB->FindContainerDef(ContainerTag, Def))
	{
		UE_LOG(LogItemInventory, Warning, TEXT("AddContainerByTag: no DT_Containers row for %s"), *ContainerTag.ToString());
		return FGuid();
	}
	FInventoryContainer Container;
	Container.ContainerTag = Def.ContainerTag;
	Container.Type = Def.Type;
	Container.Width = Def.Width;
	Container.Height = Def.Height;
	Container.MaxSlots = Def.MaxSlots;
	Container.MaxWeight = Def.MaxWeight;
	Container.AcceptedItemTags = Def.AcceptedItemTags;
	Container.BlockedItemTags = Def.BlockedItemTags;
	return AddContainer(Container);
}

FGuid UInventoryComponent::AddNestedContainerForItem(FGuid OwnerItemInstanceId, FGameplayTag ContainerTag)
{
	const FGuid NewId = AddContainerByTag(ContainerTag);
	if (NewId.IsValid())
	{
		if (FInventoryContainer* Container = GetContainerMutable(NewId))
		{
			Container->OwnerItemInstanceId = OwnerItemInstanceId;
			Container->Type = EInventoryContainerType::SlotList; // nested defaults
			NotifyInventoryChanged();
		}
	}
	return NewId;
}

bool UInventoryComponent::GetContainer(FGuid ContainerId, FInventoryContainer& OutContainer) const
{
	if (const FInventoryContainer* Found = GetContainerConst(ContainerId))
	{
		OutContainer = *Found;
		return true;
	}
	return false;
}

FGuid UInventoryComponent::FindContainerByTag(FGameplayTag ContainerTag) const
{
	for (const FInventoryContainer& C : Containers)
	{
		if (C.ContainerTag == ContainerTag)
		{
			return C.ContainerId;
		}
	}
	return FGuid();
}

FInventoryContainer* UInventoryComponent::GetContainerMutable(const FGuid& ContainerId)
{
	return Containers.FindByPredicate([&ContainerId](const FInventoryContainer& C) { return C.ContainerId == ContainerId; });
}

const FInventoryContainer* UInventoryComponent::GetContainerConst(const FGuid& ContainerId) const
{
	return Containers.FindByPredicate([&ContainerId](const FInventoryContainer& C) { return C.ContainerId == ContainerId; });
}

// --- Queries ---

bool UInventoryComponent::IsSlotOccupied(const FGuid& ContainerId, int32 SlotIndex) const
{
	for (const FRepInventoryEntry& E : ReplicatedItems.Entries)
	{
		if (E.ContainerId == ContainerId && E.SlotIndex == SlotIndex)
		{
			return true;
		}
	}
	return false;
}

int32 UInventoryComponent::FindFreeSlot(const FGuid& ContainerId) const
{
	const FInventoryContainer* Container = GetContainerConst(ContainerId);
	if (!Container)
	{
		return INDEX_NONE;
	}

	const int32 SlotCount = Container->GetSlotCount();
	if (SlotCount > 0)
	{
		for (int32 Slot = 0; Slot < SlotCount; ++Slot)
		{
			if (!IsSlotOccupied(ContainerId, Slot))
			{
				return Slot;
			}
		}
		return INDEX_NONE; // full
	}

	// Unlimited (weight-gated) list: append after the highest used index.
	int32 MaxUsed = INDEX_NONE;
	for (const FRepInventoryEntry& E : ReplicatedItems.Entries)
	{
		if (E.ContainerId == ContainerId)
		{
			MaxUsed = FMath::Max(MaxUsed, E.SlotIndex);
		}
	}
	return MaxUsed + 1;
}

FIntPoint UInventoryComponent::GetItemGridSize(FName ItemId) const
{
	if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this))
	{
		if (const FItemDefinitionRow* Def = DB->GetItemDefinition(ItemId))
		{
			return FIntPoint(FMath::Max(1, Def->GridSize.X), FMath::Max(1, Def->GridSize.Y));
		}
	}
	return FIntPoint(1, 1);
}

bool UInventoryComponent::IsGridRegionFree(const FInventoryContainer& Container, int32 TopLeft, FIntPoint Size, const FGuid& IgnoreInstance) const
{
	const int32 W = FMath::Max(1, Container.Width);
	const int32 H = FMath::Max(1, Container.Height);
	const int32 X0 = TopLeft % W;
	const int32 Y0 = TopLeft / W;
	if (X0 + Size.X > W || Y0 + Size.Y > H)
	{
		return false; // would overflow the grid
	}

	// Build an occupancy mask from existing entries' footprints.
	TArray<bool> Occupied;
	Occupied.Init(false, W * H);
	for (const FRepInventoryEntry& E : ReplicatedItems.Entries)
	{
		if (E.ContainerId != Container.ContainerId || E.Item.InstanceId == IgnoreInstance || E.SlotIndex < 0)
		{
			continue;
		}
		const FIntPoint ES = GetItemGridSize(E.Item.ItemId);
		const int32 EX = E.SlotIndex % W;
		const int32 EY = E.SlotIndex / W;
		for (int32 dy = 0; dy < ES.Y; ++dy)
		{
			for (int32 dx = 0; dx < ES.X; ++dx)
			{
				const int32 CellX = EX + dx;
				const int32 CellY = EY + dy;
				if (CellX < W && CellY < H) { Occupied[CellY * W + CellX] = true; }
			}
		}
	}

	for (int32 dy = 0; dy < Size.Y; ++dy)
	{
		for (int32 dx = 0; dx < Size.X; ++dx)
		{
			if (Occupied[(Y0 + dy) * W + (X0 + dx)]) { return false; }
		}
	}
	return true;
}

int32 UInventoryComponent::FindFreeSlotForItem(const FGuid& ContainerId, const FItemInstanceData& Item) const
{
	const FInventoryContainer* Container = GetContainerConst(ContainerId);
	if (!Container)
	{
		return INDEX_NONE;
	}
	if (Container->Type != EInventoryContainerType::Grid)
	{
		return FindFreeSlot(ContainerId);
	}

	const int32 W = FMath::Max(1, Container->Width);
	const int32 H = FMath::Max(1, Container->Height);
	const FIntPoint Size = GetItemGridSize(Item.ItemId);
	for (int32 Cell = 0; Cell < W * H; ++Cell)
	{
		if (IsGridRegionFree(*Container, Cell, Size, FGuid()))
		{
			return Cell;
		}
	}
	return INDEX_NONE;
}

float UInventoryComponent::GetContainerWeight(FGuid ContainerId) const
{
	float Total = 0.f;
	const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
	for (const FRepInventoryEntry& E : ReplicatedItems.Entries)
	{
		if (E.ContainerId != ContainerId)
		{
			continue;
		}
		if (DB)
		{
			if (const FItemDefinitionRow* Def = DB->GetItemDefinition(E.Item.ItemId))
			{
				Total += Def->Weight * E.Item.Quantity;
			}
		}
	}
	return Total;
}

int32 UInventoryComponent::GetItemCount(FName ItemId) const
{
	int32 Count = 0;
	for (const FRepInventoryEntry& E : ReplicatedItems.Entries)
	{
		if (E.Item.ItemId == ItemId)
		{
			Count += E.Item.Quantity;
		}
	}
	return Count;
}

bool UInventoryComponent::FindItem(FGuid InstanceId, FItemInstanceData& OutItem) const
{
	if (const FRepInventoryEntry* Entry = ReplicatedItems.FindByInstanceId(InstanceId))
	{
		OutItem = Entry->Item;
		return true;
	}
	return false;
}

void UInventoryComponent::GetContainerItems(FGuid ContainerId, TArray<FItemInstanceData>& OutItems) const
{
	for (const FRepInventoryEntry& E : ReplicatedItems.Entries)
	{
		if (E.ContainerId == ContainerId)
		{
			OutItems.Add(E.Item);
		}
	}
}

void UInventoryComponent::GetAllItems(TArray<FItemInstanceData>& OutItems) const
{
	OutItems.Reserve(ReplicatedItems.Entries.Num());
	for (const FRepInventoryEntry& E : ReplicatedItems.Entries)
	{
		OutItems.Add(E.Item);
	}
}

bool UInventoryComponent::CanAcceptItem(const FItemInstanceData& Item, FGuid ContainerId) const
{
	const FInventoryContainer* Container = GetContainerConst(ContainerId);
	if (!Container || !Item.IsValid())
	{
		return false;
	}

	const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
	const FItemDefinitionRow* Def = DB ? DB->GetItemDefinition(Item.ItemId) : nullptr;
	if (!Def)
	{
		return false;
	}

	// Capability: storage-style containers respect bCanStoreInInventory.
	const bool bStorageStyle =
		Container->Type == EInventoryContainerType::SlotList ||
		Container->Type == EInventoryContainerType::Grid ||
		Container->Type == EInventoryContainerType::Hotbar;
	if (bStorageStyle && !Def->bCanStoreInInventory)
	{
		return false;
	}

	// Tag gating.
	FGameplayTagContainer ItemTags = Def->ItemTags;
	ItemTags.AddTag(Def->ItemType);
	if (!Container->AcceptedItemTags.IsEmpty() && !ItemTags.HasAny(Container->AcceptedItemTags))
	{
		return false;
	}
	if (!Container->BlockedItemTags.IsEmpty() && ItemTags.HasAny(Container->BlockedItemTags))
	{
		return false;
	}

	// Weight.
	if (Container->HasWeightLimit())
	{
		const float Projected = GetContainerWeight(ContainerId) + Def->Weight * Item.Quantity;
		if (Projected > Container->MaxWeight)
		{
			return false;
		}
	}

	// Space: a free slot, or an existing stack of the same item with headroom.
	if (Container->HasSlotLimit())
	{
		if (FindFreeSlotForItem(ContainerId, Item) != INDEX_NONE)
		{
			return true;
		}
		const int32 MaxStack = FMath::Max(1, Def->MaxStack);
		for (const FRepInventoryEntry& E : ReplicatedItems.Entries)
		{
			if (E.ContainerId == ContainerId && E.Item.CanStackWith(Item) && E.Item.Quantity < MaxStack)
			{
				return true;
			}
		}
		return false;
	}

	return true;
}

// --- Mutations ---

void UInventoryComponent::StackIntoExisting(const FGuid& ContainerId, FItemInstanceData& Item, int32& Remaining)
{
	const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
	const int32 MaxStack = DB ? DB->GetMaxStack(Item.ItemId) : 1;
	if (MaxStack <= 1)
	{
		return;
	}

	for (FRepInventoryEntry& E : ReplicatedItems.Entries)
	{
		if (Remaining <= 0)
		{
			break;
		}
		if (E.ContainerId == ContainerId && E.Item.CanStackWith(Item) && E.Item.Quantity < MaxStack)
		{
			const int32 Space = MaxStack - E.Item.Quantity;
			const int32 Moved = FMath::Min(Space, Remaining);
			E.Item.Quantity += Moved;
			Remaining -= Moved;
			ReplicatedItems.MarkItemDirty(E);
		}
	}
}

bool UInventoryComponent::AddItem(FItemInstanceData& Item)
{
	if (!HasAuthorityChecked(TEXT("AddItem")))
	{
		return false;
	}
	for (const FInventoryContainer& C : Containers)
	{
		if (CanAcceptItem(Item, C.ContainerId))
		{
			return AddItemToContainer(Item, C.ContainerId);
		}
	}
	UE_LOG(LogItemInventory, Verbose, TEXT("AddItem: no container accepts %s x%d"), *Item.ItemId.ToString(), Item.Quantity);
	return false;
}

void UInventoryComponent::StripServerOnlyProperties(FItemInstanceData& Item)
{
	const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
	if (!DB)
	{
		return;
	}
	TArray<FGameplayTag> ServerOnly;
	DB->GetServerOnlyProperties(Item.ItemId, ServerOnly);
	if (ServerOnly.Num() == 0)
	{
		return;
	}
	FItemRuntimeProperties& Side = ServerOnlyProps.FindOrAdd(Item.InstanceId);
	for (const FGameplayTag& Tag : ServerOnly)
	{
		if (Item.RuntimeProperties.HasFloat(Tag))
		{
			Side.SetFloat(Tag, Item.RuntimeProperties.GetFloat(Tag));
			Item.RuntimeProperties.RemoveFloat(Tag); // never replicated to clients
		}
	}
}

float UInventoryComponent::GetServerOnlyFloat(FGuid InstanceId, FGameplayTag PropertyTag, float Fallback) const
{
	if (const FItemRuntimeProperties* Side = ServerOnlyProps.Find(InstanceId))
	{
		return Side->GetFloat(PropertyTag, Fallback);
	}
	return Fallback;
}

bool UInventoryComponent::AddItemToContainer(FItemInstanceData& Item, FGuid ContainerId)
{
	if (!HasAuthorityChecked(TEXT("AddItemToContainer")))
	{
		return false;
	}
	const FInventoryContainer* Container = GetContainerConst(ContainerId);
	if (!Container || !Item.IsValid())
	{
		return false;
	}

	Item.EnsureInstanceId();
	StripServerOnlyProperties(Item);

	const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
	const int32 MaxStack = DB ? DB->GetMaxStack(Item.ItemId) : 1;

	int32 Remaining = Item.Quantity;

	// 1) Top up compatible existing stacks.
	StackIntoExisting(ContainerId, Item, Remaining);

	// 2) Place leftovers into fresh slots, one stack at a time.
	bool bFirstNewStack = true;
	while (Remaining > 0)
	{
		const int32 Slot = FindFreeSlotForItem(ContainerId, Item);
		if (Slot == INDEX_NONE)
		{
			break; // container full
		}

		const int32 ThisStack = FMath::Min(MaxStack, Remaining);
		FItemInstanceData NewStack = Item;
		NewStack.Quantity = ThisStack;
		NewStack.LocationType = EItemLocationType::Inventory;
		// First fresh stack keeps the incoming instance id; subsequent stacks get new ids.
		NewStack.InstanceId = bFirstNewStack ? Item.InstanceId : FGuid::NewGuid();
		bFirstNewStack = false;

		FRepInventoryEntry& Entry = ReplicatedItems.AddOrUpdate(ContainerId, Slot, NewStack);
		NotifyItemAdded(Entry.Item);

		Remaining -= ThisStack;
	}

	const int32 AddedCount = Item.Quantity - Remaining;
	Item.Quantity = Remaining; // report leftovers back to caller
	if (AddedCount > 0)
	{
		NotifyInventoryChanged();
	}
	return Remaining == 0;
}

bool UInventoryComponent::RemoveItem(FGuid InstanceId)
{
	if (!HasAuthorityChecked(TEXT("RemoveItem")))
	{
		return false;
	}
	FItemInstanceData Removed;
	const bool bFound = FindItem(InstanceId, Removed);
	if (bFound && Removed.Lock.bLocked)
	{
		UE_LOG(LogItemInventory, Verbose, TEXT("RemoveItem blocked: %s is locked."), *InstanceId.ToString());
		return false;
	}
	if (ReplicatedItems.RemoveByInstanceId(InstanceId))
	{
		NotifyItemRemoved(Removed);
		NotifyInventoryChanged();
		return true;
	}
	return false;
}

bool UInventoryComponent::RemoveQuantity(FGuid InstanceId, int32 Amount)
{
	if (!HasAuthorityChecked(TEXT("RemoveQuantity")) || Amount <= 0)
	{
		return false;
	}
	FRepInventoryEntry* Entry = ReplicatedItems.FindByInstanceId(InstanceId);
	if (!Entry || Entry->Item.Lock.bLocked)
	{
		return false;
	}
	if (Amount >= Entry->Item.Quantity)
	{
		return RemoveItem(InstanceId);
	}
	Entry->Item.Quantity -= Amount;
	ReplicatedItems.MarkItemDirty(*Entry);
	NotifyInventoryChanged();
	return true;
}

bool UInventoryComponent::MoveItem(FGuid InstanceId, FGuid FromContainerId, FGuid ToContainerId, int32 TargetSlot)
{
	if (!HasAuthorityChecked(TEXT("MoveItem")))
	{
		return false;
	}
	FRepInventoryEntry* Entry = ReplicatedItems.FindByInstanceId(InstanceId);
	if (!Entry || Entry->ContainerId != FromContainerId || Entry->Item.Lock.bLocked)
	{
		return false;
	}
	if (!CanAcceptItem(Entry->Item, ToContainerId))
	{
		return false;
	}

	// Resolve target slot.
	if (TargetSlot == INDEX_NONE)
	{
		TargetSlot = FindFreeSlot(ToContainerId);
		if (TargetSlot == INDEX_NONE)
		{
			return false;
		}
	}

	// Occupant handling: merge if stackable, else swap.
	FRepInventoryEntry* Occupant = nullptr;
	for (FRepInventoryEntry& E : ReplicatedItems.Entries)
	{
		if (E.ContainerId == ToContainerId && E.SlotIndex == TargetSlot && E.Item.InstanceId != InstanceId)
		{
			Occupant = &E;
			break;
		}
	}

	if (Occupant)
	{
		if (Occupant->Item.CanStackWith(Entry->Item))
		{
			return MergeStack(InstanceId, Occupant->Item.InstanceId);
		}
		// Swap positions.
		const FGuid FromC = Entry->ContainerId;
		const int32 FromS = Entry->SlotIndex;
		Occupant->ContainerId = FromC;
		Occupant->SlotIndex = FromS;
		Occupant->Item.CurrentContainerId = FromC;
		Occupant->Item.SlotIndex = FromS;
		ReplicatedItems.MarkItemDirty(*Occupant);
	}

	Entry->ContainerId = ToContainerId;
	Entry->SlotIndex = TargetSlot;
	Entry->Item.CurrentContainerId = ToContainerId;
	Entry->Item.SlotIndex = TargetSlot;
	ReplicatedItems.MarkItemDirty(*Entry);
	NotifyInventoryChanged();
	return true;
}

bool UInventoryComponent::SplitStack(FGuid InstanceId, int32 Amount)
{
	if (!HasAuthorityChecked(TEXT("SplitStack")) || Amount <= 0)
	{
		return false;
	}
	FRepInventoryEntry* Entry = ReplicatedItems.FindByInstanceId(InstanceId);
	if (!Entry || Entry->Item.Lock.bLocked || Amount >= Entry->Item.Quantity)
	{
		return false;
	}

	const FGuid ContainerId = Entry->ContainerId;
	const int32 FreeSlot = FindFreeSlotForItem(ContainerId, Entry->Item);
	if (FreeSlot == INDEX_NONE)
	{
		return false;
	}

	Entry->Item.Quantity -= Amount;
	ReplicatedItems.MarkItemDirty(*Entry);

	FItemInstanceData NewStack = Entry->Item;
	NewStack.InstanceId = FGuid::NewGuid();
	NewStack.Quantity = Amount;
	FRepInventoryEntry& Added = ReplicatedItems.AddOrUpdate(ContainerId, FreeSlot, NewStack);
	NotifyItemAdded(Added.Item);
	NotifyInventoryChanged();
	return true;
}

bool UInventoryComponent::MergeStack(FGuid SourceInstanceId, FGuid TargetInstanceId)
{
	if (!HasAuthorityChecked(TEXT("MergeStack")) || SourceInstanceId == TargetInstanceId)
	{
		return false;
	}
	FRepInventoryEntry* Source = ReplicatedItems.FindByInstanceId(SourceInstanceId);
	FRepInventoryEntry* Target = ReplicatedItems.FindByInstanceId(TargetInstanceId);
	if (!Source || !Target || !Target->Item.CanStackWith(Source->Item))
	{
		return false;
	}

	const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
	const int32 MaxStack = DB ? DB->GetMaxStack(Target->Item.ItemId) : 1;
	const int32 Space = MaxStack - Target->Item.Quantity;
	if (Space <= 0)
	{
		return false;
	}

	const int32 Moved = FMath::Min(Space, Source->Item.Quantity);
	Target->Item.Quantity += Moved;
	Source->Item.Quantity -= Moved;
	ReplicatedItems.MarkItemDirty(*Target);

	if (Source->Item.Quantity <= 0)
	{
		const FItemInstanceData Removed = Source->Item;
		ReplicatedItems.RemoveByInstanceId(SourceInstanceId);
		NotifyItemRemoved(Removed);
	}
	else
	{
		ReplicatedItems.MarkItemDirty(*Source);
	}
	NotifyInventoryChanged();
	return true;
}

bool UInventoryComponent::UpdateItem(const FItemInstanceData& Item)
{
	if (!HasAuthorityChecked(TEXT("UpdateItem")))
	{
		return false;
	}
	FRepInventoryEntry* Entry = ReplicatedItems.FindByInstanceId(Item.InstanceId);
	if (!Entry)
	{
		return false;
	}
	FItemInstanceData Stored = Item;
	StripServerOnlyProperties(Stored);
	Entry->Item = Stored;
	Entry->Item.CurrentContainerId = Entry->ContainerId;
	Entry->Item.SlotIndex = Entry->SlotIndex;
	ReplicatedItems.MarkItemDirty(*Entry);
	NotifyInventoryChanged();
	return true;
}

bool UInventoryComponent::SetItemLock(FGuid InstanceId, FGameplayTag Reason, AActor* LockedBy, float ExpireWorldTime)
{
	if (!HasAuthorityChecked(TEXT("SetItemLock")))
	{
		return false;
	}
	FRepInventoryEntry* Entry = ReplicatedItems.FindByInstanceId(InstanceId);
	if (!Entry)
	{
		return false;
	}
	Entry->Item.Lock.bLocked = true;
	Entry->Item.Lock.LockReason = Reason;
	Entry->Item.Lock.LockedBy = LockedBy;
	Entry->Item.Lock.LockExpireTime = ExpireWorldTime;
	ReplicatedItems.MarkItemDirty(*Entry);
	NotifyInventoryChanged();
	return true;
}

bool UInventoryComponent::ClearItemLock(FGuid InstanceId)
{
	if (!HasAuthorityChecked(TEXT("ClearItemLock")))
	{
		return false;
	}
	FRepInventoryEntry* Entry = ReplicatedItems.FindByInstanceId(InstanceId);
	if (!Entry)
	{
		return false;
	}
	Entry->Item.Lock.Clear();
	ReplicatedItems.MarkItemDirty(*Entry);
	NotifyInventoryChanged();
	return true;
}

void UInventoryComponent::SetItemInSlot(const FItemInstanceData& Item, FGuid ContainerId, int32 SlotIndex)
{
	if (!HasAuthorityChecked(TEXT("SetItemInSlot")))
	{
		return;
	}
	FItemInstanceData Copy = Item;
	Copy.LocationType = EItemLocationType::Inventory;
	StripServerOnlyProperties(Copy);
	ReplicatedItems.AddOrUpdate(ContainerId, SlotIndex, Copy);
	NotifyInventoryChanged();
}

void UInventoryComponent::ClearAllItems()
{
	if (!HasAuthorityChecked(TEXT("ClearAllItems")))
	{
		return;
	}
	ReplicatedItems.Entries.Reset();
	ReplicatedItems.MarkArrayDirty();
	NotifyInventoryChanged();
}

void UInventoryComponent::SetContainers(const TArray<FInventoryContainer>& InContainers)
{
	if (!HasAuthorityChecked(TEXT("SetContainers")))
	{
		return;
	}
	Containers = InContainers;
	NotifyInventoryChanged();
}

// --- Notifications ---

void UInventoryComponent::NotifyInventoryChanged()
{
	OnInventoryChanged.Broadcast();
}

void UInventoryComponent::NotifyItemAdded(const FItemInstanceData& Item)
{
	OnItemAdded.Broadcast(Item);
}

void UInventoryComponent::NotifyItemRemoved(const FItemInstanceData& Item)
{
	OnItemRemoved.Broadcast(Item);
}
