// Copyright Ngonart OU. All Rights Reserved.
// UE5 Modular Game Framework — SS_PlanarCaptureManager implementation

#include "Capture/SS_PlanarCaptureManager.h"
#include "Capture/BP_PlanarCaptureActor.h"
#include "Capture/BPC_PlanarCapture.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/World.h"
#include "GameFramework/PlayerController.h"
#include "Kismet/GameplayStatics.h"

USS_PlanarCaptureManager::USS_PlanarCaptureManager()
{
}

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

	UE_LOG(LogTemp, Log, TEXT("SS_PlanarCaptureManager: Initialized for world."));
}

void USS_PlanarCaptureManager::Deinitialize()
{
	// Release all render targets
	for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
	{
		if (Entry.RenderTarget)
		{
			Entry.RenderTarget->ConditionalBeginDestroy();
		}
	}
	RenderTargetPool.Empty();

	RegisteredSurfaces.Empty();

	Super::Deinitialize();
}

void USS_PlanarCaptureManager::Tick(float DeltaTime)
{
	TimeSinceLastEvaluation += DeltaTime;

	if (TimeSinceLastEvaluation >= FullEvaluationInterval)
	{
		TimeSinceLastEvaluation = 0.0f;
		EvaluateAllSurfaces();
	}
}

TStatId USS_PlanarCaptureManager::GetStatId() const
{
	RETURN_QUICK_DECLARE_CYCLE_STAT(USS_PlanarCaptureManager, STATGROUP_Tickables);
}

// ========================================================================
// Surface Registry
// ========================================================================

void USS_PlanarCaptureManager::RegisterSurface(ABP_PlanarCaptureActor* Surface)
{
	if (!Surface)
	{
		return;
	}

	// Check for duplicates
	for (const TWeakObjectPtr<ABP_PlanarCaptureActor>& Existing : RegisteredSurfaces)
	{
		if (Existing.Get() == Surface)
		{
			UE_LOG(LogTemp, Warning, TEXT("SS_PlanarCaptureManager: Surface '%s' already registered."),
				*Surface->SurfaceDisplayName);
			return;
		}
	}

	RegisteredSurfaces.Add(Surface);
	UE_LOG(LogTemp, Log, TEXT("SS_PlanarCaptureManager: Registered surface '%s'. Total: %d"),
		*Surface->SurfaceDisplayName, RegisteredSurfaces.Num());

	OnSurfaceRegistered.Broadcast(Surface, RegisteredSurfaces.Num());

	// Evaluate immediately to assign initial tier
	EvaluateAllSurfaces();
}

void USS_PlanarCaptureManager::UnregisterSurface(ABP_PlanarCaptureActor* Surface)
{
	if (!Surface)
	{
		return;
	}

	RegisteredSurfaces.RemoveAll([Surface](const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry)
	{
		return Entry.Get() == Surface;
	});

	UE_LOG(LogTemp, Log, TEXT("SS_PlanarCaptureManager: Unregistered surface '%s'. Total: %d"),
		*Surface->SurfaceDisplayName, RegisteredSurfaces.Num());

	OnSurfaceUnregistered.Broadcast(Surface, RegisteredSurfaces.Num());
}

TArray<ABP_PlanarCaptureActor*> USS_PlanarCaptureManager::GetRegisteredSurfaces() const
{
	TArray<ABP_PlanarCaptureActor*> Result;
	for (const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry : RegisteredSurfaces)
	{
		if (Entry.IsValid())
		{
			Result.Add(Entry.Get());
		}
	}
	return Result;
}

// ========================================================================
// Quality Budget Management
// ========================================================================

void USS_PlanarCaptureManager::ForceAllSurfacesToTier(EPlanarCaptureQualityTier Tier)
{
	ForceTierOverride = Tier;

	for (TWeakObjectPtr<ABP_PlanarCaptureActor>& SurfaceWeak : RegisteredSurfaces)
	{
		if (ABP_PlanarCaptureActor* Surface = SurfaceWeak.Get())
		{
			if (UBPC_PlanarCapture* Capture = Surface->CaptureComponent)
			{
				Capture->ApplyQualityTier(Tier);
			}
		}
	}
}

void USS_PlanarCaptureManager::ReleaseForceTier()
{
	ForceTierOverride.Reset();
	EvaluateAllSurfaces();
}

// ========================================================================
// Render Target Pool
// ========================================================================

