// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — FL_GameUtilities (02)
// Shared Blueprint Function Library. In C++, all functions are static template/inline
// with proper null-safety — no BP workarounds needed.

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "GameplayTagContainer.h"
#include "FL_GameUtilities.generated.h"

class UGI_GameFramework;
class APC_CoreController;

/**
 * FL_GameUtilities — Static utility functions available from any Blueprint or C++.
 *
 * In C++, these are proper static functions with template type-safety.
 * The Blueprint version requires macro wrappers for subsystem access —
 * here we get clean UFUNCTION(BlueprintCallable) with fast native paths.
 */
UCLASS()
class PG_FRAMEWORK_API UFL_GameUtilities : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

public:
	// ========================================================================
	// Subsystem Access (Null-Safe)
	// ========================================================================

	/**
	 * Safe subsystem retrieval — returns nullptr instead of crashing.
	 * Use this instead of raw GetSubsystem() everywhere.
	 */
	template<typename T>
	static T* GetSubsystemSafe(const UObject* WorldContextObject)
	{
		if (!WorldContextObject)
		{
			UE_LOG(LogTemp, Warning, TEXT("FL_GameUtilities::GetSubsystemSafe — Invalid WorldContextObject"));
			return nullptr;
		}

		const UGameInstance* GameInstance = WorldContextObject->GetWorld()->GetGameInstance();
		if (!GameInstance)
		{
			UE_LOG(LogTemp, Warning, TEXT("FL_GameUtilities::GetSubsystemSafe — No GameInstance found"));
			return nullptr;
		}

		T* Subsystem = GameInstance->GetSubsystem<T>();
		if (!Subsystem)
		{
			UE_LOG(LogTemp, Warning, TEXT("FL_GameUtilities::GetSubsystemSafe — Subsystem '%s' not found"),
				*T::StaticClass()->GetName());
		}

		return Subsystem;
	}

	// Blueprint-accessible subsystem getters (UFUNCTION wrappers for the template)
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Utilities",
		meta = (WorldContext = "WorldContextObject"))
	static UGI_GameFramework* GetGameFramework(const UObject* WorldContextObject);

	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Utilities",
		meta = (WorldContext = "WorldContextObject", DeterminesOutputType = "SubsystemClass"))
	static UGameInstanceSubsystem* GetSubsystemByClass(const UObject* WorldContextObject,
		TSubclassOf<UGameInstanceSubsystem> SubsystemClass);

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

	/**
	 * Finds the first component on an actor that implements a given interface.
	 */
	UFUNCTION(BlueprintCallable, Category = "Framework|Utilities")
	static UActorComponent* FindComponentByInterface(AActor* Actor,
		TSubclassOf<UInterface> InterfaceClass);

	/**
	 * Finds the nearest actor within a radius that has a specific gameplay tag.
	 */
	UFUNCTION(BlueprintCallable, Category = "Framework|Utilities",
		meta = (WorldContext = "WorldContextObject"))
	static AActor* FindNearestActorWithTag(const UObject* WorldContextObject,
		FVector Origin, float Radius, FGameplayTag RequiredTag);

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

	/** Remap a value from [InMin, InMax] to [OutMin, OutMax]. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Math")
	static float RemapFloat(float Value, float InMin, float InMax, float OutMin, float OutMax);

	/** Linear interpolation clamped to [0, 1]. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Math")
	static float LerpClamped(float A, float B, float Alpha);

	/** Convert a direction vector to a 2D angle in degrees. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Math")
	static float VectorToAngle2D(FVector2D Direction);

	/** Shortest signed angle difference between two angles in degrees. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Math")
	static float AngleDifference(float A, float B);

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

	/** Safe gameplay tag check — returns false if actor has no tag container. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Tags")
	static bool HasGameplayTag(AActor* Actor, FGameplayTag Tag);

	/**
	 * Creates a gameplay tag from a string with validation.
	 * Returns EmptyTag and logs warning if the tag isn't registered.
	 */
	UFUNCTION(BlueprintCallable, Category = "Framework|Tags")
	static FGameplayTag MakeTagFromString(const FString& TagString, bool bLogWarning = true);

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

	/** Format seconds into HH:MM:SS string. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Text")
	static FText FormatTime(float TotalSeconds);

	/** Returns singular or plural form based on count. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Text")
	static FText Pluralise(const FText& Singular, const FText& Plural, int32 Count);

	/** Truncates text to MaxLength with ellipsis. */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Text")
	static FText TruncateText(const FText& Text, int32 MaxLength);

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

	/**
	 * Projects a world position to screen space.
	 * bIsOnScreen is false if the point is behind the camera.
	 */
	UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Framework|Screen",
		meta = (WorldContext = "WorldContextObject"))
	static bool WorldToScreenSafe(const UObject* WorldContextObject, FVector WorldPosition,
		FVector2D& OutScreenPosition, bool& bIsOnScreen);

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

	/** Debug log — stripped from shipping builds. */
	UFUNCTION(BlueprintCallable, Category = "Framework|Debug")
	static void DebugLog(const FString& Message, bool bPrintToScreen = false,
		float ScreenDuration = 5.0f, FColor ScreenColor = FColor::Cyan);

	/** Draw debug sphere in world — stripped from shipping builds. */
	UFUNCTION(BlueprintCallable, Category = "Framework|Debug")
	static void DebugSphere(const UObject* WorldContextObject, FVector Location,
		float Radius = 50.0f, FColor Color = FColor::Green, float Duration = 5.0f);
};
