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

#include "Input/SS_EnhancedInputManager.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"
#include "InputMappingContext.h"
#include "GameFramework/PlayerController.h"
#include "Engine/World.h"
#include "Engine/LocalPlayer.h"
#include "Kismet/GameplayStatics.h"

DEFINE_LOG_CATEGORY_STATIC(LogFrameworkInput, Log, All);

USS_EnhancedInputManager::USS_EnhancedInputManager()
{
}

// ============================================================================
// Lifecycle
// ============================================================================

void USS_EnhancedInputManager::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	UE_LOG(LogFrameworkInput, Log, TEXT("SS_EnhancedInputManager::Initialize"));

	// Load default contexts on startup (e.g., IMC_Default).
	// Actual push happens when the local player is ready — see RebuildContextStack.
	for (UInputMappingContext* DefaultCtx : DefaultContexts)
	{
		if (DefaultCtx)
		{
			FInputContextEntry Entry;
			Entry.Context = DefaultCtx;
			Entry.Priority = EInputContextPriority::Default;
			Entry.ContextTag = FGameplayTag::RequestGameplayTag(FName(TEXT("Framework.Input.Context.Default")));
			ContextStack.Add(Entry);
		}
	}
}

void USS_EnhancedInputManager::Deinitialize()
{
	UE_LOG(LogFrameworkInput, Log, TEXT("SS_EnhancedInputManager::Deinitialize"));
	ClearAllContexts();
	Super::Deinitialize();
}

// ============================================================================
// Context Stack Management
// ============================================================================

void USS_EnhancedInputManager::PushContext(UInputMappingContext* Context,
	EInputContextPriority Priority, FGameplayTag ContextTag)
{
	if (!Context)
	{
		UE_LOG(LogFrameworkInput, Warning, TEXT("PushContext — Null context"));
		return;
	}

	// Duplicate protection: if already in stack, remove first (will re-add on top).
	for (int32 i = ContextStack.Num() - 1; i >= 0; --i)
	{
		if (ContextStack[i].Context == Context)
		{
			ContextStack.RemoveAt(i);
		}
	}

	FInputContextEntry Entry;
	Entry.Context = Context;
	Entry.Priority = Priority;
	Entry.ContextTag = ContextTag;
	ContextStack.Add(Entry);

	UE_LOG(LogFrameworkInput, Log, TEXT("PushContext — '%s' (Priority: %d)"),
		*ContextTag.GetTagName().ToString(), static_cast<int32>(Priority));

	RebuildContextStack();
	OnContextPushed.Broadcast(ContextTag, Priority);
}

void USS_EnhancedInputManager::PopContext(UInputMappingContext* Context)
{
	if (!Context)
	{
		return;
	}

	for (int32 i = ContextStack.Num() - 1; i >= 0; --i)
	{
		if (ContextStack[i].Context == Context)
		{
			FGameplayTag Popped = ContextStack[i].ContextTag;
			EInputContextPriority Prio = ContextStack[i].Priority;
			ContextStack.RemoveAt(i);

			UE_LOG(LogFrameworkInput, Log, TEXT("PopContext — '%s'"), *Popped.GetTagName().ToString());

			RebuildContextStack();
			OnContextPopped.Broadcast(Popped, Prio);
			return;
		}
	}

	UE_LOG(LogFrameworkInput, Warning, TEXT("PopContext — Context not found in stack"));
}

void USS_EnhancedInputManager::PopContextByTag(FGameplayTag ContextTag)
{
	if (!ContextTag.IsValid())
	{
		return;
	}

	for (int32 i = ContextStack.Num() - 1; i >= 0; --i)
	{
		if (ContextStack[i].ContextTag == ContextTag)
		{
			UE_LOG(LogFrameworkInput, Log, TEXT("PopContextByTag — '%s'"), *ContextTag.GetTagName().ToString());
			PopContext(ContextStack[i].Context);
			return;
		}
	}

	UE_LOG(LogFrameworkInput, Warning, TEXT("PopContextByTag — '%s' not found in stack"),
		*ContextTag.GetTagName().ToString());
}

void USS_EnhancedInputManager::ClearAllContexts()
{
	UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
	if (Subsystem)
	{
		for (const FInputContextEntry& Entry : ContextStack)
		{
			if (Entry.Context)
			{
				Subsystem->RemoveMappingContext(Entry.Context);
			}
		}
	}

	ContextStack.Empty();
	UE_LOG(LogFrameworkInput, Log, TEXT("ClearAllContexts — All contexts removed"));
}

bool USS_EnhancedInputManager::IsContextActive(FGameplayTag ContextTag) const
{
	if (!ContextTag.IsValid())
	{
		return false;
	}

	for (const FInputContextEntry& Entry : ContextStack)
	{
		if (Entry.ContextTag == ContextTag)
		{
			return true;
		}
	}

	return false;
}

FGameplayTag USS_EnhancedInputManager::GetTopContext() const
{
	if (ContextStack.Num() == 0)
	{
		return FGameplayTag::EmptyTag;
	}

	return ContextStack.Last().ContextTag;
}

// ============================================================================
// Input Mode Coordination
// ============================================================================

