// Copyright Epic Games, Inc. All Rights Reserved.


#include "AgrarianGamePlayerController.h"
#include "AgrarianCampfire.h"
#include "AgrarianCraftingComponent.h"
#include "AgrarianGameCharacter.h"
#include "AgrarianInventoryComponent.h"
#include "AgrarianItemPickup.h"
#include "AgrarianPersistenceSubsystem.h"
#include "AgrarianShelterActor.h"
#include "AgrarianSurvivalComponent.h"
#include "EnhancedInputSubsystems.h"
#include "Engine/LocalPlayer.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "InputMappingContext.h"
#include "Blueprint/UserWidget.h"
#include "AgrarianGame.h"
#include "Widgets/Input/SVirtualJoystick.h"

namespace
{
	const FVector GroundZeroDeveloperTravelHomeLocation(-22000.0f, -3500.0f, 1148.0f);
	const TCHAR* GroundZeroServerTravelMapPath = TEXT("/Game/Agrarian/Maps/L_GroundZeroTerrain_Test");

	bool ResolveAgrarianServerTravelMap(const FName MapName, FString& OutTravelMap)
	{
		if (MapName == NAME_None
			|| MapName == TEXT("GroundZero")
			|| MapName == TEXT("L_GroundZeroTerrain_Test"))
		{
			OutTravelMap = GroundZeroServerTravelMapPath;
			return true;
		}

		return false;
	}

	bool ApplyAgrarianItemUseEffect(const FName ItemId, const int32 Quantity, UAgrarianSurvivalComponent* SurvivalComponent, FString& OutEffectSummary)
	{
		if (!SurvivalComponent || Quantity <= 0)
		{
			return false;
		}

		if (ItemId == TEXT("food"))
		{
			SurvivalComponent->AddFood(15.0f * Quantity);
			OutEffectSummary = FString::Printf(TEXT("restored %.0f hunger"), 15.0f * Quantity);
			return true;
		}

		if (ItemId == TEXT("meat"))
		{
			SurvivalComponent->AddFood(22.0f * Quantity);
			SurvivalComponent->AddSickness(3.0f * Quantity);
			OutEffectSummary = FString::Printf(TEXT("restored %.0f hunger but added raw-meat sickness risk"), 22.0f * Quantity);
			return true;
		}

		if (ItemId == TEXT("bandage"))
		{
			SurvivalComponent->ReduceInjury(18.0f * Quantity);
			SurvivalComponent->ReduceBleeding(28.0f * Quantity);
			SurvivalComponent->ReduceSprain(10.0f * Quantity);
			SurvivalComponent->RestoreHealth(4.0f * Quantity);
			OutEffectSummary = FString::Printf(TEXT("treated %.0f injury, %.0f bleeding, and %.0f sprain severity"), 18.0f * Quantity, 28.0f * Quantity, 10.0f * Quantity);
			return true;
		}

		return false;
	}
}

void AAgrarianGamePlayerController::BeginPlay()
{
	Super::BeginPlay();

	// only spawn touch controls on local player controllers
	if (ShouldUseTouchControls() && IsLocalPlayerController())
	{
		// spawn the mobile controls widget
		MobileControlsWidget = CreateWidget<UUserWidget>(this, MobileControlsWidgetClass);

		if (MobileControlsWidget)
		{
			// add the controls to the player screen
			MobileControlsWidget->AddToPlayerScreen(0);

		} else {

			UE_LOG(LogAgrarianGame, Error, TEXT("Could not spawn mobile controls widget."));

		}

	}
}

void AAgrarianGamePlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();

	// only add IMCs for local player controllers
	if (IsLocalPlayerController())
	{
		// Add Input Mapping Contexts
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
		{
			for (UInputMappingContext* CurrentContext : DefaultMappingContexts)
			{
				Subsystem->AddMappingContext(CurrentContext, 0);
			}

			// only add these IMCs if we're not using mobile touch input
			if (!ShouldUseTouchControls())
			{
				for (UInputMappingContext* CurrentContext : MobileExcludedMappingContexts)
				{
					Subsystem->AddMappingContext(CurrentContext, 0);
				}
			}
		}
	}
}

bool AAgrarianGamePlayerController::ShouldUseTouchControls() const
{
	// are we on a mobile platform? Should we force touch?
	return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls;
}

