// Copyright ExByte Studios. Item Interaction Ecosystem.

#include "WorldInteractionComponent.h"
#include "HeldItemComponent.h"
#include "EquipmentComponent.h"
#include "WorldItemSlotComponent.h"
#include "PickupItemActor.h"
#include "ItemWorldLog.h"
#include "InventoryComponent.h"
#include "ItemDatabaseSubsystem.h"
#include "ItemDefinitionRow.h"
#include "ItemAuxiliaryRows.h"
#include "ItemInteractionInterfaces.h"
#include "GameFramework/Pawn.h"

bool UWorldInteractionComponent::ExecuteAuthorized(const FInteractionContext& Context, const FInteractionOption& Option, const FInteractionRequest& Request)
{
	switch (Option.Mode)
	{
	case EItemInteractionMode::CarryAttached:
		return ExecuteCarry(Context, /*bPhysics*/ false);

	case EItemInteractionMode::CarryPhysicsHandle:
		return ExecuteCarry(Context, /*bPhysics*/ true);

	case EItemInteractionMode::DropHeldItem:
		return ExecuteDropHeld(Context);

	case EItemInteractionMode::InstallIntoWorldSlot:
		return ExecuteInstall(Context);

	case EItemInteractionMode::RemoveFromWorldSlot:
		return ExecuteRemoveFromSlot(Context);

	case EItemInteractionMode::EquipFromInventory:
		return ExecuteEquip(Context, Request);

	case EItemInteractionMode::UseHeldOnTarget:
		return ExecuteUseHeldOnTarget(Context, Option);

	default:
		return Super::ExecuteAuthorized(Context, Option, Request);
	}
}

bool UWorldInteractionComponent::ExecuteCarry(const FInteractionContext& Context, bool bPhysics)
{
	UHeldItemComponent* Held = GetOwner()->FindComponentByClass<UHeldItemComponent>();
	if (!Held || !Context.TargetActor)
	{
		return false;
	}
	return bPhysics ? Held->ServerHoldWorldItemPhysics(Context.TargetActor)
	                : Held->ServerHoldWorldItem(Context.TargetActor);
}

bool UWorldInteractionComponent::ExecuteDropHeld(const FInteractionContext& Context)
{
	UHeldItemComponent* Held = GetOwner()->FindComponentByClass<UHeldItemComponent>();
	return Held && Held->ServerDropHeldItem();
}

bool UWorldInteractionComponent::ExecuteInstall(const FInteractionContext& Context)
{
	UHeldItemComponent* Held = GetOwner()->FindComponentByClass<UHeldItemComponent>();
	if (!Held || !Held->HasHeldItem() || !Context.TargetActor)
	{
		return false;
	}

	FItemInstanceData HeldItem;
	Held->GetHeldItem(HeldItem);

	// Find a compatible, free slot on the target.
	UWorldItemSlotComponent* Chosen = nullptr;
	TArray<UWorldItemSlotComponent*> Slots;
	Context.TargetActor->GetComponents<UWorldItemSlotComponent>(Slots);
	for (UWorldItemSlotComponent* Slot : Slots)
	{
		if (Slot && Slot->CanInstall(HeldItem))
		{
			Chosen = Slot;
			break;
		}
	}
	if (!Chosen)
	{
		UE_LOG(LogItemWorld, Warning, TEXT("[ServerReject] Install failed: no compatible free slot."));
		return false;
	}

	// Hand the carried actor over to the slot as its visual.
	FItemInstanceData Item;
	AActor* Visual = nullptr;
	if (!Held->ServerHandOffHeldItem(Item, Visual))
	{
		return false;
	}
	if (!Chosen->InstallItem(Item, Visual))
	{
		// Roll back: drop the item back to the world so it is not lost.
		const FTransform Xf(GetOwner()->GetActorRotation(), GetOwner()->GetActorLocation() + GetOwner()->GetActorForwardVector() * 100.f);
		if (APickupItemActor* Pickup = Cast<APickupItemActor>(Visual))
		{
			Pickup->ReleaseToWorld(Xf);
		}
		else
		{
			APickupItemActor::SpawnPickupFromInstance(this, Item, Xf);
			if (Visual) { Visual->Destroy(); }
		}
		return false;
	}
	return true;
}

bool UWorldInteractionComponent::ExecuteEquip(const FInteractionContext& Context, const FInteractionRequest& Request)
{
	UEquipmentComponent* Equip = GetOwner()->FindComponentByClass<UEquipmentComponent>();
	if (!Equip)
	{
		return false;
	}

	FGameplayTag SlotTag = Request.TargetSlotTag;
	if (!SlotTag.IsValid() && Context.PlayerInventory)
	{
		// Fall back to the item's first allowed equipment slot.
		FItemInstanceData Item;
		if (Context.PlayerInventory->FindItem(Request.ItemInstanceId, Item))
		{
			if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this))
			{
				if (const FItemDefinitionRow* Def = DB->GetItemDefinition(Item.ItemId))
				{
					if (!Def->AllowedEquipmentSlots.IsEmpty())
					{
						SlotTag = Def->AllowedEquipmentSlots.First();
					}
				}
			}
		}
	}
	return Equip->ServerEquip(Request.ItemInstanceId, SlotTag);
}