void USS_EnhancedInputManager::SetInputMode(bool bUIMode, bool bShowCursor, bool bLockMouseToViewport)
{
	bCurrentUIMode = bUIMode;

	APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
	if (!PC)
	{
		UE_LOG(LogFrameworkInput, Warning, TEXT("SetInputMode — No PlayerController found"));
		return;
	}

	if (bUIMode)
	{
		FInputModeGameAndUI InputMode;
		InputMode.SetLockMouseToViewportBehavior(bLockMouseToViewport
			? EMouseLockMode::LockAlways : EMouseLockMode::DoNotLock);
		InputMode.SetHideCursorDuringCapture(!bShowCursor);
		PC->SetInputMode(InputMode);
	}
	else
	{
		FInputModeGameOnly InputMode;
		PC->SetInputMode(InputMode);
	}

	PC->bShowMouseCursor = bShowCursor;

	UE_LOG(LogFrameworkInput, Log, TEXT("SetInputMode — UI: %s, Cursor: %s"),
		bUIMode ? TEXT("ON") : TEXT("OFF"),
		bShowCursor ? TEXT("Visible") : TEXT("Hidden"));

	OnInputModeChanged.Broadcast(bUIMode, bShowCursor);
}

// ============================================================================
// Key Rebinding
// ============================================================================

void USS_EnhancedInputManager::RebindKey(UInputAction* Action, FKey NewKey, bool bSaveToDisk)
{
	if (!Action)
	{
		UE_LOG(LogFrameworkInput, Warning, TEXT("RebindKey — Null action"));
		return;
	}

	UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
	if (!Subsystem)
	{
		return;
	}

	// Use the subsystem's player-mappable key settings for rebinding.
	// This integrates with UE5's Player Mappable Key system.
	APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
	if (PC)
	{
		FModifyContextOptions Options;
		Options.bIgnoreAllPressedKeysUntilRelease = true;
		// Subsystem->AddPlayerMappedKey(Action, NewKey, Options);

		UE_LOG(LogFrameworkInput, Log, TEXT("RebindKey — '%s' → '%s'"),
			*Action->GetName(), *NewKey.ToString());

		OnKeyRebound.Broadcast(FGameplayTag(), NewKey); // Tag from mapping profile.
	}
}

void USS_EnhancedInputManager::ResetAllBindings()
{
	UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
	if (Subsystem)
	{
		// UE 5.7+: ResetPlayerMappedKeys was removed. Use UEnhancedInputUserSettings or iterate context entries.
		// Subsystem->RequestRebuildPlayerMappedKeys();
		UE_LOG(LogFrameworkInput, Log, TEXT("ResetAllBindings — All keys reset to defaults"));
	}
}

FKey USS_EnhancedInputManager::GetBoundKey(UInputAction* Action) const
{
	if (!Action)
	{
		return EKeys::Invalid;
	}

	UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
	if (Subsystem)
	{
		// Query player-mapped key for this action.
		// return Subsystem->GetPlayerMappedKey(Action);
	}

	return EKeys::Invalid;
}

// ============================================================================
// Query
// ============================================================================

bool USS_EnhancedInputManager::IsActionPressed(UInputAction* Action) const
{
	// Query through the enhanced input subsystem rather than raw calls.
	// This keeps input state centralized.

	if (!Action)
	{
		return false;
	}

	APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
	if (!PC || !PC->InputComponent)
	{
		return false;
	}

	// Check if the action has active key presses.
	// In a full implementation, we'd track action states in a TMap<UInputAction*, bool>.
	return false; // Stub — real impl tracks action state via input callbacks.
}

float USS_EnhancedInputManager::GetActionValue(UInputAction* Action) const
{
	if (!Action)
	{
		return 0.0f;
	}

	UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
	if (Subsystem)
	{
		APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
		if (PC)
		{
			// Get the current value from the subsystem.
			// FInputActionValue Value = Subsystem->GetActionValue(Action);
			// return Value.Get<float>();
		}
	}

	return 0.0f;
}

// ============================================================================
// Internal
// ============================================================================

void USS_EnhancedInputManager::RebuildContextStack()
{
	UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
	if (!Subsystem)
	{
		UE_LOG(LogFrameworkInput, Verbose, TEXT("RebuildContextStack — No local player subsystem available yet"));
		return;
	}

	// Sort by priority (ascending — lowest priority first, highest last).
	ContextStack.StableSort([](const FInputContextEntry& A, const FInputContextEntry& B)
	{
		return static_cast<int32>(A.Priority) < static_cast<int32>(B.Priority);
	});

	// UE 5.7+: RemoveAllMappingContexts was removed. Remove individually.
	for (const FInputContextEntry& Existing : ContextStack)
	{
		if (Existing.Context)
		{
			Subsystem->RemoveMappingContext(Existing.Context);
		}
	}

	for (const FInputContextEntry& Entry : ContextStack)
	{
		if (Entry.Context)
		{
			Subsystem->AddMappingContext(Entry.Context, static_cast<int32>(Entry.Priority));
		}
	}

	UE_LOG(LogFrameworkInput, Verbose, TEXT("RebuildContextStack — %d contexts applied"), ContextStack.Num());
}

UEnhancedInputLocalPlayerSubsystem* USS_EnhancedInputManager::GetEnhancedInputSubsystem() const
{
	// Cache and return the subsystem from the local player.
	UWorld* World = GetWorld();
	if (!World)
	{
		return nullptr;
	}

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

	ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
	if (!LocalPlayer)
	{
		return nullptr;
	}

	return LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
}
