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

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "ItemDataAsset.h"
#include "HeldItemComponent.generated.h"

class AFpsPlayer;
class UAnimInstance;
class UItemDataAsset;
class UStaticMeshComponent;

/** Diffuse a chaque changement d'objet en main, nul compris. L'UI et l'anim s'y abonnent. */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHeldItemChanged, UItemDataAsset*, NewItem);

/**
 * Ce qu'a fait PlayUseMontage, du point de vue de l'appelant.
 *
 * Trois etats et pas un booleen, parce que l'appelant en tire trois conduites
 * differentes : un geste qui demarre porte son propre marqueur d'impact et il
 * n'y a plus qu'a attendre ; un geste deja en cours en enverra un lui aussi ;
 * une absence de montage, elle, ne produira JAMAIS de marqueur et doit retomber
 * sur l'effet immediat. Les confondre donne soit un objet sans montage qui ne
 * fait plus rien du tout, soit un clic martele qui declenche l'effet a chaque
 * image pendant que le bras, lui, ne rejoue pas.
 */
UENUM(BlueprintType)
enum class EUseMontageResult : uint8
{
	/** Le geste vient de demarrer : son AnimNotify declenchera l'effet. */
	Started,

	/** Le meme geste tournait deja ; on ne l'a pas relance. */
	AlreadyPlaying,

	/** Rien a jouer -- l'objet n'a pas de UseMontage, ou les bras sont distants. */
	NoMontage
};

