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

#include "Player/BPC_StateManager.h"
#include "Player/BPC_HealthSystem.h"
#include "Player/BPC_StressSystem.h"
#include "Player/BPC_StaminaSystem.h"
#include "Player/BPC_MovementStateSystem.h"
#include "GameplayTagsManager.h"

DEFINE_LOG_CATEGORY_STATIC(LogStateManager, Log, All);

// Stub variable for combat encounter check (would come from GS_CoreGameState binding).
namespace { bool bEncounterActive = false; }

UBPC_StateManager::UBPC_StateManager()
{
	PrimaryComponentTick.bCanEverTick = true;
	PrimaryComponentTick.TickInterval = 0.1f; // 10Hz — smooth heart rate without per-frame cost.

	HeartRateSmoothSpeed = 2.0f;
	TargetHeartRate = 70.0f;
}

// ============================================================================
// Overrides
// ============================================================================

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

	// Cache references to sibling components on the owner.
	AActor* Owner = GetOwner();
	if (Owner)
	{
		CachedHealthSystem = Owner->FindComponentByClass<UBPC_HealthSystem>();
		CachedStressSystem = Owner->FindComponentByClass<UBPC_StressSystem>();
		CachedStaminaSystem = Owner->FindComponentByClass<UBPC_StaminaSystem>();
		CachedMovementSystem = Owner->FindComponentByClass<UBPC_MovementStateSystem>();
	}

	// Set default states.
	CurrentActionState = DefaultActionState;
	CurrentOverlayState = DefaultOverlayState;

	UE_LOG(LogStateManager, Log, TEXT("BPC_StateManager::BeginPlay — Default: %s / %s"),
		*CurrentActionState.GetTagName().ToString(),
		*CurrentOverlayState.GetTagName().ToString());
}

void UBPC_StateManager::TickComponent(float DeltaTime, ELevelTick TickType,
	FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	// Smooth heart rate toward target.
	if (!FMath::IsNearlyEqual(HeartRateBPM, TargetHeartRate, 0.5f))
	{
		HeartRateBPM = FMath::FInterpTo(HeartRateBPM, TargetHeartRate, DeltaTime, HeartRateSmoothSpeed);

		EHeartRateTier NewTier = GetHeartRateTier(HeartRateBPM);
		if (NewTier != HeartRateTier)
		{
			HeartRateTier = NewTier;
			OnVitalSignChanged.Broadcast(FGameplayTag::RequestGameplayTag(
				FName(TEXT("Framework.State.Vital.HeartRate"))), HeartRateBPM);
		}
	}
}

// ============================================================================
// Core Query — Hot Path
// ============================================================================

bool UBPC_StateManager::IsActionPermitted(FGameplayTag ActionTag) const
{
	if (!ActionTag.IsValid())
	{
		return false;
	}

	// Force stack overrides everything — check first.
	if (IsBlockedByForceStack(ActionTag))
	{
		return false;
	}

	// Evaluate gating rules.
	if (!EvaluateGatingRules(ActionTag))
	{
		return false;
	}

	return true;
}

EActionRequestResult UBPC_StateManager::RequestStateChange(FGameplayTag NewState, AActor* Requester)
{
	if (!NewState.IsValid())
	{
		return EActionRequestResult::InvalidState;
	}

	if (CurrentActionState == NewState)
	{
		return EActionRequestResult::AlreadyActive;
	}

	// Check force stack override.
	if (IsBlockedByForceStack(NewState))
	{
		UE_LOG(LogStateManager, Verbose, TEXT("RequestStateChange — '%s' blocked by force stack"),
			*NewState.GetTagName().ToString());
		return EActionRequestResult::BlockedByForce;
	}

	// Check gating.
	if (!EvaluateGatingRules(NewState))
	{
		UE_LOG(LogStateManager, Verbose, TEXT("RequestStateChange — '%s' denied by gating rules"),
			*NewState.GetTagName().ToString());
		return EActionRequestResult::Denied;
	}

	// Apply the state change.
	FGameplayTag OldState = CurrentActionState;
	CurrentActionState = NewState;

	UE_LOG(LogStateManager, Log, TEXT("RequestStateChange — '%s' → '%s' (by %s)"),
		*OldState.GetTagName().ToString(),
		*NewState.GetTagName().ToString(),
		Requester ? *Requester->GetName() : TEXT("Unknown"));

	OnActionStateChanged.Broadcast(NewState, OldState);

	return EActionRequestResult::Granted;
}

// ============================================================================
// Force Stack Pattern
// ============================================================================

