// Fill out your copyright notice in the Description page of Project Settings.


#include "Subsystem/InteractionSubsystem.h"
#include "EngineUtils.h"
#include "InteractionSettings.h"
#include "GameFramework/Character.h"
#include "Interface/InteractableActorInterface.h"

void UInteractionSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);
	
	if (GetWorld())
	{
		GetWorld()->AddOnActorSpawnedHandler(FOnActorSpawned::FDelegate::CreateUObject(this, &UInteractionSubsystem::RegisterInteractableActor));
		GetWorld()->AddOnActorDestroyedHandler(FOnActorDestroyed::FDelegate::CreateUObject(this, &UInteractionSubsystem::UnregisterInteractableActor));
	}
}

void UInteractionSubsystem::Deinitialize()
{
	InteractableActorList.Empty();
	
	if (UInteractionSubsystem* IS = GetWorld()->GetSubsystem<UInteractionSubsystem>())
	{
		if (IS->CurrentlyInteractableObject.IsValid())
		{
			IInteractableActorInterface::Execute_ToggleInteractableUI(IS->CurrentlyInteractableObject.Get(), false);
		}
		IS->CurrentlyInteractableObject = nullptr;
	}

	Super::Deinitialize();
}

bool UInteractionSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
	if (!Super::ShouldCreateSubsystem(Outer)) return false;
	
	if (!Outer || !Outer->GetWorld()) return false;
	
	UWorld* World = Outer->GetWorld();
	return World->WorldType == EWorldType::Type::Game || World->WorldType == EWorldType::Type::PIE;
}

void UInteractionSubsystem::OnWorldBeginPlay(UWorld& InWorld)
{
	Super::OnWorldBeginPlay(InWorld);
	
	RescanWorld();
}

void UInteractionSubsystem::Tick(float DeltaTime)
{
	GetClosestInteractable(this);
}

AActor* UInteractionSubsystem::GetClosestInteractable(const UObject* WorldContext)
{
	AActor* Closest = nullptr;
	
	if (!IsValid(WorldContext) || !IsValid(WorldContext->GetWorld()))
	{
		return Closest;
	}
	
	UInteractionSubsystem* IS = WorldContext->GetWorld()->GetSubsystem<UInteractionSubsystem>();
	ensure(IS);
	
	const APlayerController* PC = WorldContext->GetWorld()->GetFirstPlayerController();
	if (!PC) return Closest;
	
	const ACharacter* PlayerCharacter = Cast<ACharacter>(PC->GetPawn());
	if (!PlayerCharacter) return Closest;
	
	const UInteractionSettings* InteractionSettings = GetDefault<UInteractionSettings>();
	ensure(InteractionSettings);
	
	float ClosestDist = 9999999.f;
	for (TWeakObjectPtr<AActor> Actor : IS->InteractableActorList)
	{
		if (!Actor.IsValid())
			continue;
		
		const float Dist = FVector::DistSquared(Actor->GetActorLocation(), PlayerCharacter->GetActorLocation());
		const float MaxInteractionDistance = IInteractableActorInterface::Execute_GetInteractionDistance(Actor.Get());
		
		if (FMath::Pow(MaxInteractionDistance, 2) < Dist || Dist > ClosestDist)
			continue;
				
		FVector2D ScreenLoc;
		if (!PC->ProjectWorldLocationToScreen(Actor->GetActorLocation(), ScreenLoc))
		{
			continue;
		}
		
		if (!IInteractableActorInterface::Execute_IsInteractable(Actor.Get()))
		{
			continue;
		}
		
		ClosestDist = Dist;
		Closest = Actor.Get();
	}
	
	if (IS->CurrentlyInteractableObject != Closest)
	{
		if (IS->CurrentlyInteractableObject.IsValid())
		{
			// UE_LOG(LogTemp, Warning, TEXT("Turning off last interactable: %s"), *IS->CurrentlyInteractableObject.Get()->GetActorNameOrLabel())
			IInteractableActorInterface::Execute_ToggleInteractableUI(IS->CurrentlyInteractableObject.Get(), false);
		}
		if (Closest)
		{
			// UE_LOG(LogTemp, Warning, TEXT("Turning ON interactable: %s"), *Closest->GetActorNameOrLabel())
			IInteractableActorInterface::Execute_ToggleInteractableUI(Closest, true);
		}
		IS->CurrentlyInteractableObject = Closest;
	}
	
	return Closest;
}

void UInteractionSubsystem::UpdateInteractableText(AActor* Interactable)
{
	if (!Interactable) return;
	
	UInteractionSubsystem* InteractionSubsystem = Interactable->GetWorld()->GetSubsystem<UInteractionSubsystem>();
	if (!InteractionSubsystem) return;
	
	if (InteractionSubsystem->CurrentlyInteractableObject != Interactable) return;
	
	IInteractableActorInterface::Execute_ToggleInteractableUI(InteractionSubsystem->CurrentlyInteractableObject.Get(), true);
}

void UInteractionSubsystem::RescanWorld()
{
	UWorld* World = GetWorld();
	int32 Count = 0;
	
	if (!World)
		return;
	
	// Prune stuff
	InteractableActorList.RemoveAllSwap(
		[](TWeakObjectPtr<AActor> Actor)
		{
			return !Actor.IsValid();
		}
	);
	
	for (TActorIterator<AActor> It(World); It; ++It)
	{
		Count++;
		AActor* Actor = *It;
		if (IsValid(Actor) && Actor->Implements<UInteractableActorInterface>())
		{
			RegisterInteractableActor(Actor);
		}
	}
}

void UInteractionSubsystem::RegisterInteractableActor(AActor* InteractableActor)
{
	if (IsValid(InteractableActor) && InteractableActor->Implements<UInteractableActorInterface>())
	{
		InteractableActorList.Add(InteractableActor);
	}
}

void UInteractionSubsystem::UnregisterInteractableActor(AActor* InteractableActor)
{
	InteractableActorList.RemoveAllSwap(
		[InteractableActor](TWeakObjectPtr<AActor> Actor)
		{
			return !Actor.IsValid() || Actor.Get() == InteractableActor;
		}
	);
}