void AAgrarianGamePlayerController::AgrarianGrantItem(FName ItemId, int32 Quantity)
{
	if (ItemId == NAME_None || Quantity <= 0)
	{
		ClientMessage(TEXT("Usage: AgrarianGrantItem <ItemId> <Quantity>"));
		return;
	}

	ServerAgrarianGrantItem(ItemId, Quantity);
}

void AAgrarianGamePlayerController::AgrarianSaveWorld()
{
	ServerAgrarianSaveWorld();
}

void AAgrarianGamePlayerController::AgrarianLoadWorld()
{
	ServerAgrarianLoadWorld();
}

void AAgrarianGamePlayerController::AgrarianSurvival()
{
	const AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
	const UAgrarianSurvivalComponent* SurvivalComponent = AgrarianCharacter ? AgrarianCharacter->GetSurvivalComponent() : nullptr;
	if (!SurvivalComponent)
	{
		ClientMessage(TEXT("No Agrarian survival component found."));
		return;
	}

	const FAgrarianSurvivalSnapshot& Survival = SurvivalComponent->Survival;
	ClientMessage(FString::Printf(
		TEXT("%s | Health %.1f | Stamina %.1f | Exhaustion %.1f | Hunger %.1f | Thirst %.1f | Temp %.1fC | Injury %.1f | Bleeding %.1f | Sprain %.1f | Sickness %.1f | Death %s"),
		Survival.bIsDead ? TEXT("DEAD") : TEXT("ALIVE"),
		Survival.Health,
		Survival.Stamina,
		Survival.Exhaustion,
		Survival.Hunger,
		Survival.Thirst,
		Survival.BodyTemperature,
		Survival.InjurySeverity,
		Survival.BleedingSeverity,
		Survival.SprainSeverity,
		Survival.SicknessSeverity,
		*Survival.LastDeathReason.ToString()));
}

void AAgrarianGamePlayerController::AgrarianHeal()
{
	ServerAgrarianHeal();
}

void AAgrarianGamePlayerController::AgrarianRespawn()
{
	ServerAgrarianRespawn();
}

void AAgrarianGamePlayerController::AgrarianDropItem(FName ItemId, int32 Quantity)
{
	if (ItemId == NAME_None || Quantity <= 0)
	{
		ClientMessage(TEXT("Usage: AgrarianDropItem <ItemId> <Quantity>"));
		return;
	}

	ServerAgrarianDropItem(ItemId, Quantity);
}

void AAgrarianGamePlayerController::AgrarianSplitStack(int32 StackIndex, int32 SplitQuantity)
{
	if (StackIndex < 0 || SplitQuantity <= 0)
	{
		ClientMessage(TEXT("Usage: AgrarianSplitStack <StackIndex> <SplitQuantity>"));
		return;
	}

	ServerAgrarianSplitStack(StackIndex, SplitQuantity);
}

void AAgrarianGamePlayerController::AgrarianUseItem(FName ItemId, int32 Quantity)
{
	if (ItemId == NAME_None || Quantity <= 0)
	{
		ClientMessage(TEXT("Usage: AgrarianUseItem <ItemId> <Quantity>"));
		return;
	}

	ServerAgrarianUseItem(ItemId, Quantity);
}

void AAgrarianGamePlayerController::AgrarianCraft(FName RecipeId)
{
	if (RecipeId == NAME_None)
	{
		ClientMessage(TEXT("Usage: AgrarianCraft <RecipeId>"));
		return;
	}

	ServerAgrarianCraft(RecipeId);
}

void AAgrarianGamePlayerController::AgrarianCraftStatus()
{
	const AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
	const UAgrarianCraftingComponent* CraftingComponent = AgrarianCharacter ? AgrarianCharacter->GetCraftingComponent() : nullptr;
	if (!CraftingComponent)
	{
		ClientMessage(TEXT("No Agrarian crafting component found."));
		return;
	}

	TArray<FAgrarianRecipe> Recipes;
	CraftingComponent->GetKnownRecipes(Recipes);
	if (Recipes.IsEmpty())
	{
		ClientMessage(TEXT("No known Agrarian recipes."));
		return;
	}

	ClientMessage(FString::Printf(TEXT("Known Agrarian recipes: %d"), Recipes.Num()));
	for (const FAgrarianRecipe& Recipe : Recipes)
	{
		FText FailureReason;
		const bool bCanCraft = CraftingComponent->CanCraft(Recipe.RecipeId, FailureReason);
		const FString RecipeName = Recipe.DisplayName.IsEmpty() ? Recipe.RecipeId.ToString() : Recipe.DisplayName.ToString();
		ClientMessage(FString::Printf(
			TEXT("- %s (%s): %s"),
			*RecipeName,
			*Recipe.RecipeId.ToString(),
			bCanCraft ? TEXT("ready") : *FailureReason.ToString()));
	}
}

