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

#include "Capture/BPC_PlanarCapture.h"
#include "Capture/BP_PlanarCaptureActor.h"
#include "Capture/SS_PlanarCaptureManager.h"
#include "Capture/PlanarCaptureCameraUtils.h"
#include "Components/SceneCaptureComponent2D.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/World.h"
#include "GameFramework/PlayerController.h"
#include "Materials/MaterialParameterCollection.h"
#include "Materials/MaterialParameterCollectionInstance.h"
#include "Kismet/GameplayStatics.h"

UBPC_PlanarCapture::UBPC_PlanarCapture()
{
	PrimaryComponentTick.bCanEverTick = true;
	PrimaryComponentTick.bStartWithTickEnabled = true;
	PrimaryComponentTick.TickInterval = 0.0f; // We throttle internally via TimeSinceLastCapture

	// Initialize default quality profiles
	QualityProfiles.SetNum(4);

	// Low — 256px, 4fps
	QualityProfiles[0].RenderTargetSize = 256;
	QualityProfiles[0].CaptureInterval = 0.25f;
	QualityProfiles[0].bEnableShadows = false;
	QualityProfiles[0].bEnablePostProcess = false;
	QualityProfiles[0].bEnableFog = false;
	QualityProfiles[0].bEnableBloom = false;
	QualityProfiles[0].bEnableAO = false;
	QualityProfiles[0].bEnableLumen = false;
	QualityProfiles[0].bEnableMotionBlur = false;
	QualityProfiles[0].bEnableClipPlane = false;

	// Medium — 512px, 15fps
	QualityProfiles[1].RenderTargetSize = 512;
	QualityProfiles[1].CaptureInterval = 0.0667f;
	QualityProfiles[1].bEnableShadows = true;
	QualityProfiles[1].bEnablePostProcess = false;
	QualityProfiles[1].bEnableFog = false;
	QualityProfiles[1].bEnableBloom = false;
	QualityProfiles[1].bEnableAO = false;
	QualityProfiles[1].bEnableLumen = false;
	QualityProfiles[1].bEnableMotionBlur = false;
	QualityProfiles[1].bEnableClipPlane = true;

	// High — 1024px, 30fps
	QualityProfiles[2].RenderTargetSize = 1024;
	QualityProfiles[2].CaptureInterval = 0.0333f;
	QualityProfiles[2].bEnableShadows = true;
	QualityProfiles[2].bEnablePostProcess = true;
	QualityProfiles[2].bEnableFog = true;
	QualityProfiles[2].bEnableBloom = false;
	QualityProfiles[2].bEnableAO = true;
	QualityProfiles[2].bEnableLumen = true;
	QualityProfiles[2].bEnableMotionBlur = false;
	QualityProfiles[2].bEnableClipPlane = true;

	// Hero — 2048px, 60fps
	QualityProfiles[3].RenderTargetSize = 2048;
	QualityProfiles[3].CaptureInterval = 0.0167f;
	QualityProfiles[3].bEnableShadows = true;
	QualityProfiles[3].bEnablePostProcess = true;
	QualityProfiles[3].bEnableFog = true;
	QualityProfiles[3].bEnableBloom = true;
	QualityProfiles[3].bEnableAO = true;
	QualityProfiles[3].bEnableLumen = true;
	QualityProfiles[3].bEnableMotionBlur = true;
	QualityProfiles[3].bEnableClipPlane = true;
}

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

	CachedOwningActor = Cast<ABP_PlanarCaptureActor>(GetOwner());
	if (!CachedOwningActor)
	{
		UE_LOG(LogTemp, Warning, TEXT("BPC_PlanarCapture: Owner is not a BP_PlanarCaptureActor! Capture disabled."));
		SetComponentTickEnabled(false);
		return;
	}

	// Cache manager reference
	if (UWorld* World = GetWorld())
	{
		CachedManager = World->GetSubsystem<USS_PlanarCaptureManager>();
	}

	ResolveSoftReferences();
}

