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

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CharacterAppearanceTypes.h"
#include "CharacterAppearanceComponent.generated.h"

class UCharacterPartsDataAsset;
class USkeletalMeshComponent;
struct FCharacterBodySet;

/** Diffuse a chaque apparence appliquee, pour que l'UI se rafraichisse. */
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAppearanceApplied);

/**
 * Monte un personnage a partir d'une FCharacterAppearance : assigne les meshes,
 * les met en leader pose, et pose les couleurs sur des materiaux dynamiques.
 *
 * Un systeme = un composant, comme le reste du projet. Mais celui-ci a une
 * particularite qui vaut d'etre tenue : il ne suppose PAS vivre sur le pawn. Il
 * cherche le mesh d'un ACharacter s'il en trouve un, accepte qu'on lui en
 * designe un autre, et fonctionne donc a l'identique sur le mannequin de l'ecran
 * de customisation. C'est ce qui garantit que le personnage montre au joueur et
 * celui que ses amis verront ne peuvent pas diverger : c'est le meme code.
 *
 * Il ne replique rien -- l'apparence vivra sur ASurvivalPlayerState, comme le
 * pseudo, et pour la meme raison : elle doit survivre a la mort du pawn.
 */
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class EMBERWILD_API UCharacterAppearanceComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	UCharacterAppearanceComponent();

	virtual void BeginPlay() override;

	UPROPERTY(BlueprintAssignable, Category = "Appearance")
	FOnAppearanceApplied OnAppearanceApplied;

	/** Ton DA_CharacterParts. Sans lui le composant ne monte rien et le dit. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Appearance")
	TObjectPtr<UCharacterPartsDataAsset> Catalog;

	/**
	 * L'apparence posee au BeginPlay, tant que rien ne l'a remplacee.
	 *
	 * Elle sert au mannequin de l'ecran, aux tests en PIE, et de repli si les
	 * reglages du joueur sont vides -- c'est-a-dire au tout premier lancement.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Appearance")
	FCharacterAppearance DefaultAppearance;

	/**
	 * Designe le mesh porteur. A appeler AVANT la premiere application.
	 *
	 * Inutile sur un ACharacter, dont le mesh est trouve tout seul. Necessaire
	 * sur un acteur quelconque -- le mannequin de l'ecran de customisation.
	 */
	UFUNCTION(BlueprintCallable, Category = "Appearance")
	void SetTargetMesh(USkeletalMeshComponent* InTargetMesh);

	/**
	 * Monte l'apparence demandee. Idempotente, et bon marche a la repetition :
	 * les meshes ne sont retouches que si une PIECE a change, jamais pour une
	 * simple couleur. C'est ce qui permet a un curseur d'appeler cette fonction
	 * a chaque image sans engendrer un materiau par frame.
	 */
	UFUNCTION(BlueprintCallable, Category = "Appearance")
	void ApplyAppearance(const FCharacterAppearance& InAppearance);

	UFUNCTION(BlueprintPure, Category = "Appearance")
	const FCharacterAppearance& GetAppearance() const { return Appearance; }

	// ------------------------------------------------------------------
	// Reglage par nom -- ce qui permet de tester le montage avant qu'il
	// existe la moindre interface.
	//
	// Ces fonctions ne portent PAS le specificateur Exec, et ce n'est pas un
	// oubli : la chaine de routage des commandes console (voir Player.cpp)
	// interroge le monde, le PlayerInput, le controller, le pawn, le HUD, le
	// GameMode, le CheatManager, le GameState et le camera manager -- jamais
	// les composants, AActor ne surchargeant pas ProcessConsoleExec pour les
	// parcourir. Une UFUNCTION(Exec) posee ici serait silencieusement ignoree.
	// Les commandes vivent donc sur AFpsPlayer, qui delegue ici.
	// ------------------------------------------------------------------

	/**
	 * Cherche, parmi les slots de materiau du mesh de tete, celui dont le nom
	 * contient SlotHint. INDEX_NONE si aucun -- l'appelant ne pose alors rien.
	 */
	static int32 FindEyeMaterialSlot(const USkeletalMeshComponent* HeadComponent, FName SlotHint);

	/** Categories : body, head, hair, eyebrows, beard, mustache, eyes. */
	UFUNCTION(BlueprintCallable, Category = "Appearance|Debug")
	void ApplyPartByName(const FString& Part, int32 Index);

	/** Categories : skin, hair, eye, underwear. */
	UFUNCTION(BlueprintCallable, Category = "Appearance|Debug")
	void ApplyColorByName(const FString& Part, int32 ColorIndex);

	/** Ecrit l'apparence courante et le nombre d'options de chaque categorie. */
	UFUNCTION(BlueprintCallable, Category = "Appearance|Debug")
	void LogAppearance() const;

