// Copyright ExByte Studios. Item Interaction Ecosystem.

#include "HeldItemComponent.h"
#include "PickupItemActor.h"
#include "ItemWorldLog.h"
#include "ItemNativeTags.h"
#include "ItemDatabaseSubsystem.h"
#include "ItemDefinitionRow.h"
#include "ItemAuxiliaryRows.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/PrimitiveComponent.h"
#include "PhysicsEngine/PhysicsHandleComponent.h"
#include "Net/UnrealNetwork.h"

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

void UHeldItemComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
	if (HeldItem.Mode == EHeldItemMode::CarryPhysicsHandle && HasAuthority())
	{
		UpdatePhysicsCarryTarget();
	}
}

bool UHeldItemComponent::ServerHoldWorldItemPhysics(AActor* WorldItem)
{
	if (!HasAuthority() || HeldItem.bHasHeldItem || !WorldItem)
	{
		return false;
	}
	IWorldItemProvider* Provider = Cast<IWorldItemProvider>(WorldItem);
	FItemInstanceData Item;
	if (!Provider || !Provider->IsItemAvailable() || !Provider->GetProvidedItem(Item) || !CanHoldItem(Item))
	{
		return false;
	}

	UPrimitiveComponent* Prim = nullptr;
	if (APickupItemActor* Pickup = Cast<APickupItemActor>(WorldItem))
	{
		Prim = Pickup->GetMeshComponent();
	}
	if (!Prim)
	{
		Prim = Cast<UPrimitiveComponent>(WorldItem->GetRootComponent());
	}
	if (!Prim)
	{
		return false;
	}

	// Claim the item but keep physics enabled (unlike attached carry).
	Provider->SetCarried(GetOwner(), true);
	Prim->SetSimulatePhysics(true);
	Prim->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);

	if (!PhysicsHandle)
	{
		PhysicsHandle = NewObject<UPhysicsHandleComponent>(this, TEXT("CarryPhysicsHandle"));
		PhysicsHandle->RegisterComponent();
	}
	PhysicsHandle->GrabComponentAtLocation(Prim, NAME_None, Prim->GetComponentLocation());

	HeldItem.bHasHeldItem = true;
	HeldItem.InstanceId = Item.InstanceId;
	HeldItem.ItemId = Item.ItemId;
	HeldItem.Mode = EHeldItemMode::CarryPhysicsHandle;
	HeldItem.HeldWorldActor = WorldItem;
	HeldItem.MoveSpeedMultiplier = 0.9f;
	HeldItem.Item = Item;
	HeldItem.Item.LocationType = EItemLocationType::Held;

	ApplyMovementState();
	SetComponentTickEnabled(true);
	OnHeldItemChanged.Broadcast(HeldItem);
	return true;
}

void UHeldItemComponent::UpdatePhysicsCarryTarget()
{
	if (!PhysicsHandle || !PhysicsHandle->GetGrabbedComponent() || !GetOwner())
	{
		return;
	}
	FRotator ViewRot = GetOwner()->GetActorRotation();
	if (const APawn* Pawn = Cast<APawn>(GetOwner()))
	{
		ViewRot = Pawn->GetControlRotation();
	}
	const FVector Target = GetOwner()->GetActorLocation() + ViewRot.Vector() * PhysicsHoldDistance + FVector(0, 0, 50.f);
	PhysicsHandle->SetTargetLocationAndRotation(Target, ViewRot);
}

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

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

bool UHeldItemComponent::CanHoldItem(const FItemInstanceData& Item) const
{
	if (HeldItem.bHasHeldItem || !Item.IsValid())
	{
		return false;
	}
	if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this))
	{
		if (const FItemDefinitionRow* Def = DB->GetItemDefinition(Item.ItemId))
		{
			return Def->bCanWorldCarry;
		}
	}
	return false;
}

bool UHeldItemComponent::ServerHoldWorldItem(AActor* WorldItem)
{
	if (!HasAuthority() || HeldItem.bHasHeldItem || !WorldItem)
	{
		return false;
	}
	IWorldItemProvider* Provider = Cast<IWorldItemProvider>(WorldItem);
	if (!Provider || !Provider->IsItemAvailable())
	{
		return false;
	}
	FItemInstanceData Item;
	if (!Provider->GetProvidedItem(Item) || !CanHoldItem(Item))
	{
		return false;
	}

	// Attach the world actor to the character's carry socket.
	USceneComponent* AttachParent = nullptr;
	if (const ACharacter* Char = Cast<ACharacter>(GetOwner()))
	{
		AttachParent = Char->GetMesh();
	}
	if (!AttachParent)
	{
		AttachParent = GetOwner()->GetRootComponent();
	}
	WorldItem->AttachToComponent(AttachParent, FAttachmentTransformRules::SnapToTargetIncludingScale, CarrySocket);
	Provider->SetCarried(GetOwner(), true);

	// Resolve carry move-speed multiplier from the feature table.
	float MoveMult = 1.f;
	if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this))
	{
		TArray<FItemFeatureRow> Features;
		DB->GetFeatureRows(Item.ItemId, Features);
		for (const FItemFeatureRow& Row : Features)
		{
			if (Row.Param == FName("MoveSpeedMultiplier"))
			{
				MoveMult = Row.GetFloat(1.f);
				break;
			}
		}
	}

	HeldItem.bHasHeldItem = true;
	HeldItem.InstanceId = Item.InstanceId;
	HeldItem.ItemId = Item.ItemId;
	HeldItem.Mode = EHeldItemMode::CarryAttached;
	HeldItem.HeldWorldActor = WorldItem;
	HeldItem.HoldSocket = CarrySocket;
	HeldItem.MoveSpeedMultiplier = MoveMult;
	HeldItem.bBlocksSprint = MoveMult < 0.9f;
	HeldItem.Item = Item;
	HeldItem.Item.LocationType = EItemLocationType::Held;

	ApplyMovementState();
	OnHeldItemChanged.Broadcast(HeldItem);
	UE_LOG(LogItemWorld, Log, TEXT("[Interaction] %s carried %s"), *GetNameSafe(GetOwner()), *Item.ItemId.ToString());
	return true;
}

