// Copyright ExByte Studios. Item Interaction Ecosystem.

#include "Modules/ModuleManager.h"
#include "ItemDebugLog.h"
#include "ItemDatabaseSubsystem.h"
#include "ItemDefinitionRow.h"
#include "InventoryComponent.h"
#include "ItemFeatureLibrary.h"
#include "ItemCraftingLibrary.h"
#include "PickupItemActor.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
#include "Engine/World.h"
#include "Engine/Engine.h"

DEFINE_LOG_CATEGORY(LogItemDebug);

// Implemented as the Item Debugger console toolkit (section 29).
namespace ItemDebugCmd
{
	static APawn* GetPlayerPawn(UWorld* World)
	{
		APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
		return PC ? PC->GetPawn() : nullptr;
	}

	static UInventoryComponent* GetPlayerInventory(UWorld* World)
	{
		APawn* Pawn = GetPlayerPawn(World);
		return Pawn ? Pawn->FindComponentByClass<UInventoryComponent>() : nullptr;
	}

	static void Screen(const FString& Msg, const FColor Color = FColor::Green)
	{
		UE_LOG(LogItemDebug, Log, TEXT("%s"), *Msg);
		if (GEngine)
		{
			GEngine->AddOnScreenDebugMessage(-1, 4.f, Color, Msg);
		}
	}

	static void Give(const TArray<FString>& Args, UWorld* World)
	{
		if (Args.Num() < 1) { Screen(TEXT("Usage: ItemEco.Give <ItemId> [Qty]"), FColor::Yellow); return; }
		UInventoryComponent* Inv = GetPlayerInventory(World);
		UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(World);
		if (!Inv || !DB) { Screen(TEXT("No player inventory / database."), FColor::Red); return; }

		const FName Id(*Args[0]);
		const int32 Qty = Args.Num() > 1 ? FMath::Max(1, FCString::Atoi(*Args[1])) : 1;
		if (!DB->HasItem(Id)) { Screen(FString::Printf(TEXT("Unknown item '%s'"), *Id.ToString()), FColor::Red); return; }

		FItemInstanceData Item = DB->MakeItemInstance(Id, Qty);
		Inv->AddItem(Item);
		Screen(FString::Printf(TEXT("[Inventory] gave %s x%d"), *Id.ToString(), Qty));
	}

	static void Spawn(const TArray<FString>& Args, UWorld* World)
	{
		if (Args.Num() < 1) { Screen(TEXT("Usage: ItemEco.Spawn <ItemId> [Qty]"), FColor::Yellow); return; }
		APawn* Pawn = GetPlayerPawn(World);
		if (!Pawn) { Screen(TEXT("No player pawn."), FColor::Red); return; }

		const FName Id(*Args[0]);
		const int32 Qty = Args.Num() > 1 ? FMath::Max(1, FCString::Atoi(*Args[1])) : 1;
		const FTransform Xf(Pawn->GetActorRotation(), Pawn->GetActorLocation() + Pawn->GetActorForwardVector() * 200.f + FVector(0, 0, 50.f));
		if (APickupItemActor::SpawnPickup(World, Id, Qty, Xf))
		{
			Screen(FString::Printf(TEXT("Spawned pickup %s x%d"), *Id.ToString(), Qty));
		}
		else
		{
			Screen(FString::Printf(TEXT("Failed to spawn '%s'"), *Id.ToString()), FColor::Red);
		}
	}

	static void Clear(const TArray<FString>&, UWorld* World)
	{
		UInventoryComponent* Inv = GetPlayerInventory(World);
		if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
		TArray<FItemInstanceData> Items;
		Inv->GetAllItems(Items);
		for (const FItemInstanceData& It : Items) { Inv->RemoveItem(It.InstanceId); }
		Screen(FString::Printf(TEXT("Cleared %d items."), Items.Num()));
	}