/**
 * Montre dans la main l'objet occupant la case active de la barre rapide.
 *
 * Un systeme = un composant, comme le reste du projet. Celui-ci ne decide de
 * rien et ne consomme rien : il regarde l'inventaire et pose deux meshes. Ce que
 * l'objet FAIT quand on clique reste dans AFpsPlayer::PerformUseItem.
 *
 * Deux meshes et pas un, parce qu'il y a deux corps a habiller :
 *
 *   - les BRAS (FirstPersonArms), visibles du seul proprietaire, attaches a la
 *     camera. C'est la hache qu'on voit soi-meme.
 *   - le CORPS (le Mesh du Character), invisible pour son proprietaire mais vu
 *     par les trois autres joueurs -- et dont on percoit sa propre ombre.
 *
 * Les bras recopient du porteur leur « visible pour moi seul ». Le mesh du corps,
 * lui, se cache par la VISIBILITE chez le seul joueur qui controle ce pawn --
 * bOwnerNoSee y couperait son ombre, voir UpdateLocalVisibility().
 *
 * ------------------------------------------------------------------
 * Reseau : c'est le CLIENT qui pilote, toujours
 * ------------------------------------------------------------------
 *
 * UInventoryComponent::SelectedHotbarIndex n'est PAS replique -- c'est une
 * intention purement locale, et il n'y a aucune raison de la mettre sur le
 * reseau. Le serveur ne peut donc pas deviner ce que le joueur a en main : lire
 * l'inventaire d'un pawn distant y trouverait toujours la case 0.
 *
 * Ce qui se replique est donc l'OBJET lui-meme, pas l'index. Le proprietaire
 * l'applique en local et l'annonce ; le serveur l'enregistre et le rediffuse aux
 * autres. Exactement le schema de bIsSprinting, COND_SkipOwner compris : renvoyer
 * a l'emetteur une valeur qui a un aller-retour de retard ne peut que faire
 * clignoter ce qu'il tient deja.
 */
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class EMBERWILD_API UHeldItemComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	UHeldItemComponent();

	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

	/** L'objet actuellement montre dans la main. Nul = les mains sont vides. */
	UFUNCTION(BlueprintPure, Category = "Held Item")
	UItemDataAsset* GetHeldItem() const { return HeldItem; }

	/**
	 * Vrai quand un mesh est effectivement monte dans la main.
	 *
	 * Different de `GetHeldItem() != nullptr`, et la nuance compte pour
	 * l'animation : la plupart des ressources du jeu n'ont pas bShowInHand, donc
	 * les tenir ne met rien dans la paume. Se fier a l'objet ferait jouer la
	 * posture « objet en main » sur des mains vides, ce qui se voit tout de suite.
	 */
	UFUNCTION(BlueprintPure, Category = "Held Item")
	bool HasItemInHand() const { return bItemVisibleInHand; }


	/**
	 * Relit la case active et met la main a jour.
	 *
	 * Sans effet ailleurs que chez le joueur local : ce composant est le seul du
	 * projet dont la SOURCE de verite vive uniquement sur la machine du
	 * proprietaire. Idempotente, donc appelable a la volee.
	 */
	UFUNCTION(BlueprintCallable, Category = "Held Item")
	void RefreshFromInventory();

	/**
	 * Joue le geste du clic gauche avec l'objet en main.
	 *
	 * Appelee par le PAWN au moment de l'input, donc chez le joueur local et
	 * AVANT tout aller-retour reseau : un coup de pioche qui attendrait la
	 * reponse du serveur partirait avec un temps de retard sur le clic, et c'est
	 * la premiere chose qu'on sent dans un jeu.
	 *
	 * Il ne fait QUE l'animation : l'effet de l'objet part du marqueur
	 * UAnimNotify_ItemAction pose dans le montage, pour tomber au moment de
	 * l'impact et pas a l'appui. D'ou le retour a trois etats, que l'appelant
	 * doit lire -- voir EUseMontageResult.
	 *
	 * @return ce qu'il est advenu du geste.
	 */
	UFUNCTION(BlueprintCallable, Category = "Held Item")
	EUseMontageResult PlayUseMontage();

	UPROPERTY(BlueprintAssignable, Category = "Held Item")
	FOnHeldItemChanged OnHeldItemChanged;

	/**
	 * Os ou socket des BRAS auquel l'objet s'accroche.
	 *
	 * C'est un OS du rig et pas un socket taille sur mesure, d'ou les offsets
	 * par objet dans UItemDataAsset : creer un socket par outil demanderait de
	 * rouvrir le squelette a chaque nouvel objet, alors que l'offset se regle
	 * dans le DataAsset qu'on est deja en train d'ecrire.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Held Item")
	FName FirstPersonSocket = TEXT("RightHand_Holder_JTN");

	/** Os de la main droite du mannequin UE5, porteur du mesh 3e personne. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Held Item")
	FName ThirdPersonSocket = TEXT("hand_r");

	/**
	 * Geste du clic gauche quand les mains sont VIDES (MTG_Punch).
	 *
	 * Ici et pas sur un DataAsset, pour la raison evidente qu'il n'y a pas
	 * d'objet a qui le demander. Ici et pas sur AFpsPlayer a cote de GrabMontage,
	 * parce que c'est ce composant qui choisit quel geste part au clic : deux
	 * moities de la meme decision dans deux classes finiraient par diverger.
	 *
	 * Il ne sert QUE quand aucun objet n'occupe la case active. Un objet en main
	 * sans UseMontage ne joue rien -- retomber sur le poing ferait boxer le
	 * joueur en mangeant une pomme.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Held Item")
	TObjectPtr<class UAnimMontage> UnarmedUseMontage;

	// ------------------------------------------------------------------
	// Accroupissement
	//
	// Des MONTAGES et pas des etats de la machine, alors que CrouchIdle et
	// CrouchWalk, eux, sont bien des etats. La frontiere est la meme que partout
	// ailleurs : ce qui DURE est un etat, ce qui ARRIVE est un montage. Plier les
	// genoux prend trois dixiemes de seconde et se superpose au cycle en cours.
	//
	// Raison technique par-dessus, et elle est decisive : un etat dont la sortie
	// depend de la fin de l'animation utilise « Automatic Rule Based on Sequence
	// Player in State », qui a besoin d'un Sequence Player pour lire sa duree. Un
	// etat qui contiendrait un choix entre deux animations -- avec et sans objet
	// -- lui retire cette certitude, et il faudrait ecrire des durees en dur dans
	// les transitions, a re-regler a chaque animation livree.
	//
	// Ici et pas sur UItemDataAsset : le geste est GENERIQUE, deux variantes en
	// tout. Le jour ou un outil voudra la sienne, un champ optionnel sur son
	// DataAsset primera sur ceux-ci, sans rien casser.
	// ------------------------------------------------------------------

	/** Geste d'accroupissement, mains vides (MTG_EnterCrouch). */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Held Item|Crouch")
	TObjectPtr<class UAnimMontage> UnarmedEnterCrouchMontage;

	/** Meme geste, un objet en main. Vide = on rejoue la version mains vides. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Held Item|Crouch")
	TObjectPtr<class UAnimMontage> HeldEnterCrouchMontage;

	/** Geste de relevee, mains vides (MTG_ExitCrouch). */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Held Item|Crouch")
	TObjectPtr<class UAnimMontage> UnarmedExitCrouchMontage;

	/** Meme geste, un objet en main. Vide = on rejoue la version mains vides. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Held Item|Crouch")
	TObjectPtr<class UAnimMontage> HeldExitCrouchMontage;

	/**
	 * Joue le geste d'accroupissement ou de relevee, selon ce qu'on tient.
	 *
	 * Appelee par AFpsPlayer depuis OnStartCrouch / OnEndCrouch, qui sont deja
	 * surchargees : le pawn sait exactement quand la capsule change de taille,
	 * personne n'a a le deviner.
	 */
	UFUNCTION(BlueprintCallable, Category = "Held Item|Crouch")
	bool PlayCrouchMontage(bool bEntering);