void UBPC_PlanarCapture::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	ShutdownCapture();
	Super::EndPlay(EndPlayReason);
}

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

	if (!bIsCapturing || !SceneCapture || CurrentQualityTier == EPlanarCaptureQualityTier::Off)
	{
		return;
	}

	TimeSinceLastCapture += DeltaTime;

	if (TimeSinceLastCapture >= ActiveProfile.CaptureInterval)
	{
		TimeSinceLastCapture = 0.0f;

		// Get viewer camera for transform computation
		APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
		if (!PC || !PC->PlayerCameraManager)
		{
			return;
		}

		const FTransform ViewerTransform = FTransform(
			PC->PlayerCameraManager->GetCameraRotation(),
			PC->PlayerCameraManager->GetCameraLocation()
		);

		// Compute capture camera position
		const FTransform CaptureTransform = ComputeCaptureCameraTransform(ViewerTransform);
		SceneCapture->SetWorldTransform(CaptureTransform);

		// Capture the scene
		SceneCapture->CaptureScene();

		// Push MPC parameters
		if (CachedOwningActor && CachedOwningActor->SurfaceMPC)
		{
			PushMPCParameters(CachedOwningActor->SurfaceMPC);
		}

		OnCaptureRendered.Broadcast();
	}
}

// ========================================================================
// Public API
// ========================================================================

EPlanarCaptureInitResult UBPC_PlanarCapture::InitializeCapture()
{
	if (!CachedOwningActor)
	{
		return EPlanarCaptureInitResult::InvalidSurfaceMesh;
	}

	if (CurrentQualityTier == EPlanarCaptureQualityTier::Off)
	{
		return EPlanarCaptureInitResult::Success; // Nothing to initialize
	}

	// Request render target from pool
	const int32 ProfileIndex = static_cast<int32>(CurrentQualityTier) - 1; // Skip "Off" (0)
	if (ProfileIndex < 0 || ProfileIndex >= QualityProfiles.Num())
	{
		return EPlanarCaptureInitResult::NoRenderTargetPool;
	}

	ActiveProfile = QualityProfiles[ProfileIndex];

	if (CachedManager)
	{
		CaptureRenderTarget = CachedManager->RequestRenderTarget(ActiveProfile.RenderTargetSize);
	}

	if (!CaptureRenderTarget)
	{
		UE_LOG(LogTemp, Error, TEXT("BPC_PlanarCapture: Failed to allocate render target (%dx%d)"),
			ActiveProfile.RenderTargetSize, ActiveProfile.RenderTargetSize);
		return EPlanarCaptureInitResult::NoRenderTargetPool;
	}

	CreateSceneCaptureComponent();

	bIsCapturing = true;
	OnCaptureInitialized.Broadcast(EPlanarCaptureInitResult::Success);
	return EPlanarCaptureInitResult::Success;
}

void UBPC_PlanarCapture::ShutdownCapture()
{
	bIsCapturing = false;

	if (SceneCapture)
	{
		SceneCapture->DestroyComponent();
		SceneCapture = nullptr;
	}

	if (CaptureRenderTarget && CachedManager)
	{
		CachedManager->ReleaseRenderTarget(CaptureRenderTarget);
		CaptureRenderTarget = nullptr;
	}

	// Clean up frame ring buffer
	for (UTextureRenderTarget2D* RT : FrameRingBuffer)
	{
		if (RT && CachedManager)
		{
			CachedManager->ReleaseRenderTarget(RT);
		}
	}
	FrameRingBuffer.Empty();
}

void UBPC_PlanarCapture::ApplyQualityTier(EPlanarCaptureQualityTier Tier)
{
	const EPlanarCaptureQualityTier OldTier = CurrentQualityTier;
	CurrentQualityTier = Tier;

	if (Tier == EPlanarCaptureQualityTier::Off)
	{
		ShutdownCapture();
		OnCaptureQualityChanged.Broadcast(OldTier, Tier);
		return;
	}

	// If transitioning from Off to active, initialize
	if (OldTier == EPlanarCaptureQualityTier::Off)
	{
		InitializeCapture();
	}
	else
	{
		// Update active profile and resize render target if needed
		const int32 ProfileIndex = static_cast<int32>(Tier) - 1;
		if (ProfileIndex >= 0 && ProfileIndex < QualityProfiles.Num())
		{
			ActiveProfile = QualityProfiles[ProfileIndex];
			ApplyShowFlags();
		}
	}

	OnCaptureQualityChanged.Broadcast(OldTier, Tier);
}

