// Copyright ExByte Studios. Item Interaction Ecosystem.

#include "InteractionTraceComponent.h"
#include "InteractableComponent.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
#include "Engine/World.h"

UInteractionTraceComponent::UInteractionTraceComponent()
{
	PrimaryComponentTick.bCanEverTick = true;
	PrimaryComponentTick.TickInterval = 0.1f; // 10 Hz is plenty for prompts
	SetIsReplicatedByDefault(false);          // purely local
}

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

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

void UInteractionTraceComponent::TickTrace()
{
	FVector ViewLoc;
	FRotator ViewRot;
	if (!GetViewPoint(ViewLoc, ViewRot))
	{
		SetTarget(nullptr, nullptr);
		return;
	}

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

	FCollisionQueryParams Params(SCENE_QUERY_STAT(InteractionTrace), false, GetOwner());
	Params.bTraceComplex = false;

	FHitResult Hit;
	const bool bHit = GetWorld()->SweepSingleByChannel(
		Hit, ViewLoc, End, FQuat::Identity, TraceChannel,
		FCollisionShape::MakeSphere(TraceRadius), Params);

	AActor* HitActor = bHit ? Hit.GetActor() : nullptr;
	UInteractableComponent* Interactable = HitActor ? HitActor->FindComponentByClass<UInteractableComponent>() : nullptr;

	if (Interactable)
	{
		// Distance gate against the component's own reach.
		const float DistSq = FVector::DistSquared(ViewLoc, Hit.ImpactPoint);
		if (DistSq > FMath::Square(Interactable->InteractionDistance))
		{
			Interactable = nullptr;
			HitActor = nullptr;
		}
	}
	else
	{
		HitActor = nullptr;
	}

	LastHitLocation = Hit.ImpactPoint;
	LastHitNormal = Hit.ImpactNormal;
	SetTarget(HitActor, Interactable);
}

void UInteractionTraceComponent::SetTarget(AActor* NewTarget, UInteractableComponent* NewInteractable)
{
	if (CurrentTarget == NewTarget && CurrentInteractable == NewInteractable)
	{
		return;
	}
	CurrentTarget = NewTarget;
	CurrentInteractable = NewInteractable;
	OnTargetChanged.Broadcast(NewTarget, NewInteractable);
}