void AAgrarianGamePlayerController::AgrarianTravel(float X, float Y, float Z)
{
	ServerAgrarianTravel(FVector(X, Y, Z));
}

void AAgrarianGamePlayerController::AgrarianTravelHome()
{
	ServerAgrarianTravel(GroundZeroDeveloperTravelHomeLocation);
}

void AAgrarianGamePlayerController::AgrarianServerTravel(FName MapName)
{
	ServerAgrarianServerTravel(MapName);
}

void AAgrarianGamePlayerController::ServerAgrarianGrantItem_Implementation(FName ItemId, int32 Quantity)
{
	AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
	UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter ? AgrarianCharacter->GetInventoryComponent() : nullptr;
	if (!InventoryComponent)
	{
		ClientMessage(TEXT("No Agrarian inventory component found."));
		return;
	}

	FAgrarianItemStack Stack;
	Stack.ItemId = ItemId;
	Stack.DisplayName = FText::FromName(ItemId);
	Stack.Quantity = Quantity;

	if (InventoryComponent->AddItem(Stack))
	{
		ClientMessage(FString::Printf(TEXT("Granted %d x %s."), Quantity, *ItemId.ToString()));
	}
	else
	{
		ClientMessage(FString::Printf(TEXT("Failed to grant %d x %s."), Quantity, *ItemId.ToString()));
	}
}

void AAgrarianGamePlayerController::ServerAgrarianSaveWorld_Implementation()
{
	UAgrarianPersistenceSubsystem* Persistence = GetGameInstance() ? GetGameInstance()->GetSubsystem<UAgrarianPersistenceSubsystem>() : nullptr;
	if (!Persistence)
	{
		ClientMessage(TEXT("No Agrarian persistence subsystem found."));
		return;
	}

	const bool bSaved = Persistence->SaveCurrentWorld();
	ClientMessage(bSaved ? TEXT("Agrarian world saved.") : TEXT("Agrarian world save failed."));
}

void AAgrarianGamePlayerController::ServerAgrarianLoadWorld_Implementation()
{
	UAgrarianPersistenceSubsystem* Persistence = GetGameInstance() ? GetGameInstance()->GetSubsystem<UAgrarianPersistenceSubsystem>() : nullptr;
	if (!Persistence)
	{
		ClientMessage(TEXT("No Agrarian persistence subsystem found."));
		return;
	}

	Persistence->RegisterWorldActorClass(TEXT("primitive_shelter"), AAgrarianShelterActor::StaticClass());
	Persistence->RegisterWorldActorClass(TEXT("campfire"), AAgrarianCampfire::StaticClass());
	int32 RestoredPlayerCount = 0;
	int32 RestoredActorCount = 0;
	const bool bLoaded = Persistence->LoadCurrentWorld(RestoredPlayerCount, RestoredActorCount);
	ClientMessage(FString::Printf(
		TEXT("%s Restored players: %d. Restored actors: %d."),
		bLoaded ? TEXT("Agrarian world loaded.") : TEXT("Agrarian world load restored actors/players, but world state restore failed."),
		RestoredPlayerCount,
		RestoredActorCount));
}

void AAgrarianGamePlayerController::ServerAgrarianHeal_Implementation()
{
	AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
	UAgrarianSurvivalComponent* SurvivalComponent = AgrarianCharacter ? AgrarianCharacter->GetSurvivalComponent() : nullptr;
	if (!SurvivalComponent)
	{
		ClientMessage(TEXT("No Agrarian survival component found."));
		return;
	}

	SurvivalComponent->Revive(100.0f);
	SurvivalComponent->AddFood(100.0f);
	SurvivalComponent->AddWater(100.0f);
	SurvivalComponent->ReduceExhaustion(100.0f);
	SurvivalComponent->ReduceSickness(100.0f);
	SurvivalComponent->AddWarmth(37.0f - SurvivalComponent->Survival.BodyTemperature);
	ClientMessage(TEXT("Agrarian survival restored."));
}

