// Copyright ExByte Studios. Item Interaction Ecosystem.

#include "InteractionAction.h"
#include "InteractableComponent.h"
#include "ItemInteractionLog.h"
#include "ItemNativeTags.h"
#include "ItemDatabaseSubsystem.h"
#include "ItemDefinitionRow.h"
#include "InventoryComponent.h"
#include "GameFramework/Character.h"

namespace
{
	/** Picks the item a requirement should inspect: held first, else the target item. */
	const FItemInstanceData* ResolveSubjectItem(const FInteractionContext& Ctx)
	{
		if (Ctx.bHasHeldItem) { return &Ctx.HeldItem; }
		if (Ctx.bHasTargetItem) { return &Ctx.TargetItem; }
		return nullptr;
	}

	bool ItemHasTag(const UObject* WorldCtx, const FItemInstanceData& Item, const FGameplayTag& Tag)
	{
		if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(WorldCtx))
		{
			if (const FItemDefinitionRow* Def = DB->GetItemDefinition(Item.ItemId))
			{
				return Def->ItemTags.HasTag(Tag) || Def->ItemType.MatchesTag(Tag);
			}
		}
		return false;
	}

	bool EvaluateRequirement(const FInteractionRequirement& Req, const FInteractionContext& Ctx)
	{
		switch (Req.RequirementType)
		{
		case EInteractionRequirementType::None:
			return true;

		case EInteractionRequirementType::HasInventorySpace:
		{
			if (!Ctx.PlayerInventory) { return false; }
			const FItemInstanceData* Subject = Ctx.bHasTargetItem ? &Ctx.TargetItem : ResolveSubjectItem(Ctx);
			if (!Subject) { return true; }
			TArray<FInventoryContainer> Containers;
			Ctx.PlayerInventory->GetContainers(Containers);
			for (const FInventoryContainer& C : Containers)
			{
				if (Ctx.PlayerInventory->CanAcceptItem(*Subject, C.ContainerId))
				{
					return true;
				}
			}
			return false;
		}

		case EInteractionRequirementType::HasItemWithTag:
		{
			if (!Ctx.PlayerInventory) { return false; }
			TArray<FItemInstanceData> Items;
			Ctx.PlayerInventory->GetAllItems(Items);
			for (const FItemInstanceData& It : Items)
			{
				if (ItemHasTag(Ctx.PlayerInventory.Get(), It, Req.RequiredTag)) { return true; }
			}
			return false;
		}

		case EInteractionRequirementType::HasHeldItemWithTag:
			return Ctx.bHasHeldItem && ItemHasTag(Ctx.Character.Get(), Ctx.HeldItem, Req.RequiredTag);

		case EInteractionRequirementType::PropertyGreaterThan:
		{
			const FItemInstanceData* Subject = ResolveSubjectItem(Ctx);
			return Subject && Subject->RuntimeProperties.GetFloat(Req.RequiredTag) > Req.RequiredValue;
		}

		case EInteractionRequirementType::PropertyLessThan:
		{
			const FItemInstanceData* Subject = ResolveSubjectItem(Ctx);
			return Subject && Subject->RuntimeProperties.GetFloat(Req.RequiredTag) < Req.RequiredValue;
		}

		case EInteractionRequirementType::TargetHasFreeSlotTag:
			// Re-validated against the world slot component at execution time (stage 8).
			return Ctx.TargetActor != nullptr;

		case EInteractionRequirementType::NotWet:
		{
			const FItemInstanceData* Subject = ResolveSubjectItem(Ctx);
			return !Subject || Subject->RuntimeProperties.GetFloat(ItemEcosystemTags::Property_Wetness) < 0.5f;
		}

		case EInteractionRequirementType::NotBroken:
		{
			const FItemInstanceData* Subject = ResolveSubjectItem(Ctx);
			return !Subject || !Subject->StateTags.HasTag(ItemEcosystemTags::State_Broken);
		}

		default:
			return true;
		}
	}
}

bool UInteractionAction::CanExecute_Implementation(const FInteractionContext& Context, FText& OutDisabledReason) const
{
	for (const FInteractionRequirement& Req : Requirements)
	{
		if (!EvaluateRequirement(Req, Context))
		{
			OutDisabledReason = Req.FailureText.IsEmpty()
				? NSLOCTEXT("ItemInteraction", "RequirementFailed", "Requirement not met")
				: Req.FailureText;
			return false;
		}
	}
	return true;
}

void UInteractionAction::Execute_Implementation(const FInteractionContext& Context)
{
	// Base does nothing; concrete actions / the interaction component handle effects.
	UE_LOG(LogItemInteraction, Verbose, TEXT("UInteractionAction '%s' base Execute (no-op)."), *ActionTag.ToString());
}

FInteractionOption UInteractionAction::BuildOption(const FInteractionContext& Context) const
{
	FInteractionOption Option;
	Option.ActionTag = ActionTag;
	Option.Mode = Mode;
	Option.DisplayText = DisplayText;
	Option.Priority = Priority;
	Option.bRequiresHold = bRequiresHold;
	Option.HoldDuration = HoldDuration;
	Option.ActionDuration = Execution.bHasDuration ? Execution.Duration : 0.f;

	FText Reason;
	Option.bIsEnabled = CanExecute(Context, Reason);
	Option.DisabledReason = Reason;

	Option.Request.Mode = Mode;
	Option.Request.ActionTag = ActionTag;
	Option.Request.TargetActor = Context.TargetActor;
	if (Context.bHasTargetItem) { Option.Request.ItemInstanceId = Context.TargetItem.InstanceId; }
	else if (Context.bHasHeldItem) { Option.Request.ItemInstanceId = Context.HeldItem.InstanceId; }
	return Option;
}

UWorld* UInteractionAction::GetWorld() const
{
	if (HasAllFlags(RF_ClassDefaultObject))
	{
		return nullptr;
	}
	if (const UObject* Outer = GetOuter())
	{
		return Outer->GetWorld();
	}
	return nullptr;
}