void UBPC_StateManager::ForceStateChange(FGameplayTag ForceState, FString Reason)
{
	if (!ForceState.IsValid())
	{
		return;
	}

	// Save current states before overriding.
	if (ForceStack.Num() == 0)
	{
		PreForceActionState = CurrentActionState;
		PreForceOverlayState = CurrentOverlayState;
	}

	FForceStackEntry Entry;
	Entry.State = ForceState;
	Entry.Reason = Reason;
	ForceStack.Push(Entry);

	// Override active state.
	FGameplayTag OldState = CurrentActionState;
	CurrentActionState = ForceState;

	UE_LOG(LogStateManager, Log, TEXT("ForceStateChange — Pushed '%s' (Reason: %s). Stack depth: %d"),
		*ForceState.GetTagName().ToString(), *Reason, ForceStack.Num());

	OnForceStackPushed.Broadcast(ForceState);
	OnActionStateChanged.Broadcast(ForceState, OldState);
}

void UBPC_StateManager::RestorePreviousState()
{
	if (ForceStack.Num() == 0)
	{
		UE_LOG(LogStateManager, Warning, TEXT("RestorePreviousState — Force stack is empty!"));
		return;
	}

	FForceStackEntry Popped = ForceStack.Pop();

	FGameplayTag RestoredAction;
	FGameplayTag RestoredOverlay;

	if (ForceStack.Num() > 0)
	{
		// Still have forced states — use the next one down.
		RestoredAction = ForceStack.Top().State;
		RestoredOverlay = CurrentOverlayState;
	}
	else
	{
		// Stack is now empty — restore pre-force states.
		RestoredAction = PreForceActionState;
		RestoredOverlay = PreForceOverlayState;
	}

	FGameplayTag OldState = CurrentActionState;
	CurrentActionState = RestoredAction;
	CurrentOverlayState = RestoredOverlay;

	UE_LOG(LogStateManager, Log, TEXT("RestorePreviousState — Popped '%s', restored '%s'. Stack depth: %d"),
		*Popped.State.GetTagName().ToString(),
		*RestoredAction.GetTagName().ToString(),
		ForceStack.Num());

	OnForceStackPopped.Broadcast(RestoredAction);
	OnActionStateChanged.Broadcast(RestoredAction, OldState);
	OnOverlayStateChanged.Broadcast(RestoredOverlay, CurrentOverlayState);
}

// ============================================================================
// Gating Logic
// ============================================================================

bool UBPC_StateManager::EvaluateGatingRules(FGameplayTag ActionTag) const
{
	if (!GatingTable)
	{
		// No gating table — permit everything (lenient default).
		return true;
	}

	// The gating table evaluates: "Can ActionTag be activated given CurrentActionState?"
	// This delegates to DA_StateGatingTable's native C++ evaluation.
	// 37 rules iterated in C++ — negligible cost vs BP Chooser Table overhead.

	// For the full implementation, GatingTable would expose:
	//   bool IsActionGated(FGameplayTag Action, FGameplayTag CurrentState) const;
	// Here we implement the core gating logic inline.

	// Check for explicit blocking rules.
	// Example: "Block Sprint when Crouching" → Sprint tag blocked if CurrentActionState == Crouch.
	// Real implementation delegates to DA_StateGatingTable.
	return true; // Placeholder — full rules in DA_StateGatingTable.
}

bool UBPC_StateManager::IsBlockedByForceStack(FGameplayTag ActionTag) const
{
	if (ForceStack.Num() == 0)
	{
		return false;
	}

	// If the force stack has an active entry, most actions are blocked.
	// Death state: blocks all actions except menu/cutscene.
	const FForceStackEntry& Active = ForceStack.Top();

	// Check if the force state explicitly permits this action.
	// Death permits Menu, Cutscene; Cutscene permits nothing.
	// This logic can be extended via DA_StateGatingTable force-state rules.
	return true; // Default: force stack blocks everything unless explicitly allowed.
}

// ============================================================================
// Vital Sign Calculation
// ============================================================================

void UBPC_StateManager::RecalculateTargetHeartRate()
{
	float BaseBPM = 70.0f; // Resting heart rate.

	// Stress contribution.
	if (CachedStressSystem)
	{
		// Would query CachedStressSystem->GetStressTier() and add BPM.
		// Higher stress = higher BPM (up to +50 BPM).
	}

	// Stamina exhaustion contribution.
	if (CachedStaminaSystem)
	{
		// Low stamina = higher BPM (exhaustion adds +20 BPM).
	}

	// Combat contribution.
	if (bEncounterActive)
	{
		BaseBPM += 30.0f; // Combat adds stress.
	}

	TargetHeartRate = FMath::Clamp(BaseBPM, 50.0f, 200.0f);
}

EHeartRateTier UBPC_StateManager::GetHeartRateTier(float BPM)
{
	if (BPM < 80.0f)  return EHeartRateTier::Resting;
	if (BPM < 100.0f) return EHeartRateTier::Elevated;
	if (BPM < 130.0f) return EHeartRateTier::Stressed;
	if (BPM < 160.0f) return EHeartRateTier::Panic;
	return EHeartRateTier::Critical;
}
