// Copyright ExByte Studios. Item Interaction Ecosystem.

#include "ItemFeatureLibrary.h"
#include "ItemInventoryLog.h"
#include "ItemNativeTags.h"
#include "ItemDatabaseSubsystem.h"
#include "ItemAuxiliaryRows.h"
#include "InventoryComponent.h"
#include "Engine/World.h"

namespace
{
	float GetWorldSeconds(const UObject* WorldContext)
	{
		const UWorld* World = GEngine ? GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull) : nullptr;
		return World ? World->GetTimeSeconds() : 0.f;
	}
}

bool UItemFeatureLibrary::UpdateLazyFeatures(const UObject* WorldContext, FItemInstanceData& Item, FName& OutTransformItemId)
{
	OutTransformItemId = NAME_None;

	const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(WorldContext);
	if (!DB)
	{
		return false;
	}

	TArray<FItemFeatureRow> RottingRows;
	DB->GetFeatureRows(Item.ItemId, ItemEcosystemTags::Feature_Rotting, RottingRows);
	if (RottingRows.Num() == 0)
	{
		return false; // not perishable
	}

	float DecayPerHour = 0.f;
	FName RottenItemId = NAME_None;
	for (const FItemFeatureRow& Row : RottingRows)
	{
		if (Row.Param == FName("DecayRatePerHour")) { DecayPerHour = Row.GetFloat(); }
		else if (Row.Param == FName("RottenItemId")) { RottenItemId = FName(*Row.Value); }
	}
	if (DecayPerHour <= 0.f)
	{
		return false;
	}

	const float Now = GetWorldSeconds(WorldContext);
	const float Last = Item.RuntimeProperties.GetFloat(ItemEcosystemTags::Property_LastUpdateTime, Now);
	float Freshness = Item.RuntimeProperties.GetFloat(ItemEcosystemTags::Property_Freshness, 1.f);

	const float ElapsedHours = FMath::Max(0.f, Now - Last) / 3600.f;
	Freshness = FMath::Clamp(Freshness - DecayPerHour * ElapsedHours, 0.f, 1.f);

	Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_Freshness, Freshness);
	Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_LastUpdateTime, Now);

	if (Freshness <= 0.f)
	{
		Item.StateTags.AddTag(ItemEcosystemTags::State_Rotten);
		if (!RottenItemId.IsNone())
		{
			OutTransformItemId = RottenItemId;
		}
	}
	return true;
}

void UItemFeatureLibrary::UpdateInventoryFeatures(const UObject* WorldContext, UInventoryComponent* Inventory)
{
	if (!Inventory || !Inventory->GetOwner() || !Inventory->GetOwner()->HasAuthority())
	{
		return;
	}

	TArray<FItemInstanceData> Items;
	Inventory->GetAllItems(Items);
	for (FItemInstanceData& Item : Items)
	{
		FName TransformId;
		if (!UpdateLazyFeatures(WorldContext, Item, TransformId))
		{
			continue;
		}
		if (!TransformId.IsNone())
		{
			// Transform into the rotten variant, preserving quantity.
			const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(WorldContext);
			FItemInstanceData NewItem = DB ? DB->MakeItemInstance(TransformId, Item.Quantity) : FItemInstanceData(TransformId, Item.Quantity);
			Inventory->RemoveItem(Item.InstanceId);
			Inventory->AddItem(NewItem);
		}
		else
		{
			Inventory->UpdateItem(Item);
		}
	}
}

void UItemFeatureLibrary::ForceRot(const UObject* WorldContext, FItemInstanceData& Item, FName& OutTransformItemId)
{
	Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_Freshness, 0.f);
	Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_LastUpdateTime, GetWorldSeconds(WorldContext));
	Item.StateTags.AddTag(ItemEcosystemTags::State_Rotten);

	OutTransformItemId = NAME_None;
	if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(WorldContext))
	{
		TArray<FItemFeatureRow> Rows;
		DB->GetFeatureRows(Item.ItemId, ItemEcosystemTags::Feature_Rotting, Rows);
		for (const FItemFeatureRow& Row : Rows)
		{
			if (Row.Param == FName("RottenItemId") && !Row.Value.IsEmpty())
			{
				OutTransformItemId = FName(*Row.Value);
				break;
			}
		}
	}
}

void UItemFeatureLibrary::BreakItem(FItemInstanceData& Item)
{
	Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_Durability, 0.f);
	Item.StateTags.AddTag(ItemEcosystemTags::State_Broken);
}

void UItemFeatureLibrary::DrainBattery(FItemInstanceData& Item, float DrainPerSecond, float Seconds)
{
	const float Charge = Item.RuntimeProperties.GetFloat(ItemEcosystemTags::Property_BatteryCharge, 1.f);
	Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_BatteryCharge,
		FMath::Clamp(Charge - DrainPerSecond * Seconds, 0.f, 1.f));
}

float UItemFeatureLibrary::AddFuel(FItemInstanceData& Item, float Amount, float MaxFuel)
{
	const float Current = Item.RuntimeProperties.GetFloat(ItemEcosystemTags::Property_FuelAmount, 0.f);
	const float NewValue = FMath::Clamp(Current + Amount, 0.f, MaxFuel);
	Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_FuelAmount, NewValue);
	return NewValue - Current;
}

float UItemFeatureLibrary::TransferFuel(FItemInstanceData& From, FItemInstanceData& To, float Amount, float ToMaxFuel)
{
	const float Available = From.RuntimeProperties.GetFloat(ItemEcosystemTags::Property_FuelAmount, 0.f);
	const float ToCurrent = To.RuntimeProperties.GetFloat(ItemEcosystemTags::Property_FuelAmount, 0.f);
	const float Room = FMath::Max(0.f, ToMaxFuel - ToCurrent);
	const float Moved = FMath::Min3(Amount, Available, Room);
	if (Moved <= 0.f)
	{
		return 0.f;
	}
	From.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_FuelAmount, Available - Moved);
	To.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_FuelAmount, ToCurrent + Moved);
	return Moved;
}

void UItemFeatureLibrary::SetWetness(FItemInstanceData& Item, float Wetness)
{
	const float Clamped = FMath::Clamp(Wetness, 0.f, 1.f);
	Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_Wetness, Clamped);
	if (Clamped > 0.5f) { Item.StateTags.AddTag(ItemEcosystemTags::State_Wet); }
	else { Item.StateTags.RemoveTag(ItemEcosystemTags::State_Wet); }
}