	static void Dump(const TArray<FString>&, UWorld* World)
	{
		UInventoryComponent* Inv = GetPlayerInventory(World);
		if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
		TArray<FItemInstanceData> Items;
		Inv->GetAllItems(Items);
		Screen(FString::Printf(TEXT("--- Inventory (%d entries) ---"), Items.Num()), FColor::Cyan);
		for (const FItemInstanceData& It : Items)
		{
			UE_LOG(LogItemDebug, Log, TEXT("  %s x%d  [container %s slot %d]"),
				*It.ItemId.ToString(), It.Quantity, *It.CurrentContainerId.ToString(), It.SlotIndex);
		}
	}

	static void Validate(const TArray<FString>&, UWorld* World)
	{
		UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(World);
		if (!DB) { Screen(TEXT("No database."), FColor::Red); return; }
		TArray<FItemValidationIssue> Issues;
		DB->ValidateData(Issues);
		Screen(FString::Printf(TEXT("Validation: %d issue(s)."), Issues.Num()), Issues.Num() ? FColor::Yellow : FColor::Green);
		for (const FItemValidationIssue& Issue : Issues)
		{
			UE_LOG(LogItemDebug, Warning, TEXT("  [%s] %s: %s"),
				Issue.bIsError ? TEXT("ERROR") : TEXT("WARN"), *Issue.ItemId.ToString(), *Issue.Message);
		}
	}

	// Mutates the first inventory item via the feature library.
	static void MutateFirst(UWorld* World, TFunctionRef<void(FItemInstanceData&)> Mutator, const TCHAR* What)
	{
		UInventoryComponent* Inv = GetPlayerInventory(World);
		if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
		TArray<FItemInstanceData> Items;
		Inv->GetAllItems(Items);
		if (Items.Num() == 0) { Screen(TEXT("Inventory empty."), FColor::Yellow); return; }
		FItemInstanceData Item = Items[0];
		Mutator(Item);
		Inv->UpdateItem(Item);
		Screen(FString::Printf(TEXT("%s %s"), What, *Item.ItemId.ToString()));
	}

	static void Rot(const TArray<FString>&, UWorld* World)
	{
		UInventoryComponent* Inv = GetPlayerInventory(World);
		if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
		TArray<FItemInstanceData> Items;
		Inv->GetAllItems(Items);
		if (Items.Num() == 0) { Screen(TEXT("Inventory empty."), FColor::Yellow); return; }
		FItemInstanceData Item = Items[0];
		FName TransformId;
		UItemFeatureLibrary::ForceRot(World, Item, TransformId);
		if (!TransformId.IsNone())
		{
			UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(World);
			FItemInstanceData NewItem = DB ? DB->MakeItemInstance(TransformId, Item.Quantity) : FItemInstanceData(TransformId, Item.Quantity);
			Inv->RemoveItem(Item.InstanceId);
			Inv->AddItem(NewItem);
			Screen(FString::Printf(TEXT("Rotted -> %s"), *TransformId.ToString()));
		}
		else
		{
			Inv->UpdateItem(Item);
			Screen(FString::Printf(TEXT("Rotted %s"), *Item.ItemId.ToString()));
		}
	}

	static void Break(const TArray<FString>&, UWorld* World)
	{
		MutateFirst(World, [](FItemInstanceData& It) { UItemFeatureLibrary::BreakItem(It); }, TEXT("Broke"));
	}

	static void DrainBattery(const TArray<FString>& Args, UWorld* World)
	{
		const float Amt = Args.Num() > 0 ? FCString::Atof(*Args[0]) : 0.25f;
		MutateFirst(World, [Amt](FItemInstanceData& It) { UItemFeatureLibrary::DrainBattery(It, Amt, 1.f); }, TEXT("Drained battery of"));
	}

	static void AddFuel(const TArray<FString>& Args, UWorld* World)
	{
		const float Amt = Args.Num() > 0 ? FCString::Atof(*Args[0]) : 5.f;
		MutateFirst(World, [Amt](FItemInstanceData& It) { UItemFeatureLibrary::AddFuel(It, Amt, 9999.f); }, TEXT("Added fuel to"));
	}

	static void UnlockAll(const TArray<FString>&, UWorld* World)
	{
		UInventoryComponent* Inv = GetPlayerInventory(World);
		if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
		TArray<FItemInstanceData> Items;
		Inv->GetAllItems(Items);
		int32 Count = 0;
		for (const FItemInstanceData& It : Items) { if (Inv->ClearItemLock(It.InstanceId)) { ++Count; } }
		Screen(FString::Printf(TEXT("Unlocked %d items."), Count));
	}

