// Copyright ExByte Studios. Item Interaction Ecosystem.

#include "ItemTransaction.h"
#include "ItemInventoryLog.h"
#include "InventoryComponent.h"

FItemTransaction& FItemTransaction::Add(UInventoryComponent* Inventory, const FItemInstanceData& Item)
{
	FItemTransactionStep Step;
	Step.Type = EItemTransactionStepType::AddItem;
	Step.Inventory = Inventory;
	Step.Item = Item;
	Step.Item.EnsureInstanceId();
	Step.InstanceId = Step.Item.InstanceId;
	Steps.Add(Step);
	return *this;
}

FItemTransaction& FItemTransaction::Remove(UInventoryComponent* Inventory, const FGuid& InstanceId)
{
	FItemTransactionStep Step;
	Step.Type = EItemTransactionStepType::RemoveItem;
	Step.Inventory = Inventory;
	Step.InstanceId = InstanceId;
	Steps.Add(Step);
	return *this;
}

FItemTransaction& FItemTransaction::Update(UInventoryComponent* Inventory, const FItemInstanceData& Item)
{
	FItemTransactionStep Step;
	Step.Type = EItemTransactionStepType::UpdateItem;
	Step.Inventory = Inventory;
	Step.Item = Item;
	Step.InstanceId = Item.InstanceId;
	Steps.Add(Step);
	return *this;
}

FItemTransaction& FItemTransaction::Move(UInventoryComponent* Inventory, const FGuid& InstanceId, const FGuid& ToContainerId, int32 ToSlotIndex)
{
	FItemTransactionStep Step;
	Step.Type = EItemTransactionStepType::MoveItem;
	Step.Inventory = Inventory;
	Step.InstanceId = InstanceId;
	Step.ToContainer = ToContainerId;
	Step.ToSlot = ToSlotIndex;
	Steps.Add(Step);
	return *this;
}

bool FItemTransaction::Validate() const
{
	for (const FItemTransactionStep& Step : Steps)
	{
		UInventoryComponent* Inv = Step.Inventory.Get();
		if (!Inv)
		{
			return false;
		}
		switch (Step.Type)
		{
		case EItemTransactionStepType::AddItem:
		{
			// At least one container must accept it.
			TArray<FInventoryContainer> Containers;
			Inv->GetContainers(Containers);
			bool bAccepts = false;
			for (const FInventoryContainer& C : Containers)
			{
				if (Inv->CanAcceptItem(Step.Item, C.ContainerId)) { bAccepts = true; break; }
			}
			if (!bAccepts) { return false; }
			break;
		}
		case EItemTransactionStepType::RemoveItem:
		case EItemTransactionStepType::UpdateItem:
		case EItemTransactionStepType::MoveItem:
		{
			FItemInstanceData Found;
			if (!Inv->FindItem(Step.InstanceId, Found) || Found.Lock.bLocked) { return false; }
			break;
		}
		}
	}
	return true;
}

bool FItemTransaction::Apply()
{
	if (!Validate())
	{
		return false;
	}

	for (FItemTransactionStep& Step : Steps)
	{
		UInventoryComponent* Inv = Step.Inventory.Get();
		bool bOk = false;
		switch (Step.Type)
		{
		case EItemTransactionStepType::AddItem:
		{
			FItemInstanceData ToAdd = Step.Item;
			bOk = Inv->AddItem(ToAdd);
			break;
		}
		case EItemTransactionStepType::RemoveItem:
		{
			Inv->FindItem(Step.InstanceId, Step.UndoSnapshot); // capture for rollback
			bOk = Inv->RemoveItem(Step.InstanceId);
			break;
		}
		case EItemTransactionStepType::UpdateItem:
		{
			Inv->FindItem(Step.InstanceId, Step.UndoSnapshot);
			bOk = Inv->UpdateItem(Step.Item);
			break;
		}
		case EItemTransactionStepType::MoveItem:
		{
			Inv->FindItem(Step.InstanceId, Step.UndoSnapshot);
			Step.UndoFromContainer = Step.UndoSnapshot.CurrentContainerId;
			Step.UndoFromSlot = Step.UndoSnapshot.SlotIndex;
			bOk = Inv->MoveItem(Step.InstanceId, Step.UndoFromContainer, Step.ToContainer, Step.ToSlot);
			break;
		}
		}

		if (!bOk)
		{
			UE_LOG(LogItemInventory, Warning, TEXT("[Transaction %s] step failed; rolling back."), *TransactionId.ToString());
			Rollback();
			return false;
		}
		Step.bApplied = true;
	}
	return true;
}

void FItemTransaction::Rollback()
{
	// Undo applied steps in reverse order.
	for (int32 i = Steps.Num() - 1; i >= 0; --i)
	{
		FItemTransactionStep& Step = Steps[i];
		if (!Step.bApplied)
		{
			continue;
		}
		UInventoryComponent* Inv = Step.Inventory.Get();
		if (!Inv)
		{
			continue;
		}
		switch (Step.Type)
		{
		case EItemTransactionStepType::AddItem:
			Inv->RemoveItem(Step.InstanceId);
			break;
		case EItemTransactionStepType::RemoveItem:
			if (Step.UndoSnapshot.IsValid()) { FItemInstanceData Restore = Step.UndoSnapshot; Inv->AddItem(Restore); }
			break;
		case EItemTransactionStepType::UpdateItem:
			if (Step.UndoSnapshot.IsValid()) { Inv->UpdateItem(Step.UndoSnapshot); }
			break;
		case EItemTransactionStepType::MoveItem:
			Inv->MoveItem(Step.InstanceId, Step.ToContainer, Step.UndoFromContainer, Step.UndoFromSlot);
			break;
		}
		Step.bApplied = false;
	}
}