bool UWorldInteractionComponent::ExecuteUseHeldOnTarget(const FInteractionContext& Context, const FInteractionOption& Option)
{
	UHeldItemComponent* Held = GetOwner()->FindComponentByClass<UHeldItemComponent>();
	if (!Held || !Held->HasHeldItem() || !Context.TargetActor)
	{
		return false;
	}
	FItemInstanceData HeldItem;
	Held->GetHeldItem(HeldItem);

	// Find the result verb from the data-driven interaction row for this held item.
	FName ResultVerb = NAME_None;
	if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this))
	{
		TArray<FItemInteractionRow> Rows;
		DB->GetInteractionRows(HeldItem.ItemId, Rows);
		for (const FItemInteractionRow& Row : Rows)
		{
			if (Row.ActionTag == Option.ActionTag) { ResultVerb = Row.Result; break; }
		}
	}
	if (ResultVerb.IsNone())
	{
		return false;
	}

	// Locate an IItemUseTarget on the target actor or its components.
	IItemUseTarget* UseTarget = Cast<IItemUseTarget>(Context.TargetActor);
	if (!UseTarget)
	{
		for (UActorComponent* Comp : Context.TargetActor->GetComponents())
		{
			if (IItemUseTarget* AsTarget = Cast<IItemUseTarget>(Comp)) { UseTarget = AsTarget; break; }
		}
	}
	if (!UseTarget)
	{
		return false;
	}

	if (UseTarget->ReceiveHeldUse(ResultVerb, HeldItem, GetOwner()))
	{
		Held->ServerUpdateHeldItem(HeldItem);
		return true;
	}
	return false;
}

bool UWorldInteractionComponent::ExecuteRemoveFromSlot(const FInteractionContext& Context)
{
	if (!Context.TargetActor)
	{
		return false;
	}
	// Find the first occupied slot on the target.
	UWorldItemSlotComponent* Occupied = nullptr;
	TArray<UWorldItemSlotComponent*> Slots;
	Context.TargetActor->GetComponents<UWorldItemSlotComponent>(Slots);
	for (UWorldItemSlotComponent* Slot : Slots)
	{
		if (Slot && Slot->IsOccupied()) { Occupied = Slot; break; }
	}
	if (!Occupied)
	{
		return false;
	}

	FItemInstanceData Item;
	if (!Occupied->RemoveItem(Item))
	{
		return false;
	}

	// Prefer the player's inventory; otherwise drop it at their feet.
	UInventoryComponent* Inv = Context.PlayerInventory;
	if (Inv && Inv->AddItem(Item) && Item.Quantity <= 0)
	{
		return true;
	}
	const FTransform Xf(GetOwner()->GetActorRotation(), GetOwner()->GetActorLocation() + GetOwner()->GetActorForwardVector() * 120.f + FVector(0, 0, 30.f));
	APickupItemActor::SpawnPickupFromInstance(this, Item, Xf);
	return true;
}

bool UWorldInteractionComponent::HandleDropFromInventory(FGuid InstanceId)
{
	APawn* Pawn = Cast<APawn>(GetOwner());
	UInventoryComponent* Inv = Pawn ? Pawn->FindComponentByClass<UInventoryComponent>() : nullptr;
	if (!Inv)
	{
		return false;
	}
	FItemInstanceData Item;
	if (!Inv->FindItem(InstanceId, Item) || Item.Lock.bLocked)
	{
		return false;
	}

	const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
	const FItemDefinitionRow* Def = DB ? DB->GetItemDefinition(Item.ItemId) : nullptr;
	if (Def && !Def->bCanDrop)
	{
		UE_LOG(LogItemWorld, Warning, TEXT("[ServerReject] Drop failed: %s cannot be dropped."), *Item.ItemId.ToString());
		return false;
	}

	const FTransform Xf(GetOwner()->GetActorRotation(), GetOwner()->GetActorLocation() + GetOwner()->GetActorForwardVector() * 120.f + FVector(0, 0, 20.f));
	if (APickupItemActor::SpawnPickupFromInstance(this, Item, Xf))
	{
		Inv->RemoveItem(InstanceId);
		UE_LOG(LogItemWorld, Log, TEXT("[Inventory] dropped %s"), *Item.ItemId.ToString());
		return true;
	}
	return false;
}