void AAgrarianGamePlayerController::ServerAgrarianRespawn_Implementation()
{
	AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
	UAgrarianSurvivalComponent* SurvivalComponent = AgrarianCharacter ? AgrarianCharacter->GetSurvivalComponent() : nullptr;
	if (!AgrarianCharacter || !SurvivalComponent)
	{
		ClientMessage(TEXT("No Agrarian character/survival component found."));
		return;
	}

	if (!SurvivalComponent->IsDead())
	{
		ClientMessage(TEXT("AgrarianRespawn is only available after death. Use AgrarianHeal for admin recovery."));
		return;
	}

	if (UCharacterMovementComponent* MovementComponent = AgrarianCharacter->GetCharacterMovement())
	{
		MovementComponent->StopMovementImmediately();
	}

	AgrarianCharacter->SetActorLocation(GroundZeroDeveloperTravelHomeLocation, false, nullptr, ETeleportType::TeleportPhysics);
	SurvivalComponent->Revive(60.0f);
	SurvivalComponent->AddFood(35.0f);
	SurvivalComponent->AddWater(35.0f);
	SurvivalComponent->ReduceExhaustion(100.0f);
	SurvivalComponent->ReduceInjury(100.0f);
	SurvivalComponent->ReduceBleeding(100.0f);
	SurvivalComponent->ReduceSprain(100.0f);
	SurvivalComponent->ReduceSickness(100.0f);
	SurvivalComponent->AddWarmth(37.0f - SurvivalComponent->Survival.BodyTemperature);
	ClientMessage(TEXT("Agrarian MVP respawn complete: returned to Ground Zero with acute conditions stabilized."));
}

void AAgrarianGamePlayerController::ServerAgrarianDropItem_Implementation(FName ItemId, int32 Quantity)
{
	AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
	UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter ? AgrarianCharacter->GetInventoryComponent() : nullptr;
	if (!AgrarianCharacter || !InventoryComponent)
	{
		ClientMessage(TEXT("No Agrarian inventory component found."));
		return;
	}

	FAgrarianItemStack DroppedStack;
	if (!InventoryComponent->ExtractItem(ItemId, Quantity, DroppedStack))
	{
		ClientMessage(FString::Printf(TEXT("Could not drop %d x %s."), Quantity, *ItemId.ToString()));
		return;
	}

	const FVector DropLocation = AgrarianCharacter->GetActorLocation()
		+ AgrarianCharacter->GetActorForwardVector() * 150.0f
		+ FVector(0.0f, 0.0f, 40.0f);
	const FRotator DropRotation(0.0f, AgrarianCharacter->GetActorRotation().Yaw, 0.0f);

	FActorSpawnParameters SpawnParameters;
	SpawnParameters.Owner = AgrarianCharacter;
	SpawnParameters.Instigator = AgrarianCharacter;
	SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;

	AAgrarianItemPickup* Pickup = GetWorld()
		? GetWorld()->SpawnActor<AAgrarianItemPickup>(AAgrarianItemPickup::StaticClass(), DropLocation, DropRotation, SpawnParameters)
		: nullptr;
	if (!Pickup)
	{
		InventoryComponent->AddItem(DroppedStack);
		ClientMessage(FString::Printf(TEXT("Failed to spawn dropped %s; item restored."), *ItemId.ToString()));
		return;
	}

	Pickup->PickupStack = DroppedStack;
	Pickup->Quantity = DroppedStack.Quantity;
	ClientMessage(FString::Printf(TEXT("Dropped %d x %s."), DroppedStack.Quantity, *ItemId.ToString()));
}

void AAgrarianGamePlayerController::ServerAgrarianSplitStack_Implementation(int32 StackIndex, int32 SplitQuantity)
{
	AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
	UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter ? AgrarianCharacter->GetInventoryComponent() : nullptr;
	if (!InventoryComponent)
	{
		ClientMessage(TEXT("No Agrarian inventory component found."));
		return;
	}

	if (InventoryComponent->SplitStackByIndex(StackIndex, SplitQuantity))
	{
		ClientMessage(FString::Printf(TEXT("Split %d items from stack %d."), SplitQuantity, StackIndex));
	}
	else
	{
		ClientMessage(FString::Printf(TEXT("Could not split %d items from stack %d."), SplitQuantity, StackIndex));
	}
}