protected:
	virtual void BeginPlay() override;
	virtual void OnUnregister() override;

private:
	/**
	 * L'objet en main. Un pointeur d'asset se replique comme n'importe quelle
	 * reference d'objet -- c'est deja ce que fait FInventorySlot::Item, et c'est
	 * pour ca qu'on peut se permettre d'envoyer l'objet plutot qu'un index.
	 *
	 * COND_SkipOwner : le proprietaire vient de l'appliquer en local, le lui
	 * renvoyer ecraserait son etat courant par une valeur en retard.
	 */
	UPROPERTY(ReplicatedUsing = OnRep_HeldItem)
	TObjectPtr<UItemDataAsset> HeldItem;

	UFUNCTION()
	void OnRep_HeldItem();

	/** Point d'entree unique : applique en local, puis annonce au serveur. */
	void SetHeldItem(UItemDataAsset* NewItem);

	UFUNCTION(Server, Reliable)
	void Server_SetHeldItem(UItemDataAsset* NewItem);

	/** Monte ou demonte les deux meshes selon HeldItem. Tourne sur TOUTES les machines. */
	void ApplyHeldVisuals();

	/**
	 * Joue le geste de sortie d'outil sur les bras du joueur LOCAL.
	 *
	 * Appelee depuis SetHeldItem et pas depuis ApplyHeldVisuals, alors que les
	 * deux disent « l'objet en main a change » : ApplyHeldVisuals tourne sur
	 * toutes les machines (OnRep, serveur), et un geste de bras n'a de sens que
	 * chez celui qui les voit.
	 */
	void PlayEquipMontage();

	/**
	 * Joue un montage sur les bras du joueur LOCAL, et lui seul.
	 *
	 * @param bSkipIfAlreadyPlaying vrai pour le clic gauche : marteler le bouton
	 *        relancerait le geste depuis le debut a chaque image, ce qui donne un
	 *        bras qui tremble au lieu de frapper. Faux pour l'equipement, ou
	 *        changer d'outil doit couper net le geste precedent.
	 * @return true si un montage a demarre.
	 */
	bool PlayLocalArmsMontage(class UAnimMontage* Montage, bool bSkipIfAlreadyPlaying);

	/**
	 * Branche ou debranche la couche d'animation de l'objet sur les bras.
	 *
	 * Locale elle aussi, et pour la meme raison que le montage : les bras sont en
	 * OnlyOwnerSee. Chez l'hote, lier une couche sur le squelette de bras d'un ami
	 * ferait evaluer un graphe que personne ne rend.
	 *
	 * Idempotente : elle compare a la couche deja liee et ne fait rien si elle est
	 * la bonne. C'est ce qui permet de l'appeler a chaque rafraichissement
	 * d'inventaire sans relancer le graphe.
	 */
	void UpdateArmsAnimLayer();

	/**
	 * Cache le mesh du CORPS chez le joueur qui controle ce pawn, et lui seul.
	 *
	 * Par la VISIBILITE et non par bOwnerNoSee : sur un UStaticMeshComponent,
	 * bCastHiddenShadow n'honore que la premiere -- cache par le proprietaire,
	 * l'objet perd son ombre en meme temps que son mesh. Detail dans le .cpp.
	 */
	void UpdateLocalVisibility();

	/**
	 * Cree les deux composants de mesh s'ils manquent.
	 *
	 * A la demande et pas dans le constructeur : le composant n'a alors aucune
	 * hypothese sur l'ordre de creation des sous-objets d'AFpsPlayer, et un pawn
	 * qui ne tient jamais rien ne paie pas deux composants pour rien.
	 */
	void EnsureHeldMeshComponents();

	/**
	 * Cree un mesh d'objet en main attache a Parent, ou nullptr si impossible.
	 *
	 * @param bCastWorldShadow l'objet projette-t-il une ombre dans le monde.
	 *        Vrai pour la main du corps, faux pour les bras -- un mesh colle a
	 *        l'objectif projetterait une ombre enorme en travers du decor.
	 */
	UStaticMeshComponent* CreateHeldMeshComponent(FName ComponentName, class USkeletalMeshComponent* Parent,
		FName SocketName, bool bCastWorldShadow);

	/** Les deux delegues d'inventaire menent ici. */
	UFUNCTION()
	void HandleInventoryChanged();

	UFUNCTION()
	void HandleSelectedHotbarSlotChanged(int32 NewIndex);

	/** Le pawn porteur, ou nullptr si le composant vit ailleurs. */
	AFpsPlayer* GetOwningPlayer() const;

	/** Mesh vu par le seul proprietaire, accroche aux bras. */
	UPROPERTY(Transient)
	TObjectPtr<UStaticMeshComponent> FirstPersonHeldMesh;

	/** Mesh vu par les autres joueurs, accroche a la main du corps. */
	UPROPERTY(Transient)
	TObjectPtr<UStaticMeshComponent> ThirdPersonHeldMesh;

	/**
	 * Un mesh est-il reellement monte en main, en cache.
	 *
	 * Recopie de la decision prise par ApplyHeldVisuals plutot que recalculee :
	 * l'AnimInstance la lit a chaque frame, et surtout deux facons de repondre a
	 * la meme question finiraient par diverger.
	 */
	bool bItemVisibleInHand = false;

	/**
	 * La couche actuellement liee aux bras, ou nulle.
	 *
	 * Indispensable, et pas un simple cache : LinkAnimClassLayers AJOUTE une
	 * couche, elle ne remplace pas la precedente. Sans garder ce qui est en
	 * place pour le delier, passer de la hache a la pioche laisserait les deux
	 * branchees -- et c'est la premiere qui gagnerait sur les fonctions que la
	 * seconde n'implemente pas.
	 */
	UPROPERTY(Transient)
	TSubclassOf<UAnimInstance> LinkedArmsLayer;

	/** Evite de repeter l'avertissement de socket introuvable a chaque montage. */
	bool bLoggedMissingSocket = false;
};