void UBPC_PlanarCapture::CaptureNow()
{
	if (!SceneCapture)
	{
		return;
	}

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

	const FTransform ViewerTransform = FTransform(
		PC->PlayerCameraManager->GetCameraRotation(),
		PC->PlayerCameraManager->GetCameraLocation()
	);

	const FTransform CaptureTransform = ComputeCaptureCameraTransform(ViewerTransform);
	SceneCapture->SetWorldTransform(CaptureTransform);
	SceneCapture->CaptureScene();

	TimeSinceLastCapture = 0.0f;
	OnCaptureRendered.Broadcast();
}

void UBPC_PlanarCapture::ActivateHorrorReflection()
{
	// Save current ShowOnly list
	SavedShowOnlyActors.Empty();
	for (const FPlanarCaptureActorListEntry& Entry : ShowOnlyActors)
	{
		SavedShowOnlyActors.Add(Entry.Actor);
	}

	// Clear and set wrong reflection actor
	ShowOnlyActors.Empty();
	if (WrongReflectionActor.IsValid())
	{
		FPlanarCaptureActorListEntry NewEntry;
		NewEntry.Actor = WrongReflectionActor;
		NewEntry.bActive = true;
		ShowOnlyActors.Add(NewEntry);
	}

	UpdateActorLists();
}

void UBPC_PlanarCapture::DeactivateHorrorReflection()
{
	ShowOnlyActors.Empty();
	for (const TSoftObjectPtr<AActor>& SavedActor : SavedShowOnlyActors)
	{
		FPlanarCaptureActorListEntry NewEntry;
		NewEntry.Actor = SavedActor;
		NewEntry.bActive = true;
		ShowOnlyActors.Add(NewEntry);
	}

	SavedShowOnlyActors.Empty();
	UpdateActorLists();
}

void UBPC_PlanarCapture::PushDelayedFrame()
{
	if (ActiveProfile.DelayedFrameCount <= 0 || !CaptureRenderTarget)
	{
		return;
	}

	// Ensure ring buffer is properly sized
	while (FrameRingBuffer.Num() < ActiveProfile.DelayedFrameCount)
	{
		UTextureRenderTarget2D* NewRT = nullptr;
		if (CachedManager)
		{
			NewRT = CachedManager->RequestRenderTarget(ActiveProfile.RenderTargetSize);
		}
		FrameRingBuffer.Add(NewRT);
	}

	// Copy current render target to ring buffer slot
	RingBufferWriteIndex = (RingBufferWriteIndex + 1) % FrameRingBuffer.Num();

	// Push the oldest frame to the material as the delayed reflection
	if (FrameRingBuffer.IsValidIndex(RingBufferWriteIndex) && FrameRingBuffer[RingBufferWriteIndex])
	{
		// The material samples the delayed frame via the MPC texture parameter
		// This is set in PushMPCParameters
	}
}

void UBPC_PlanarCapture::SetScriptedPriority(float Priority)
{
	ScriptedPriorityOverride = FMath::Clamp(Priority, 0.0f, 1.0f);
}

void UBPC_PlanarCapture::PushMPCParameters(UMaterialParameterCollection* MPC)
{
	if (!MPC)
	{
		return;
	}

	UMaterialParameterCollectionInstance* MPCInstance = GetWorld()->GetParameterCollectionInstance(MPC);
	if (!MPCInstance)
	{
		return;
	}

	// Push standard parameters
	// These are used by M_CaptureSurface_Master to drive steam, dirt, horror effects
	MPCInstance->SetScalarParameterValue(FName("SteamIntensity"), 0.0f);
	MPCInstance->SetScalarParameterValue(FName("DirtOpacity"), 0.0f);
	MPCInstance->SetScalarParameterValue(FName("CondensationFlow"), 0.0f);
	MPCInstance->SetScalarParameterValue(FName("DistortionAmplitude"), 0.0f);
	MPCInstance->SetScalarParameterValue(FName("MirrorDarkness"), 0.0f);
	MPCInstance->SetScalarParameterValue(FName("WrongReflectionBlend"), 0.0f);
	MPCInstance->SetScalarParameterValue(FName("TextRevealProgress"), 0.0f);
	MPCInstance->SetScalarParameterValue(FName("SteamEmissiveIntensity"), 0.0f);
	MPCInstance->SetScalarParameterValue(FName("DelayedReflectionBlend"), 0.0f);
	MPCInstance->SetScalarParameterValue(FName("SurfaceAge"), 0.0f);
}