void AAgrarianGamePlayerController::ServerAgrarianUseItem_Implementation(FName ItemId, int32 Quantity)
{
	AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
	UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter ? AgrarianCharacter->GetInventoryComponent() : nullptr;
	UAgrarianSurvivalComponent* SurvivalComponent = AgrarianCharacter ? AgrarianCharacter->GetSurvivalComponent() : nullptr;
	if (!InventoryComponent || !SurvivalComponent)
	{
		ClientMessage(TEXT("No Agrarian inventory or survival component found."));
		return;
	}

	FAgrarianItemStack UsedStack;
	if (!InventoryComponent->ExtractItem(ItemId, Quantity, UsedStack))
	{
		ClientMessage(FString::Printf(TEXT("Could not use %d x %s."), Quantity, *ItemId.ToString()));
		return;
	}

	FString EffectSummary;
	if (!ApplyAgrarianItemUseEffect(ItemId, UsedStack.Quantity, SurvivalComponent, EffectSummary))
	{
		InventoryComponent->AddItem(UsedStack);
		ClientMessage(FString::Printf(TEXT("%s cannot be used yet; item restored."), *ItemId.ToString()));
		return;
	}

	ClientMessage(FString::Printf(TEXT("Used %d x %s: %s."), UsedStack.Quantity, *ItemId.ToString(), *EffectSummary));
}

void AAgrarianGamePlayerController::ServerAgrarianCraft_Implementation(FName RecipeId)
{
	AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
	UAgrarianCraftingComponent* CraftingComponent = AgrarianCharacter ? AgrarianCharacter->GetCraftingComponent() : nullptr;
	if (!CraftingComponent)
	{
		ClientMessage(TEXT("No Agrarian crafting component found."));
		return;
	}

	FText FailureReason;
	if (!CraftingComponent->CanCraft(RecipeId, FailureReason))
	{
		ClientMessage(FString::Printf(TEXT("Cannot craft %s: %s"), *RecipeId.ToString(), *FailureReason.ToString()));
		return;
	}

	if (CraftingComponent->Craft(RecipeId))
	{
		ClientMessage(FString::Printf(TEXT("Crafted %s."), *RecipeId.ToString()));
	}
	else
	{
		ClientMessage(FString::Printf(TEXT("Failed to craft %s."), *RecipeId.ToString()));
	}
}

void AAgrarianGamePlayerController::ServerAgrarianTravel_Implementation(FVector Destination)
{
	APawn* ControlledPawn = GetPawn();
	if (!ControlledPawn)
	{
		ClientMessage(TEXT("No controlled pawn found for developer travel."));
		return;
	}

	if (!Destination.ContainsNaN())
	{
		ControlledPawn->TeleportTo(Destination, ControlledPawn->GetActorRotation(), false, true);
		if (ACharacter* ControlledCharacter = Cast<ACharacter>(ControlledPawn))
		{
			if (UCharacterMovementComponent* Movement = ControlledCharacter->GetCharacterMovement())
			{
				Movement->StopMovementImmediately();
			}
		}

		ClientMessage(FString::Printf(
			TEXT("Developer travel complete: X %.1f Y %.1f Z %.1f"),
			Destination.X,
			Destination.Y,
			Destination.Z));
	}
	else
	{
		ClientMessage(TEXT("Developer travel failed: invalid destination."));
	}
}

void AAgrarianGamePlayerController::ServerAgrarianServerTravel_Implementation(FName MapName)
{
	UWorld* World = GetWorld();
	if (!World || !HasAuthority())
	{
		ClientMessage(TEXT("Agrarian server travel failed: no authoritative world."));
		return;
	}

	FString TravelMap;
	if (!ResolveAgrarianServerTravelMap(MapName, TravelMap))
	{
		ClientMessage(TEXT("Usage: AgrarianServerTravel GroundZero"));
		return;
	}

	const FString TravelURL = FString::Printf(TEXT("%s?listen"), *TravelMap);
	ClientMessage(FString::Printf(TEXT("Agrarian server travel requested: %s"), *TravelURL));
	World->ServerTravel(TravelURL, false, false);
}
