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

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Engine/EngineTypes.h"
#include "InteractionComponent.generated.h"

/**
 * Diffuse a chaque changement de cible visee. NewFocus vaut nullptr quand le
 * joueur ne vise plus rien : c'est le signal pour cacher le prompt a l'ecran.
 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInteractionFocusChanged, AActor*, NewFocus, AActor*, OldFocus);

/**
 * Diffuse quand le TEXTE du prompt change, ce qui n'est pas la meme chose qu'un
 * changement de cible : un feu de camp affiche un compte a rebours, et le fait
 * de balayer d'un emplacement a l'autre ne change pas l'acteur vise. S'abonner
 * uniquement a OnFocusChanged laisserait ces deux cas figes a l'ecran.
 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnInteractionPromptChanged, const FText&, NewPrompt);

/**
 * A poser sur le pawn du joueur. Lance regulierement un trace depuis le point
 * de vue de la camera, garde en memoire l'objet IInteractable vise, et declenche
 * l'interaction a la demande.
 *
 * Le composant ne connait aucun type d'objet concret : il ne parle qu'a
 * l'interface. Tu peux ajouter des portes, des coffres ou des PNJ sans jamais
 * revenir toucher a ce fichier.
 */
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class EMBERWILD_API UInteractionComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	UInteractionComponent();

	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

	/**
	 * Declenche l'interaction sur l'objet vise, s'il y en a un.
	 *
	 * Le trace, lui, reste PUREMENT LOCAL : chacun vise chez soi, a sa propre
	 * frequence, sans le moindre aller-retour reseau -- le prompt doit s'afficher
	 * a l'instant ou l'on vise, pas 60 ms plus tard. Seule l'action part au
	 * serveur, qui est le seul a pouvoir modifier le monde.
	 */
	UFUNCTION(BlueprintCallable, Category = "Interaction")
	void TryInteract();

	/** L'acteur actuellement vise, ou nullptr. */
	UFUNCTION(BlueprintPure, Category = "Interaction")
	AActor* GetFocusedActor() const { return FocusedActor.Get(); }

	/**
	 * Texte a afficher a l'ecran, vide si rien n'est vise.
	 *
	 * Renvoie une valeur DEJA calculee : le prompt est reconstruit une fois par
	 * trace, pas une fois par lecture. Un widget peut donc l'interroger sans
	 * jamais declencher le moindre travail.
	 */
	UFUNCTION(BlueprintPure, Category = "Interaction")
	const FText& GetFocusedPrompt() const { return CachedPrompt; }

	/** Branche ton widget la-dessus pour afficher / cacher le prompt. */
	UPROPERTY(BlueprintAssignable, Category = "Interaction")
	FOnInteractionFocusChanged OnFocusChanged;

	/** Le texte affiche a change. C'est a ca qu'un widget de prompt s'abonne. */
	UPROPERTY(BlueprintAssignable, Category = "Interaction")
	FOnInteractionPromptChanged OnPromptChanged;

protected:
	/** Portee du trace en cm. 250 = un peu plus d'un bras et demi. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction", meta = (ClampMin = "1"))
	float InteractionRange = 250.f;

	/**
	 * Rayon de la sphere balayee. Un trace en ligne pure oblige a viser au pixel
	 * pres et rend le jeu penible ; quelques cm de tolerance changent tout.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction", meta = (ClampMin = "0"))
	float TraceRadius = 8.f;

	/**
	 * Intervalle entre deux traces, en secondes. Inutile de tracer 120 fois par
	 * seconde pour un affichage que l'oeil lit a 20 Hz.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction", meta = (ClampMin = "0"))
	float TraceInterval = 0.05f;

	/** Canal de collision teste. Visibility convient dans la majorite des cas. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
	TEnumAsByte<ECollisionChannel> TraceChannel = ECC_Visibility;

	/** Affiche le trace dans le monde, tres utile quand rien ne repond. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
	bool bDrawDebug = false;

	/**
	 * Marge accordee par le serveur quand il verifie la distance annoncee par un
	 * client. 1.0 refuserait des interactions parfaitement legitimes : entre le
	 * moment ou le client vise et celui ou son message arrive, son personnage a
	 * bouge, et la position que le serveur connait de lui a jusqu'a un aller-retour
	 * de retard.
	 *
	 * Ce n'est PAS de l'anti-triche -- coop entre amis, on ne paie pas ce prix --
	 * mais un garde-fou contre un client desynchronise qui ouvrirait un coffre a
	 * l'autre bout de la carte.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction|Reseau", meta = (ClampMin = "1.0"))
	float ServerRangeTolerance = 2.f;

private:
	/**
	 * L'interaction demandee par un client, executee par le serveur.
	 *
	 * Reliable : rater un ramassage parce qu'un paquet s'est perdu serait
	 * incomprehensible pour le joueur. Le debit est nul de toute facon, une
	 * interaction est un evenement rare a l'echelle du reseau.
	 */
	UFUNCTION(Server, Reliable)
	void Server_Interact(AActor* Target);

	/**
	 * L'execution reelle, cote autorite. Revalide la cible : le client peut
	 * l'avoir vue valide alors qu'un autre joueur venait de la ramasser.
	 */
	void PerformInteract(AActor* Target);
	/** Relance le trace et met a jour FocusedActor. */
	void UpdateFocus();

	/** Vrai si l'acteur implemente l'interface et accepte l'interaction. */
	bool IsValidInteractable(AActor* Actor) const;

	/** Change la cible en notifiant l'ancienne et la nouvelle. */
	void SetFocusedActor(AActor* NewFocus);

	/** Point de vue reel du joueur, camera comprise. */
	bool GetViewPoint(FVector& OutLocation, FRotator& OutRotation) const;

	/** Reconstruit CachedPrompt et diffuse uniquement si le texte a reellement change. */
	void RefreshPrompt();

	/** Cible courante. TWeakObjectPtr : si l'objet est detruit, on ne garde pas un pointeur mort. */
	TWeakObjectPtr<AActor> FocusedActor;

	/**
	 * Vrai si on avait une cible au dernier changement diffuse.
	 * Indispensable : quand la cible se detruit (pickup ramasse), FocusedActor
	 * se vide tout seul, et une simple comparaison de pointeurs ne verrait
	 * jamais la transition "j'avais une cible" -> "je n'en ai plus".
	 */
	bool bHasFocusedActor = false;

	/**
	 * Dernier texte diffuse. Le garder permet deux choses : ne reveiller l'UI
	 * que sur un vrai changement, et servir GetFocusedPrompt() sans recalculer.
	 */
	FText CachedPrompt;

	float TimeSinceLastTrace = 0.f;
};
