// Copyright ExByte Studios. Item Interaction Ecosystem.

#include "EquipmentComponent.h"
#include "ItemWorldLog.h"
#include "ItemDatabaseSubsystem.h"
#include "ItemDefinitionRow.h"
#include "InventoryComponent.h"
#include "GameFramework/Character.h"
#include "Components/SkeletalMeshComponent.h"
#include "Net/UnrealNetwork.h"

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

void UEquipmentComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(UEquipmentComponent, Equipped);
}

void UEquipmentComponent::OnUnregister()
{
	if (HasAuthority())
	{
		for (const FEquippedItemEntry& Entry : Equipped)
		{
			if (Entry.EquippedActor)
			{
				Entry.EquippedActor->Destroy();
			}
		}
	}
	Super::OnUnregister();
}

bool UEquipmentComponent::HasAuthority() const
{
	return GetOwner() && GetOwner()->HasAuthority();
}

USceneComponent* UEquipmentComponent::GetAttachTarget() const
{
	if (const ACharacter* Char = Cast<ACharacter>(GetOwner()))
	{
		return Char->GetMesh();
	}
	return GetOwner() ? GetOwner()->GetRootComponent() : nullptr;
}

FName UEquipmentComponent::ResolveSocket(const FGameplayTag& SlotTag) const
{
	if (const FName* Found = SlotSockets.Find(SlotTag))
	{
		return *Found;
	}
	return DefaultEquipSocket;
}

bool UEquipmentComponent::IsSlotEquipped(FGameplayTag SlotTag) const
{
	return Equipped.ContainsByPredicate([&SlotTag](const FEquippedItemEntry& E) { return E.SlotTag == SlotTag; });
}

bool UEquipmentComponent::ServerEquip(FGuid InstanceId, FGameplayTag SlotTag)
{
	if (!HasAuthority())
	{
		return false;
	}

	UInventoryComponent* Inv = GetOwner()->FindComponentByClass<UInventoryComponent>();
	if (!Inv)
	{
		return false;
	}
	FItemInstanceData Item;
	if (!Inv->FindItem(InstanceId, Item))
	{
		return false;
	}

	const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
	const FItemDefinitionRow* Def = DB ? DB->GetItemDefinition(Item.ItemId) : nullptr;
	if (!Def || !Def->bCanEquip)
	{
		return false;
	}
	// Slot compatibility (if the item restricts equipment slots).
	if (!Def->AllowedEquipmentSlots.IsEmpty() && !Def->AllowedEquipmentSlots.HasTag(SlotTag))
	{
		return false;
	}

	// Free the slot first.
	ServerUnequip(SlotTag);

	FEquippedItemEntry Entry;
	Entry.SlotTag = SlotTag;
	Entry.InstanceId = InstanceId;
	Entry.ItemId = Item.ItemId;

	if (!Def->EquippedActorClass.IsNull())
	{
		if (UClass* ActorClass = Def->EquippedActorClass.LoadSynchronous())
		{
			FActorSpawnParameters Params;
			Params.Owner = GetOwner();
			Params.Instigator = Cast<APawn>(GetOwner());
			AActor* Spawned = GetWorld()->SpawnActor<AActor>(ActorClass, FTransform::Identity, Params);
			if (Spawned)
			{
				Spawned->AttachToComponent(GetAttachTarget(), FAttachmentTransformRules::SnapToTargetIncludingScale, ResolveSocket(SlotTag));
				Entry.EquippedActor = Spawned;
			}
		}
	}

	Equipped.Add(Entry);
	OnEquipmentChanged.Broadcast();
	UE_LOG(LogItemWorld, Log, TEXT("[Inventory] equipped %s in %s"), *Item.ItemId.ToString(), *SlotTag.ToString());
	return true;
}

bool UEquipmentComponent::ServerUnequip(FGameplayTag SlotTag)
{
	if (!HasAuthority())
	{
		return false;
	}
	const int32 Index = Equipped.IndexOfByPredicate([&SlotTag](const FEquippedItemEntry& E) { return E.SlotTag == SlotTag; });
	if (Index == INDEX_NONE)
	{
		return false;
	}
	if (Equipped[Index].EquippedActor)
	{
		Equipped[Index].EquippedActor->Destroy();
	}
	Equipped.RemoveAt(Index);
	OnEquipmentChanged.Broadcast();
	return true;
}

void UEquipmentComponent::OnRep_Equipped()
{
	OnEquipmentChanged.Broadcast();
}
