// Copyright ExByte Studios. Item Interaction Ecosystem.

#include "PickupItemActor.h"
#include "ItemWorldLog.h"
#include "InteractableComponent.h"
#include "ItemDatabaseSubsystem.h"
#include "ItemDefinitionRow.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "Net/UnrealNetwork.h"

APickupItemActor::APickupItemActor()
{
	bReplicates = true;
	SetReplicateMovement(true);
	PrimaryActorTick.bCanEverTick = false;

	Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
	SetRootComponent(Root);

	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	Mesh->SetupAttachment(Root);
	Mesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	Mesh->SetCollisionResponseToAllChannels(ECR_Block);
	Mesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);

	Interactable = CreateDefaultSubobject<UInteractableComponent>(TEXT("Interactable"));
}

void APickupItemActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(APickupItemActor, ItemData);
	DOREPLIFETIME(APickupItemActor, bIsLocked);
	DOREPLIFETIME(APickupItemActor, CurrentHolder);
}

void APickupItemActor::BeginPlay()
{
	Super::BeginPlay();
	RefreshVisuals();
}

void APickupItemActor::InitializeFromItem(const FItemInstanceData& InItem)
{
	if (!HasAuthority())
	{
		return;
	}
	ItemData = InItem;
	ItemData.EnsureInstanceId();
	ItemData.LocationType = EItemLocationType::World;
	RefreshVisuals();
}

APickupItemActor* APickupItemActor::SpawnPickup(UObject* WorldContext, FName ItemId, int32 Quantity, const FTransform& Transform)
{
	UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(WorldContext);
	if (!DB || !DB->HasItem(ItemId))
	{
		UE_LOG(LogItemWorld, Warning, TEXT("SpawnPickup: unknown item '%s'."), *ItemId.ToString());
		return nullptr;
	}
	const FItemInstanceData Item = DB->MakeItemInstance(ItemId, Quantity);
	return SpawnPickupFromInstance(WorldContext, Item, Transform);
}

APickupItemActor* APickupItemActor::SpawnPickupFromInstance(UObject* WorldContext, const FItemInstanceData& Item, const FTransform& Transform)
{
	UWorld* World = GEngine ? GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull) : nullptr;
	if (!World)
	{
		return nullptr;
	}

	// Prefer the item's authored pickup actor class; fall back to the base class.
	TSubclassOf<AActor> SpawnClass = APickupItemActor::StaticClass();
	if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(WorldContext))
	{
		if (const FItemDefinitionRow* Def = DB->GetItemDefinition(Item.ItemId))
		{
			if (!Def->PickupActorClass.IsNull())
			{
				if (UClass* Loaded = Def->PickupActorClass.LoadSynchronous())
				{
					if (Loaded->IsChildOf(APickupItemActor::StaticClass()))
					{
						SpawnClass = Loaded;
					}
				}
			}
		}
	}

	FActorSpawnParameters Params;
	Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
	APickupItemActor* Pickup = World->SpawnActor<APickupItemActor>(SpawnClass, Transform, Params);
	if (Pickup)
	{
		Pickup->InitializeFromItem(Item);
	}
	return Pickup;
}

bool APickupItemActor::GetProvidedItem(FItemInstanceData& OutItem) const
{
	if (!ItemData.IsValid())
	{
		return false;
	}
	OutItem = ItemData;
	return true;
}

bool APickupItemActor::IsItemAvailable() const
{
	return !bIsLocked && ItemData.IsValid();
}

void APickupItemActor::NotifyItemTaken(AActor* Taker)
{
	if (!HasAuthority())
	{
		return;
	}
	UE_LOG(LogItemWorld, Verbose, TEXT("Pickup %s taken by %s; destroying."), *GetName(), *GetNameSafe(Taker));
	Destroy();
}

void APickupItemActor::UpdateProvidedItem(const FItemInstanceData& RemainingItem)
{
	if (!HasAuthority())
	{
		return;
	}
	if (RemainingItem.Quantity <= 0)
	{
		Destroy();
		return;
	}
	ItemData = RemainingItem;
	ItemData.LocationType = EItemLocationType::World;
	RefreshVisuals();
}

void APickupItemActor::SetCarried(AActor* Holder, bool bCarried)
{
	if (!HasAuthority())
	{
		return;
	}
	bIsLocked = bCarried;
	CurrentHolder = bCarried ? Holder : nullptr;
	if (Mesh)
	{
		Mesh->SetSimulatePhysics(false);
		Mesh->SetCollisionEnabled(bCarried ? ECollisionEnabled::NoCollision : ECollisionEnabled::QueryAndPhysics);
	}
}

void APickupItemActor::ReleaseToWorld(const FTransform& WorldTransform)
{
	if (!HasAuthority())
	{
		return;
	}
	DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
	SetActorTransform(WorldTransform);
	bIsLocked = false;
	CurrentHolder = nullptr;
	if (Mesh)
	{
		Mesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	}
	if (ItemData.IsValid())
	{
		ItemData.LocationType = EItemLocationType::World;
	}
}

void APickupItemActor::OnRep_ItemData()
{
	RefreshVisuals();
}

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

	if (Mesh && !Def->WorldMesh.IsNull())
	{
		if (UStaticMesh* LoadedMesh = Def->WorldMesh.LoadSynchronous())
		{
			Mesh->SetStaticMesh(LoadedMesh);
		}
	}
	if (Interactable && Interactable->DisplayName.IsEmpty())
	{
		Interactable->DisplayName = Def->DisplayName;
	}
}