protected:
	virtual void OnUnregister() override;

private:
	/**
	 * Trouve le mesh porteur : celui de l'ACharacter en priorite, sinon le
	 * premier SkeletalMesh de l'acteur.
	 *
	 * L'ordre compte et n'est pas anodin : AFpsPlayer porte AUSSI FirstPersonArms,
	 * et prendre « le premier trouve » y monterait la tete du personnage sur les
	 * bras vus a l'ecran.
	 */
	USkeletalMeshComponent* ResolveTargetMesh();

	/** Cree le composant d'une categorie s'il manque, et le rend. */
	USkeletalMeshComponent* EnsurePartComponent(ECharacterPartCategory Category);

	/** Cree un composant enfant deja attache, leader-pose et configure. */
	USkeletalMeshComponent* CreateFollowerComponent(FName ComponentName);

	/** Assigne les meshes de corps, l'AnimBP, les pieces et le materiau d'iris. */
	void RebuildMeshes(const FCharacterBodySet& BodySet);

	/** Pose les quatre couleurs sur les materiaux dynamiques. */
	void ApplyColors(const FCharacterBodySet& BodySet);

	/** Pose un parametre vectoriel sur TOUS les elements d'un mesh. */
	/**
	 * Assigne un mesh a un composant en vidant d'abord ses materiaux surcharges.
	 *
	 * Indispensable : OverrideMaterials appartient au COMPOSANT, pas au mesh, et
	 * survit donc a un changement d'asset. Sans ce vidage, le corps B garderait
	 * la peau du corps A.
	 */
	static void SetPartMesh(USkeletalMeshComponent* MeshComponent, USkeletalMesh* NewMesh);

	static void TintAllElements(USkeletalMeshComponent* MeshComponent, FName Parameter, const FLinearColor& Color);

	/** Le mesh porteur, leader de tous les autres. */
	UPROPERTY(Transient)
	TObjectPtr<USkeletalMeshComponent> TargetMesh;

	/**
	 * Un composant par categorie, indexe par ECharacterPartCategory.
	 *
	 * Un tableau plutot qu'un membre nomme par piece : le montage tient alors
	 * dans une boucle, et ajouter une categorie ne demande qu'une valeur
	 * d'enum. Une case peut rester nulle -- une silhouette sans barbe n'engendre
	 * jamais le composant correspondant.
	 */
	UPROPERTY(Transient)
	TArray<TObjectPtr<USkeletalMeshComponent>> PartComponents;

	/**
	 * Les morceaux de corps au-dela du porteur. Vide aujourd'hui : le catalogue
	 * n'a qu'un mesh par silhouette. C'est ici qu'atterriront les segments le
	 * jour des armures, sans que la logique change.
	 */
	UPROPERTY(Transient)
	TArray<TObjectPtr<USkeletalMeshComponent>> ExtraBodyComponents;

	FCharacterAppearance Appearance;

	/** Faux tant qu'aucun montage n'a eu lieu : force la premiere reconstruction. */
	bool bBuilt = false;
};
