// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — FL_GameUtilities Implementation

#include "Core/FL_GameUtilities.h"
#include "Core/GI_GameFramework.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/GameStateBase.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
#include "GameplayTagsManager.h"
#include "GameplayTagAssetInterface.h"

// ============================================================================
// Subsystem Access
// ============================================================================

UGI_GameFramework* UFL_GameUtilities::GetGameFramework(const UObject* WorldContextObject)
{
	if (!WorldContextObject)
	{
		return nullptr;
	}

	UWorld* World = WorldContextObject->GetWorld();
	if (!World)
	{
		return nullptr;
	}

	return Cast<UGI_GameFramework>(World->GetGameInstance());
}

UGameInstanceSubsystem* UFL_GameUtilities::GetSubsystemByClass(const UObject* WorldContextObject,
	TSubclassOf<UGameInstanceSubsystem> SubsystemClass)
{
	if (!WorldContextObject || !SubsystemClass)
	{
		return nullptr;
	}

	const UGameInstance* GameInstance = WorldContextObject->GetWorld()->GetGameInstance();
	if (!GameInstance)
	{
		return nullptr;
	}

	return GameInstance->GetSubsystemBase(SubsystemClass);
}

// ============================================================================
// Actor Utilities
// ============================================================================

UActorComponent* UFL_GameUtilities::FindComponentByInterface(AActor* Actor,
	TSubclassOf<UInterface> InterfaceClass)
{
	if (!Actor || !InterfaceClass)
	{
		return nullptr;
	}

	TArray<UActorComponent*> Components;
	Actor->GetComponents(Components);

	for (UActorComponent* Comp : Components)
	{
		if (Comp && Comp->GetClass()->ImplementsInterface(InterfaceClass))
		{
			return Comp;
		}
	}

	return nullptr;
}

AActor* UFL_GameUtilities::FindNearestActorWithTag(const UObject* WorldContextObject,
	FVector Origin, float Radius, FGameplayTag RequiredTag)
{
	if (!WorldContextObject)
	{
		return nullptr;
	}

	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
	if (!World)
	{
		return nullptr;
	}

	// Collect all actors with the tag within radius.
	TArray<AActor*> Candidates;
	UGameplayStatics::GetAllActorsOfClass(World, AActor::StaticClass(), Candidates);

	AActor* Nearest = nullptr;
	float NearestDistSq = Radius * Radius;

	for (AActor* Actor : Candidates)
	{
		if (!Actor || !Actor->ActorHasTag(RequiredTag.GetTagName()))
		{
			continue;
		}

		float DistSq = FVector::DistSquared(Origin, Actor->GetActorLocation());
		if (DistSq < NearestDistSq)
		{
			NearestDistSq = DistSq;
			Nearest = Actor;
		}
	}

	return Nearest;
}

// ============================================================================
// Math Utilities
// ============================================================================

float UFL_GameUtilities::RemapFloat(float Value, float InMin, float InMax, float OutMin, float OutMax)
{
	if (FMath::IsNearlyEqual(InMax, InMin))
	{
		return OutMin;
	}

	float Alpha = (Value - InMin) / (InMax - InMin);
	return FMath::Lerp(OutMin, OutMax, Alpha);
}

float UFL_GameUtilities::LerpClamped(float A, float B, float Alpha)
{
	return FMath::Lerp(A, B, FMath::Clamp(Alpha, 0.0f, 1.0f));
}

float UFL_GameUtilities::VectorToAngle2D(FVector2D Direction)
{
	if (Direction.IsNearlyZero())
	{
		return 0.0f;
	}

	return FMath::RadiansToDegrees(FMath::Atan2(Direction.Y, Direction.X));
}

float UFL_GameUtilities::AngleDifference(float A, float B)
{
	float Diff = FMath::Fmod(B - A, 360.0f);
	if (Diff > 180.0f)
	{
		Diff -= 360.0f;
	}
	else if (Diff < -180.0f)
	{
		Diff += 360.0f;
	}
	return Diff;
}

// ============================================================================
// GameplayTag Utilities
// ============================================================================