// ========================================================================
// Compute
// ========================================================================

FTransform UBPC_PlanarCapture::ComputeCaptureCameraTransform(const FTransform& ViewerCameraTransform) const
{
	switch (CaptureMode)
	{
	case EPlanarCaptureMode::Mirror:
	case EPlanarCaptureMode::HorrorMirror:
	{
		const FTransform SurfaceTransform = CachedOwningActor
			? CachedOwningActor->GetActorTransform()
			: FTransform::Identity;
		return UPlanarCaptureCameraUtils::ComputeMirroredTransform(ViewerCameraTransform, SurfaceTransform);
	}

	case EPlanarCaptureMode::Portal:
	case EPlanarCaptureMode::HorrorPortal:
	{
		if (LinkedTargetSurface.IsValid())
		{
			const FTransform SourceTransform = CachedOwningActor
				? CachedOwningActor->GetActorTransform()
				: FTransform::Identity;
			const FTransform TargetTransform = LinkedTargetSurface->GetActorTransform();
			return UPlanarCaptureCameraUtils::ComputePortalTransform(
				ViewerCameraTransform, SourceTransform, TargetTransform);
		}
		return ViewerCameraTransform;
	}

	case EPlanarCaptureMode::Monitor:
	{
		if (FixedCameraActor.IsValid())
		{
			return FixedCameraActor->GetActorTransform();
		}
		return ViewerCameraTransform;
	}

	case EPlanarCaptureMode::FakeWindow:
	{
		// Fake windows use a fixed offset from the surface
		const FTransform SurfaceTransform = CachedOwningActor
			? CachedOwningActor->GetActorTransform()
			: FTransform::Identity;
		const FVector SurfaceNormal = SurfaceTransform.GetUnitAxis(EAxis::Z);
		return FTransform(SurfaceTransform.GetRotation(),
			SurfaceTransform.GetLocation() + SurfaceNormal * 200.0f);
	}

	default:
		return ViewerCameraTransform;
	}
}

FPlanarCaptureScore UBPC_PlanarCapture::GetCurrentScore() const
{
	FPlanarCaptureScore Score;

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

	const FVector ViewerPos = PC->PlayerCameraManager->GetCameraLocation();
	const FTransform ViewerTransform = FTransform(
		PC->PlayerCameraManager->GetCameraRotation(),
		ViewerPos
	);

	const FVector SurfacePos = CachedOwningActor->GetActorLocation();
	const FVector SurfaceNormal = CachedOwningActor->GetActorUpVector();

	Score.DistanceToViewer = FVector::Dist(ViewerPos, SurfacePos);
	Score.FacingAngle = FVector::DotProduct(
		PC->PlayerCameraManager->GetCameraRotation().Vector(),
		SurfaceNormal
	);

	// Compute screen coverage using bounding box
	const FBox SurfaceBounds = CachedOwningActor->GetComponentsBoundingBox();
	Score.ScreenCoverage = UPlanarCaptureCameraUtils::ComputeScreenCoverage(
		SurfaceBounds, ViewerTransform, CaptureFOV, 1920, 1080);

	Score.bInFrustum = UPlanarCaptureCameraUtils::IsSurfaceVisibleToViewer(
		SurfaceBounds, ViewerTransform, CaptureFOV, 1.777f, 10.0f, MaxViewDistance);

	Score.ScriptedPriority = ScriptedPriorityOverride;

	Score.CompositeScore = UPlanarCaptureCameraUtils::ComputeCompositeScore(
		Score.ScreenCoverage, Score.FacingAngle, Score.DistanceToViewer,
		CachedManager ? CachedManager->MaxCaptureDistance : 10000.0f,
		Score.ScriptedPriority);

	return Score;
}

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