void UHeldItemComponent::RequestDrop()
{
	Server_Drop();
}

void UHeldItemComponent::Server_Drop_Implementation()
{
	ServerDropHeldItem();
}

bool UHeldItemComponent::ServerDropHeldItem()
{
	if (!HasAuthority() || !HeldItem.bHasHeldItem)
	{
		return false;
	}

	const FVector Fwd = GetOwner()->GetActorForwardVector();
	const FTransform DropXf(GetOwner()->GetActorRotation(),
		GetOwner()->GetActorLocation() + Fwd * 100.f + FVector(0, 0, 20.f));

	if (APickupItemActor* Pickup = Cast<APickupItemActor>(HeldItem.HeldWorldActor))
	{
		Pickup->ReleaseToWorld(DropXf);
	}
	else
	{
		APickupItemActor::SpawnPickupFromInstance(this, HeldItem.Item, DropXf);
		if (HeldItem.HeldWorldActor)
		{
			HeldItem.HeldWorldActor->Destroy();
		}
	}

	// NoiseOnDrop feature: emit a hearing stimulus (section 21.6).
	if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this))
	{
		TArray<FItemFeatureRow> Noise;
		DB->GetFeatureRows(HeldItem.ItemId, ItemEcosystemTags::Feature_NoiseOnDrop, Noise);
		for (const FItemFeatureRow& Row : Noise)
		{
			if (Row.Param == FName("Radius"))
			{
				const float Radius = Row.GetFloat(0.f);
				if (Radius > 0.f)
				{
					GetOwner()->MakeNoise(1.f, Cast<APawn>(GetOwner()), DropXf.GetLocation(), Radius);
				}
				break;
			}
		}
	}

	if (PhysicsHandle && PhysicsHandle->GetGrabbedComponent()) { PhysicsHandle->ReleaseComponent(); }
	SetComponentTickEnabled(false);
	HeldItem = FHeldItemState();
	ApplyMovementState();
	OnHeldItemChanged.Broadcast(HeldItem);
	return true;
}

bool UHeldItemComponent::ServerConsumeHeldItem(FItemInstanceData& OutItem)
{
	if (!HasAuthority() || !HeldItem.bHasHeldItem)
	{
		return false;
	}
	OutItem = HeldItem.Item;
	if (HeldItem.HeldWorldActor)
	{
		HeldItem.HeldWorldActor->Destroy();
	}
	if (PhysicsHandle && PhysicsHandle->GetGrabbedComponent()) { PhysicsHandle->ReleaseComponent(); }
	SetComponentTickEnabled(false);
	HeldItem = FHeldItemState();
	ApplyMovementState();
	OnHeldItemChanged.Broadcast(HeldItem);
	return true;
}

bool UHeldItemComponent::ServerHandOffHeldItem(FItemInstanceData& OutItem, AActor*& OutActor)
{
	if (!HasAuthority() || !HeldItem.bHasHeldItem)
	{
		return false;
	}
	OutItem = HeldItem.Item;
	OutActor = HeldItem.HeldWorldActor;
	if (OutActor)
	{
		OutActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
	}
	if (PhysicsHandle && PhysicsHandle->GetGrabbedComponent()) { PhysicsHandle->ReleaseComponent(); }
	SetComponentTickEnabled(false);
	HeldItem = FHeldItemState();
	ApplyMovementState();
	OnHeldItemChanged.Broadcast(HeldItem);
	return true;
}

bool UHeldItemComponent::ServerUpdateHeldItem(const FItemInstanceData& NewItem)
{
	if (!HasAuthority() || !HeldItem.bHasHeldItem || NewItem.InstanceId != HeldItem.InstanceId)
	{
		return false;
	}
	HeldItem.Item = NewItem;
	HeldItem.Item.LocationType = EItemLocationType::Held;
	OnHeldItemChanged.Broadcast(HeldItem);
	return true;
}

bool UHeldItemComponent::GetHeldItem(FItemInstanceData& OutItem) const
{
	if (!HeldItem.bHasHeldItem)
	{
		return false;
	}
	OutItem = HeldItem.Item;
	return true;
}

void UHeldItemComponent::OnRep_HeldItem(FHeldItemState /*OldState*/)
{
	ApplyMovementState();
	OnHeldItemChanged.Broadcast(HeldItem);
}

void UHeldItemComponent::ApplyMovementState()
{
	ACharacter* Char = Cast<ACharacter>(GetOwner());
	UCharacterMovementComponent* Move = Char ? Char->GetCharacterMovement() : nullptr;
	if (!Move)
	{
		return;
	}

	// Cache the unmodified speed once, then re-derive from it each time.
	if (CachedBaseWalkSpeed <= 0.f)
	{
		CachedBaseWalkSpeed = Move->MaxWalkSpeed;
	}
	const float Mult = HeldItem.bHasHeldItem ? HeldItem.MoveSpeedMultiplier : 1.f;
	Move->MaxWalkSpeed = CachedBaseWalkSpeed * Mult;
}
