// Copyright ExByte Studios. Item Interaction Ecosystem.

#include "PlacementPreviewComponent.h"
#include "ItemPlacementLog.h"
#include "ItemDatabaseSubsystem.h"
#include "ItemDefinitionRow.h"
#include "InventoryComponent.h"
#include "PickupItemActor.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"

UPlacementPreviewComponent::UPlacementPreviewComponent()
{
	PrimaryComponentTick.bCanEverTick = true;
	PrimaryComponentTick.bStartWithTickEnabled = false;
	SetIsReplicatedByDefault(true); // for the server confirm RPC
}

bool UPlacementPreviewComponent::GetViewPoint(FVector& OutLoc, FRotator& OutRot) const
{
	const APawn* Pawn = Cast<APawn>(GetOwner());
	const APlayerController* PC = Pawn ? Cast<APlayerController>(Pawn->GetController()) : nullptr;
	if (!PC || !PC->IsLocalController())
	{
		return false;
	}
	PC->GetPlayerViewPoint(OutLoc, OutRot);
	return true;
}

void UPlacementPreviewComponent::StartPlacement(FGuid InstanceId)
{
	APawn* Pawn = Cast<APawn>(GetOwner());
	UInventoryComponent* Inv = Pawn ? Pawn->FindComponentByClass<UInventoryComponent>() : nullptr;
	if (!Inv)
	{
		return;
	}
	FItemInstanceData Item;
	if (!Inv->FindItem(InstanceId, Item))
	{
		return;
	}

	const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
	const FItemDefinitionRow* Def = DB ? DB->GetItemDefinition(Item.ItemId) : nullptr;
	if (!Def || !Def->bCanPlace)
	{
		return;
	}

	ItemInstanceId = InstanceId;
	ItemId = Item.ItemId;
	bIsPlacing = true;
	PreviewYaw = 0.f;

	// Build the local ghost mesh.
	if (!Ghost)
	{
		Ghost = NewObject<UStaticMeshComponent>(GetOwner(), TEXT("PlacementGhost"));
		Ghost->SetupAttachment(GetOwner()->GetRootComponent());
		Ghost->SetCollisionEnabled(ECollisionEnabled::NoCollision);
		Ghost->SetCastShadow(false);
		Ghost->RegisterComponent();
	}
	if (!Def->WorldMesh.IsNull())
	{
		if (UStaticMesh* M = Def->WorldMesh.LoadSynchronous())
		{
			Ghost->SetStaticMesh(M);
		}
	}
	Ghost->SetVisibility(true);
	SetComponentTickEnabled(true);
}

void UPlacementPreviewComponent::CancelPlacement()
{
	bIsPlacing = false;
	bCurrentValid = false;
	SetComponentTickEnabled(false);
	DestroyGhost();
}

void UPlacementPreviewComponent::RotatePreview(float DeltaYawDegrees)
{
	PreviewYaw = FMath::Fmod(PreviewYaw + DeltaYawDegrees, 360.f);
}

void UPlacementPreviewComponent::ConfirmPlacement()
{
	if (!bIsPlacing || !bCurrentValid)
	{
		return;
	}
	Server_ConfirmPlacement(ItemInstanceId, PreviewTransform);
	CancelPlacement();
}

void UPlacementPreviewComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
	if (!bIsPlacing)
	{
		return;
	}

	FTransform Xf;
	if (ComputePreviewTransform(Xf))
	{
		PreviewTransform = Xf;
		if (Ghost)
		{
			Ghost->SetWorldTransform(Xf);
		}

		const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
		const FItemDefinitionRow* Def = DB ? DB->GetItemDefinition(ItemId) : nullptr;
		FString Reason;
		const bool bValid = Def && ValidatePlacement(this, *Def, Xf, GetOwner(), Reason);
		SetValidity(bValid);
	}
	else
	{
		SetValidity(false);
	}
}

bool UPlacementPreviewComponent::ComputePreviewTransform(FTransform& OutTransform) const
{
	FVector ViewLoc;
	FRotator ViewRot;
	if (!GetViewPoint(ViewLoc, ViewRot))
	{
		return false;
	}

	const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
	const FItemDefinitionRow* Def = DB ? DB->GetItemDefinition(ItemId) : nullptr;
	const float MaxDist = Def ? Def->PlacementRules.MaxDistance : 400.f;

	const FVector Start = ViewLoc;
	const FVector End = ViewLoc + ViewRot.Vector() * MaxDist;

	FCollisionQueryParams Params(SCENE_QUERY_STAT(PlacementTrace), false, GetOwner());
	FHitResult Hit;
	if (!GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, Params))
	{
		return false;
	}

	FRotator Rot(0.f, PreviewYaw, 0.f);
	// Optionally align to surface normal when not requiring a flat surface.
	if (Def && !Def->PlacementRules.bRequiresFlatSurface)
	{
		const FRotator Aligned = FRotationMatrix::MakeFromZX(Hit.ImpactNormal, ViewRot.Vector()).Rotator();
		Rot = FRotator(Aligned.Pitch, PreviewYaw, Aligned.Roll);
	}

	OutTransform = FTransform(Rot, Hit.ImpactPoint);
	return true;
}