bool UFL_GameUtilities::HasGameplayTag(AActor* Actor, FGameplayTag Tag)
{
	if (!Actor || !Tag.IsValid())
	{
		return false;
	}

	// Prefer IGameplayTagAssetInterface if the actor implements it.
	if (IGameplayTagAssetInterface* TagInterface = Cast<IGameplayTagAssetInterface>(Actor))
	{
		return TagInterface->HasMatchingGameplayTag(Tag);
	}

	// Fallback: check actor tags (FName-based, less reliable).
	return Actor->ActorHasTag(Tag.GetTagName());
}

FGameplayTag UFL_GameUtilities::MakeTagFromString(const FString& TagString, bool bLogWarning)
{
	if (TagString.IsEmpty())
	{
		return FGameplayTag::EmptyTag;
	}

	UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();

	FGameplayTag OutTag = TagManager.RequestGameplayTag(FName(*TagString), false);
	if (OutTag.IsValid())
	{
		return OutTag;
	}

	if (bLogWarning)
	{
		UE_LOG(LogTemp, Warning, TEXT("FL_GameUtilities::MakeTagFromString — Tag '%s' not registered"), *TagString);
	}

	return FGameplayTag::EmptyTag;
}

// ============================================================================
// Text Utilities
// ============================================================================

FText UFL_GameUtilities::FormatTime(float TotalSeconds)
{
	int32 Hours = FMath::FloorToInt(TotalSeconds / 3600.0f);
	int32 Minutes = FMath::FloorToInt(FMath::Fmod(TotalSeconds, 3600.0f) / 60.0f);
	int32 Seconds = FMath::FloorToInt(FMath::Fmod(TotalSeconds, 60.0f));

	return FText::FromString(FString::Printf(TEXT("%02d:%02d:%02d"), Hours, Minutes, Seconds));
}

FText UFL_GameUtilities::Pluralise(const FText& Singular, const FText& Plural, int32 Count)
{
	return (Count == 1) ? Singular : Plural;
}

FText UFL_GameUtilities::TruncateText(const FText& Text, int32 MaxLength)
{
	FString Str = Text.ToString();
	if (Str.Len() <= MaxLength)
	{
		return Text;
	}

	Str = Str.Left(MaxLength - 3) + TEXT("...");
	return FText::FromString(Str);
}

// ============================================================================
// Screen / Projection Utilities
// ============================================================================

bool UFL_GameUtilities::WorldToScreenSafe(const UObject* WorldContextObject,
	FVector WorldPosition, FVector2D& OutScreenPosition, bool& bIsOnScreen)
{
	OutScreenPosition = FVector2D::ZeroVector;
	bIsOnScreen = false;

	if (!WorldContextObject)
	{
		return false;
	}

	APlayerController* PC = UGameplayStatics::GetPlayerController(WorldContextObject, 0);
	if (!PC)
	{
		return false;
	}

	FVector2D ScreenPos;
	bool bProjected = PC->ProjectWorldLocationToScreen(WorldPosition, ScreenPos, true);

	// Check if behind camera.
	bIsOnScreen = bProjected;

	// Additional check for screen bounds.
	if (bIsOnScreen)
	{
		int32 ViewportX, ViewportY;
		PC->GetViewportSize(ViewportX, ViewportY);

		bIsOnScreen = ScreenPos.X >= 0.0f && ScreenPos.X <= ViewportX &&
					  ScreenPos.Y >= 0.0f && ScreenPos.Y <= ViewportY;
	}

	OutScreenPosition = ScreenPos;
	return bIsOnScreen;
}

// ============================================================================
// Debug (Shipping-safe)
// ============================================================================

void UFL_GameUtilities::DebugLog(const FString& Message, bool bPrintToScreen, float ScreenDuration, FColor ScreenColor)
{
#if !UE_BUILD_SHIPPING
	UE_LOG(LogTemp, Log, TEXT("%s"), *Message);

	if (bPrintToScreen && GEngine)
	{
		GEngine->AddOnScreenDebugMessage(-1, ScreenDuration, ScreenColor, Message);
	}
#endif
}

void UFL_GameUtilities::DebugSphere(const UObject* WorldContextObject, FVector Location,
	float Radius, FColor Color, float Duration)
{
#if !UE_BUILD_SHIPPING
	if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
	{
		DrawDebugSphere(World, Location, Radius, 12, Color, false, Duration);
	}
#endif
}
