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


#include "InteractionPromptWidget.h"

#include "Components/TextBlock.h"
#include "EnhancedInputSubsystems.h"
#include "Engine/LocalPlayer.h"
#include "FpsPlayer.h"
#include "GameFramework/PlayerController.h"
#include "InteractionComponent.h"

#define LOCTEXT_NAMESPACE "InteractionPrompt"

UInteractionPromptWidget::UInteractionPromptWidget(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	PromptFormat = LOCTEXT("PromptFormat", "[{0}]  {1}");
}

void UInteractionPromptWidget::NativeConstruct()
{
	Super::NativeConstruct();

	// NativeConstruct est l'equivalent de BeginPlay pour un widget : il est
	// appele une fois que le widget est ajoute au viewport.
	BindToOwningPawn();
}

void UInteractionPromptWidget::BindToOwningPawn()
{
	const APlayerController* OwningController = GetOwningPlayer();
	const AFpsPlayer* Player = OwningController ? Cast<AFpsPlayer>(OwningController->GetPawn()) : nullptr;
	UInteractionComponent* NewComponent = Player ? Player->GetInteractionComponent() : nullptr;

	if (BoundComponent.Get() == NewComponent)
	{
		return;
	}

	if (BoundComponent.IsValid())
	{
		BoundComponent->OnPromptChanged.RemoveDynamic(this, &UInteractionPromptWidget::HandlePromptChanged);
	}

	BoundComponent = NewComponent;

	if (BoundComponent.IsValid())
	{
		// Abonnement au delegue : le widget ne se reveille que quand le texte
		// change vraiment. Pas de Tick, pas de polling.
		BoundComponent->OnPromptChanged.AddDynamic(this, &UInteractionPromptWidget::HandlePromptChanged);
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("InteractionPromptWidget : le pawn possede n'a pas de UInteractionComponent, le prompt restera cache."));
	}

	Refresh();
}

void UInteractionPromptWidget::NativeDestruct()
{
	// Desabonnement systematique. Sans ca, le delegue garderait une reference
	// vers un widget detruit -- typiquement au changement de niveau.
	if (BoundComponent.IsValid())
	{
		BoundComponent->OnPromptChanged.RemoveDynamic(this, &UInteractionPromptWidget::HandlePromptChanged);
	}
	BoundComponent.Reset();

	Super::NativeDestruct();
}

void UInteractionPromptWidget::HandlePromptChanged(const FText& NewPrompt)
{
	Refresh();
}

void UInteractionPromptWidget::Refresh()
{
	const FText Prompt = BoundComponent.IsValid() ? BoundComponent->GetFocusedPrompt() : FText::GetEmpty();

	if (Prompt.IsEmpty())
	{
		// Collapsed et non Hidden : Hidden garde la place reservee dans la
		// mise en page, Collapsed la libere completement.
		SetVisibility(ESlateVisibility::Collapsed);
		return;
	}

	if (PromptText)
	{
		// Un prompt non vide est toujours une action realisable : les cibles ne
		// renvoient jamais de texte purement informatif, donc la touche a
		// toujours sa place devant.
		PromptText->SetText(FText::Format(PromptFormat, GetInteractKeyDisplayName(), Prompt));
	}

	// HitTestInvisible : le prompt s'affiche mais ne capture pas la souris,
	// sinon il volerait les clics destines au jeu.
	SetVisibility(ESlateVisibility::HitTestInvisible);
}

FText UInteractionPromptWidget::GetInteractKeyDisplayName() const
{
	const APlayerController* OwningController = GetOwningPlayer();
	const AFpsPlayer* Player = OwningController ? Cast<AFpsPlayer>(OwningController->GetPawn()) : nullptr;
	if (!Player)
	{
		return FText::GetEmpty();
	}

	const UInputAction* Action = Player->GetInteractAction();
	if (!Action)
	{
		return FText::GetEmpty();
	}

	// On demande au sous-systeme Enhanced Input quelle touche est REELLEMENT
	// mappee sur l'action. Si tu changes E en F dans IMC_Default, ou si le
	// joueur remappe ses touches un jour, le texte suit tout seul.
	if (const UEnhancedInputLocalPlayerSubsystem* Subsystem =
		ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(OwningController->GetLocalPlayer()))
	{
		const TArray<FKey> Keys = Subsystem->QueryKeysMappedToAction(Action);
		if (Keys.Num() > 0)
		{
			return Keys[0].GetDisplayName(/*bLongDisplayName=*/false);
		}
	}

	return FText::GetEmpty();
}

#undef LOCTEXT_NAMESPACE