bool UPlacementPreviewComponent::ValidatePlacement(const UObject* WorldContext, const FItemDefinitionRow& Def,
	const FTransform& Transform, AActor* Instigator, FString& OutReason)
{
	const FItemPlacementRules& Rules = Def.PlacementRules;
	UWorld* World = GEngine ? GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull) : nullptr;
	if (!World)
	{
		OutReason = TEXT("No world");
		return false;
	}

	// Distance from the instigator.
	if (Instigator)
	{
		const float DistSq = FVector::DistSquared(Instigator->GetActorLocation(), Transform.GetLocation());
		if (DistSq > FMath::Square(Rules.MaxDistance + 100.f))
		{
			OutReason = TEXT("Too far");
			return false;
		}
	}

	// Slope: trace down and measure the surface normal.
	{
		const FVector Loc = Transform.GetLocation();
		FCollisionQueryParams Params(SCENE_QUERY_STAT(PlacementSlope), false, Instigator);
		FHitResult Hit;
		if (World->LineTraceSingleByChannel(Hit, Loc + FVector(0, 0, 50.f), Loc - FVector(0, 0, 100.f), ECC_Visibility, Params))
		{
			const float SlopeDeg = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(Hit.ImpactNormal, FVector::UpVector)));
			if (Rules.bCanPlaceOnGround && SlopeDeg > Rules.MaxSlopeAngle)
			{
				OutReason = TEXT("Surface too steep");
				return false;
			}
		}
		else if (Rules.bRequiresFlatSurface)
		{
			OutReason = TEXT("No surface");
			return false;
		}
	}

	// Collision: overlap a box at the target; any blocking world geometry rejects.
	{
		const FVector Extent(40.f, 40.f, 40.f);
		FCollisionQueryParams Params(SCENE_QUERY_STAT(PlacementOverlap), false, Instigator);
		FCollisionShape Box = FCollisionShape::MakeBox(Extent);
		if (World->OverlapBlockingTestByChannel(Transform.GetLocation() + FVector(0, 0, Extent.Z), FQuat::Identity, ECC_WorldStatic, Box, Params))
		{
			OutReason = TEXT("Blocked");
			return false;
		}
	}

	OutReason.Reset();
	return true;
}

void UPlacementPreviewComponent::Server_ConfirmPlacement_Implementation(FGuid InstanceId, FTransform Transform)
{
	APawn* Pawn = Cast<APawn>(GetOwner());
	UInventoryComponent* Inv = Pawn ? Pawn->FindComponentByClass<UInventoryComponent>() : nullptr;
	if (!Inv)
	{
		return;
	}
	FItemInstanceData Item;
	if (!Inv->FindItem(InstanceId, Item) || Item.Lock.bLocked)
	{
		return;
	}

	const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(this);
	const FItemDefinitionRow* Def = DB ? DB->GetItemDefinition(Item.ItemId) : nullptr;
	if (!Def || !Def->bCanPlace)
	{
		return;
	}

	FString Reason;
	if (!ValidatePlacement(this, *Def, Transform, GetOwner(), Reason))
	{
		UE_LOG(LogItemPlacement, Warning, TEXT("[ServerReject] Placement failed: %s"), *Reason);
		return;
	}

	if (APickupItemActor::SpawnPickupFromInstance(this, Item, Transform))
	{
		Inv->RemoveItem(InstanceId);
		UE_LOG(LogItemPlacement, Log, TEXT("[Placement] placed %s"), *Item.ItemId.ToString());
	}
}

void UPlacementPreviewComponent::SetValidity(bool bNewValid)
{
	if (bCurrentValid != bNewValid)
	{
		bCurrentValid = bNewValid;
		OnPlacementStateChanged.Broadcast(bNewValid);
	}
}

void UPlacementPreviewComponent::DestroyGhost()
{
	if (Ghost)
	{
		Ghost->DestroyComponent();
		Ghost = nullptr;
	}
}

void UPlacementPreviewComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	DestroyGhost();
	Super::EndPlay(EndPlayReason);
}