UTextureRenderTarget2D* USS_PlanarCaptureManager::RequestRenderTarget(int32 Size)
{
	// Check pool for an available RT of the right size
	for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
	{
		if (!Entry.bInUse && Entry.CurrentSize == Size && Entry.RenderTarget)
		{
			Entry.bInUse = true;
			return Entry.RenderTarget;
		}
	}

	// Create a new one
	return CreateRenderTarget(Size);
}

void USS_PlanarCaptureManager::ReleaseRenderTarget(UTextureRenderTarget2D* RenderTarget)
{
	if (!RenderTarget)
	{
		return;
	}

	// Find the entry and mark as free
	for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
	{
		if (Entry.RenderTarget == RenderTarget)
		{
			Entry.bInUse = false;
			Entry.OwningSurface.Reset();
			return;
		}
	}

	// Not in pool — add it
	FPlanarCaptureRenderTargetEntry NewEntry;
	NewEntry.RenderTarget = RenderTarget;
	NewEntry.CurrentSize = RenderTarget->SizeX;
	NewEntry.bInUse = false;
	RenderTargetPool.Add(NewEntry);
}

float USS_PlanarCaptureManager::GetPoolMemoryUsageMB() const
{
	float TotalBytes = 0.0f;
	for (const FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
	{
		if (Entry.RenderTarget)
		{
			// RGBA8 = 4 bytes per pixel
			TotalBytes += static_cast<float>(Entry.CurrentSize * Entry.CurrentSize * 4);
		}
	}
	return TotalBytes / (1024.0f * 1024.0f);
}

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

ABP_PlanarCaptureActor* USS_PlanarCaptureManager::GetNearestSurfaceOfMode(
	EPlanarCaptureMode Mode, FVector WorldLocation, float MaxDistance) const
{
	ABP_PlanarCaptureActor* Nearest = nullptr;
	float NearestDistSq = (MaxDistance > 0.0f) ? (MaxDistance * MaxDistance) : FLT_MAX;

	for (const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry : RegisteredSurfaces)
	{
		if (ABP_PlanarCaptureActor* Surface = Entry.Get())
		{
			if (!Surface->CaptureComponent || Surface->CaptureComponent->CaptureMode != Mode)
			{
				continue;
			}

			const float DistSq = FVector::DistSquared(Surface->GetActorLocation(), WorldLocation);
			if (DistSq < NearestDistSq)
			{
				NearestDistSq = DistSq;
				Nearest = Surface;
			}
		}
	}

	return Nearest;
}

// ========================================================================
// Internal Methods
// ========================================================================

void USS_PlanarCaptureManager::EvaluateAllSurfaces()
{
	// Reset tier counts
	TierAssignmentCounts.Empty();

	// Ensure registered surfaces are still valid
	RegisteredSurfaces.RemoveAll([](const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry)
	{
		return !Entry.IsValid();
	});

	// If force tier override is active, apply it to all
	if (ForceTierOverride.IsSet())
	{
		ForceAllSurfacesToTier(ForceTierOverride.GetValue());
		return;
	}

	// Score and assign tiers
	TArray<TPair<ABP_PlanarCaptureActor*, float>> ScoredSurfaces;

	for (TWeakObjectPtr<ABP_PlanarCaptureActor>& SurfaceWeak : RegisteredSurfaces)
	{
		if (ABP_PlanarCaptureActor* Surface = SurfaceWeak.Get())
		{
			if (!Surface->bIsActive || !Surface->CaptureComponent)
			{
				continue;
			}

			UBPC_PlanarCapture* Capture = Surface->CaptureComponent;
			FPlanarCaptureScore Score = Capture->GetCurrentScore();
			ScoredSurfaces.Add(TPair<ABP_PlanarCaptureActor*, float>(Surface, Score.CompositeScore));
		}
	}

	// Sort by score descending (highest score first)
	ScoredSurfaces.Sort([](const TPair<ABP_PlanarCaptureActor*, float>& A,
		const TPair<ABP_PlanarCaptureActor*, float>& B)
	{
		return A.Value > B.Value;
	});

	// Assign tiers within budget
	for (const auto& Pair : ScoredSurfaces)
	{
		ABP_PlanarCaptureActor* Surface = Pair.Key;
		float Score = Pair.Value;

		if (!Surface->CaptureComponent)
		{
			continue;
		}

		EPlanarCaptureQualityTier AssignedTier = ScoreAndAssignTier(Surface->CaptureComponent);

		// Apply tier
		Surface->CaptureComponent->ApplyQualityTier(AssignedTier);
	}
}

EPlanarCaptureQualityTier USS_PlanarCaptureManager::ScoreAndAssignTier(UBPC_PlanarCapture* Capture)
{
	if (!Capture)
	{
		return EPlanarCaptureQualityTier::Off;
	}

	const FPlanarCaptureScore Score = Capture->GetCurrentScore();

	// If not visible, off
	if (!Score.bInFrustum && Score.CompositeScore < 0.01f)
	{
		return EPlanarCaptureQualityTier::Off;
	}

	// If too far, off
	if (Score.DistanceToViewer > MaxCaptureDistance)
	{
		return EPlanarCaptureQualityTier::Off;
	}

	// Determine natural tier based on composite score
	EPlanarCaptureQualityTier NaturalTier;

	if (Score.CompositeScore >= 0.8f)
	{
		NaturalTier = EPlanarCaptureQualityTier::Hero;
	}
	else if (Score.CompositeScore >= 0.5f)
	{
		NaturalTier = EPlanarCaptureQualityTier::High;
	}
	else if (Score.CompositeScore >= 0.2f)
	{
		NaturalTier = EPlanarCaptureQualityTier::Medium;
	}
	else
	{
		NaturalTier = EPlanarCaptureQualityTier::Low;
	}

	// Apply global quality cap
	if (static_cast<int32>(NaturalTier) > static_cast<int32>(GlobalQualityCap))
	{
		NaturalTier = GlobalQualityCap;
	}

	// Check budget limits
	const int32 CurrentCount = TierAssignmentCounts.FindRef(NaturalTier);

	switch (NaturalTier)
	{
	case EPlanarCaptureQualityTier::Hero:
		if (CurrentCount >= MaxHeroSurfaces)
		{
			NaturalTier = EPlanarCaptureQualityTier::High;
		}
		break;
	case EPlanarCaptureQualityTier::High:
		if (CurrentCount >= MaxHighSurfaces)
		{
			NaturalTier = EPlanarCaptureQualityTier::Medium;
		}
		break;
	case EPlanarCaptureQualityTier::Medium:
		if (CurrentCount >= MaxMediumSurfaces)
		{
			NaturalTier = EPlanarCaptureQualityTier::Low;
		}
		break;
	default:
		break;
	}

	// Increment the assigned tier count
	TierAssignmentCounts.FindOrAdd(NaturalTier)++;

	return NaturalTier;
}

void USS_PlanarCaptureManager::EnforceBudgetLimits()
{
	// Additional enforcement for total memory budget
	float TotalMemoryMB = GetPoolMemoryUsageMB();

	if (TotalMemoryMB > MaxTotalRenderTargetMemoryMB)
	{
		UE_LOG(LogTemp, Warning, TEXT("SS_PlanarCaptureManager: Render target memory budget exceeded (%.2f MB / %.2f MB)"),
			TotalMemoryMB, MaxTotalRenderTargetMemoryMB);

		// If over budget, demote lowest-priority surfaces
		// Future: implement more sophisticated memory budget enforcement
	}
}

UTextureRenderTarget2D* USS_PlanarCaptureManager::CreateRenderTarget(int32 Size)
{
	UTextureRenderTarget2D* RT = NewObject<UTextureRenderTarget2D>(this);
	if (!RT)
	{
		return nullptr;
	}

	RT->InitCustomFormat(Size, Size, PF_B8G8R8A8, false);
	RT->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RGBA8;
	RT->bGPUSharedFlag = false;
	RT->ClearColor = FLinearColor::Black;
	RT->UpdateResourceImmediate(true);

	FPlanarCaptureRenderTargetEntry Entry;
	Entry.RenderTarget = RT;
	Entry.CurrentSize = Size;
	Entry.bInUse = true;
	RenderTargetPool.Add(Entry);

	return RT;
}

UTextureRenderTarget2D* USS_PlanarCaptureManager::GetOrCreateRenderTarget(int32 Size)
{
	// Check pool first
	for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
	{
		if (!Entry.bInUse && Entry.CurrentSize == Size && Entry.RenderTarget)
		{
			Entry.bInUse = true;
			return Entry.RenderTarget;
		}
	}

	return CreateRenderTarget(Size);
}