void UBPC_PlanarCapture::CreateSceneCaptureComponent()
{
	if (!GetOwner())
	{
		return;
	}

	SceneCapture = NewObject<USceneCaptureComponent2D>(GetOwner(), USceneCaptureComponent2D::StaticClass());
	if (!SceneCapture)
	{
		return;
	}

	SceneCapture->RegisterComponent();
	SceneCapture->AttachToComponent(GetOwner()->GetRootComponent(),
		FAttachmentTransformRules::SnapToTargetNotIncludingScale);

	SceneCapture->TextureTarget = CaptureRenderTarget;
	SceneCapture->FOVAngle = CaptureFOV;
	SceneCapture->bCaptureEveryFrame = false; // We control capture timing
	SceneCapture->bCaptureOnMovement = false;

	// Configure for planar surface capture
	SceneCapture->ProjectionType = ECameraProjectionMode::Perspective;
	SceneCapture->PrimitiveRenderMode = ESceneCapturePrimitiveRenderMode::PRM_UseShowOnlyList;

	ApplyShowFlags();
	UpdateActorLists();
}

void UBPC_PlanarCapture::ApplyShowFlags()
{
	if (!SceneCapture)
	{
		return;
	}

	// Apply show flags based on active quality profile
	// UE5.7: FEngineShowFlags members are set directly, not via setter methods
	if (!ActiveProfile.bEnableShadows)
	{
		SceneCapture->ShowFlags.DynamicShadows = false;
		SceneCapture->ShowFlags.ContactShadows = false;
	}
	if (!ActiveProfile.bEnableFog)
	{
		SceneCapture->ShowFlags.Fog = 0;
	}
	if (!ActiveProfile.bEnableBloom)
	{
		SceneCapture->ShowFlags.Bloom = 0;
	}
	if (!ActiveProfile.bEnableAO)
	{
		SceneCapture->ShowFlags.AmbientOcclusion = 0;
		SceneCapture->ShowFlags.ScreenSpaceAO = 0;
	}
	if (!ActiveProfile.bEnableMotionBlur)
	{
		SceneCapture->ShowFlags.MotionBlur = 0;
	}

	// Lumen is controlled via post-process settings on the capture component
	SceneCapture->PostProcessSettings.bOverride_DynamicGlobalIlluminationMethod = true;
	SceneCapture->PostProcessSettings.DynamicGlobalIlluminationMethod = ActiveProfile.bEnableLumen
		? EDynamicGlobalIlluminationMethod::Lumen
		: EDynamicGlobalIlluminationMethod::None;

	// Post-process toggle
	SceneCapture->PostProcessSettings.bOverride_BloomIntensity = !ActiveProfile.bEnablePostProcess;
	SceneCapture->PostProcessBlendWeight = ActiveProfile.bEnablePostProcess ? 1.0f : 0.0f;

	// Disable Lumen reflections on the capture itself to prevent double-rendering
	SceneCapture->PostProcessSettings.bOverride_ReflectionMethod = true;
	SceneCapture->PostProcessSettings.ReflectionMethod = EReflectionMethod::None;
}

void UBPC_PlanarCapture::UpdateActorLists()
{
	if (!SceneCapture)
	{
		return;
	}

	SceneCapture->ShowOnlyActors.Empty();
	SceneCapture->HiddenActors.Empty();

	for (const FPlanarCaptureActorListEntry& Entry : ShowOnlyActors)
	{
		if (Entry.bActive && Entry.Actor.IsValid())
		{
			SceneCapture->ShowOnlyActors.Add(Entry.Actor.Get());
		}
	}

	for (const FPlanarCaptureActorListEntry& Entry : HiddenActors)
	{
		if (Entry.bActive && Entry.Actor.IsValid())
		{
			SceneCapture->HiddenActors.Add(Entry.Actor.Get());
		}
	}
}

void UBPC_PlanarCapture::ResolveSoftReferences()
{
	if (SurfaceMeshComponent.IsValid())
	{
		// Surface mesh is resolved — ready for capture
	}
}

FPlane UBPC_PlanarCapture::GetSurfacePlane() const
{
	if (!CachedOwningActor)
	{
		return FPlane(FVector::ZeroVector, FVector::UpVector);
	}

	const FVector SurfaceLocation = CachedOwningActor->GetActorLocation();
	const FVector SurfaceNormal = CachedOwningActor->GetActorUpVector();

	return FPlane(SurfaceLocation, SurfaceNormal);
}