	static void RollLoot(const TArray<FString>& Args, UWorld* World)
	{
		if (Args.Num() < 1) { Screen(TEXT("Usage: ItemEco.RollLoot <TableId> [Rolls]"), FColor::Yellow); return; }
		UInventoryComponent* Inv = GetPlayerInventory(World);
		if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
		const int32 Rolls = Args.Num() > 1 ? FMath::Max(1, FCString::Atoi(*Args[1])) : 5;
		UItemCraftingLibrary::RollLootIntoInventory(World, Inv, FName(*Args[0]), Rolls);
		Screen(FString::Printf(TEXT("Rolled loot '%s' x%d"), *Args[0], Rolls));
	}

	static void Craft(const TArray<FString>& Args, UWorld* World)
	{
		if (Args.Num() < 1) { Screen(TEXT("Usage: ItemEco.Craft <RecipeId>"), FColor::Yellow); return; }
		UInventoryComponent* Inv = GetPlayerInventory(World);
		if (!Inv) { Screen(TEXT("No player inventory."), FColor::Red); return; }
		const bool bOk = UItemCraftingLibrary::Craft(World, Inv, FName(*Args[0]), FGameplayTag());
		Screen(FString::Printf(TEXT("Craft '%s': %s"), *Args[0], bOk ? TEXT("OK") : TEXT("FAILED")), bOk ? FColor::Green : FColor::Red);
	}
}

// --- Console command registration ---

static FAutoConsoleCommandWithWorldAndArgs GCmdGive(
	TEXT("ItemEco.Give"), TEXT("Give <ItemId> [Qty] to the local player inventory."),
	FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Give));

static FAutoConsoleCommandWithWorldAndArgs GCmdSpawn(
	TEXT("ItemEco.Spawn"), TEXT("Spawn <ItemId> [Qty] as a world pickup in front of the player."),
	FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Spawn));

static FAutoConsoleCommandWithWorldAndArgs GCmdClear(
	TEXT("ItemEco.Clear"), TEXT("Clear the local player inventory."),
	FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Clear));

static FAutoConsoleCommandWithWorldAndArgs GCmdDump(
	TEXT("ItemEco.Dump"), TEXT("Dump the local player inventory to the log."),
	FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Dump));

static FAutoConsoleCommandWithWorldAndArgs GCmdValidate(
	TEXT("ItemEco.Validate"), TEXT("Run data validation on the item database."),
	FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Validate));

static FAutoConsoleCommandWithWorldAndArgs GCmdRot(
	TEXT("ItemEco.Rot"), TEXT("Force-rot the first inventory item."),
	FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Rot));

static FAutoConsoleCommandWithWorldAndArgs GCmdBreak(
	TEXT("ItemEco.Break"), TEXT("Break the first inventory item."),
	FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Break));

static FAutoConsoleCommandWithWorldAndArgs GCmdDrain(
	TEXT("ItemEco.DrainBattery"), TEXT("DrainBattery [Amount] from the first item."),
	FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::DrainBattery));

static FAutoConsoleCommandWithWorldAndArgs GCmdAddFuel(
	TEXT("ItemEco.AddFuel"), TEXT("AddFuel [Amount] to the first item."),
	FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::AddFuel));

static FAutoConsoleCommandWithWorldAndArgs GCmdUnlock(
	TEXT("ItemEco.UnlockAll"), TEXT("Clear all item locks in the player inventory."),
	FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::UnlockAll));

static FAutoConsoleCommandWithWorldAndArgs GCmdRollLoot(
	TEXT("ItemEco.RollLoot"), TEXT("RollLoot <TableId> [Rolls] into the player inventory."),
	FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::RollLoot));

static FAutoConsoleCommandWithWorldAndArgs GCmdCraft(
	TEXT("ItemEco.Craft"), TEXT("Craft <RecipeId> using the player inventory."),
	FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&ItemDebugCmd::Craft));

IMPLEMENT_MODULE(FDefaultModuleImpl, ItemDebug)
