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

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CraftingRecipeDataAsset.h"
#include "CraftingComponent.generated.h"

class UCraftingRecipeBook;
class UInventoryComponent;
class UItemDataAsset;

/**
 * Pourquoi une fabrication a echoue. Sert a l'interface, pour dire au joueur
 * ce qui manque au lieu de laisser un bouton grise sans explication.
 */
UENUM(BlueprintType)
enum class ECraftFailureReason : uint8
{
	None,
	InvalidRecipe,
	NoRecipeBook,
	StationUnavailable,
	MissingIngredients
};

/**
 * Etat d'un ingredient pour une recette donnee : ce qu'il faut, ce qu'on a.
 *
 * C'est le composant qui fait le rapprochement, pas le widget. Une ligne
 * d'ingredient n'a plus qu'a afficher deux nombres et choisir une couleur --
 * elle n'interroge jamais l'inventaire elle-meme.
 */
USTRUCT(BlueprintType)
struct FCraftIngredientStatus
{
	GENERATED_BODY()

	UPROPERTY(BlueprintReadOnly, Category = "Crafting")
	TObjectPtr<UItemDataAsset> Item = nullptr;

	UPROPERTY(BlueprintReadOnly, Category = "Crafting")
	int32 RequiredQuantity = 0;

	UPROPERTY(BlueprintReadOnly, Category = "Crafting")
	int32 OwnedQuantity = 0;

	bool IsSatisfied() const { return OwnedQuantity >= RequiredQuantity; }
};

/** Une fabrication vient d'aboutir. Pour le son, l'animation, une notification. */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCraftSucceeded, UCraftingRecipeDataAsset*, Recipe);

/** Les postes accessibles ont change : l'interface doit revoir sa liste. */
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAvailableStationsChanged);

/**
 * Le resultat n'est pas entre dans l'inventaire. A quelqu'un de le poser
 * dans le monde -- voir le commentaire de Craft().
 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnCraftOverflow, UItemDataAsset*, Item, int32, Quantity, int32, RemainingUses);

/**
 * Fabrication d'objets a partir d'un livre de recettes et de l'inventaire du
 * meme acteur.
 *
 * Sur le pawn : fabriquer est une capacite du corps, au meme titre que porter
 * un sac. Le composant ne connait ni l'interface ni la classe du pawn -- un
 * futur etabli pourra porter le meme composant avec son propre livre.
 */
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class EMBERWILD_API UCraftingComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	UCraftingComponent();

	virtual void InitializeComponent() override;

	/**
	 * Recettes a afficher, dans l'ordre du livre.
	 *
	 * Ne renvoie que celles dont le poste est ACCESSIBLE : les mains vides, on
	 * ne voit pas les recettes d'etabli. C'est le modele choisi -- une seule
	 * fenetre de craft dont le contenu depend d'ou l'on se trouve.
	 *
	 * @param StationFilter filtre d'affichage supplementaire, purement visuel.
	 *        Passe Count pour tout voir. Un poste demande ici mais non
	 *        accessible ne fait rien apparaitre : le filtre restreint, il
	 *        n'autorise jamais.
	 *
	 * Pas de valeur par defaut sur StationFilter : UHT refuse une entree
	 * UMETA(Hidden) comme defaut, et dé-cacher Count le rendrait selectionnable
	 * dans le champ Required Station d'une recette. L'appelant l'ecrit.
	 */
	UFUNCTION(BlueprintCallable, Category = "Crafting")
	void GatherRecipes(TArray<UCraftingRecipeDataAsset*>& OutRecipes, ECraftingStation StationFilter) const;

	/** Peut-on fabriquer cette recette maintenant, et sinon pourquoi. */
	UFUNCTION(BlueprintCallable, Category = "Crafting")
	bool CanCraft(const UCraftingRecipeDataAsset* Recipe, ECraftFailureReason& OutReason) const;

	/** Le detail requis / possede de chaque ingredient, dans l'ordre de la recette. */
	UFUNCTION(BlueprintCallable, Category = "Crafting")
	void GatherIngredientStatus(const UCraftingRecipeDataAsset* Recipe, TArray<FCraftIngredientStatus>& OutStatus) const;

	/**
	 * Consomme les ingredients et ajoute le resultat a l'inventaire.
	 *
	 * L'ordre compte : on consomme AVANT d'ajouter. Refuser une fabrication
	 * parce que l'inventaire est plein serait faux -- consommer trois branches
	 * libere justement la case ou poser le resultat. Et si malgre tout il ne
	 * rentre pas, l'objet part dans OnCraftOverflow pour etre pose au sol :
	 * aucune ressource ne disparait jamais, ni a l'entree ni a la sortie.
	 *
	 * @return true si la fabrication a eu lieu.
	 */
	/**
	 * Fabrique la recette. Chez un client, envoie la demande au serveur et rend
	 * true : le resultat redescendra par la replication de l'inventaire.
	 *
	 * Le serveur refait TOUJOURS CanCraft de son cote, avec sa propre liste de
	 * postes accessibles -- celle que le controller tient a jour par
	 * Client_OpenCraftingAtStation et Server_ReleaseStation.
	 */
	UFUNCTION(BlueprintCallable, Category = "Crafting")
	bool Craft(UCraftingRecipeDataAsset* Recipe);

	// ------------------------------------------------------------------
	// Postes accessibles
	//
	// Etat courant, pas de la configuration : {Hands} en permanence, plus
	// Workbench tant qu'on est devant un etabli. C'est une CONDITION de
	// fabrication, a ne pas confondre avec le filtre d'affichage de
	// GatherRecipes -- melanger les deux rendrait un jour un craft impossible
	// a cause d'un bouton d'interface.
	// ------------------------------------------------------------------

	UFUNCTION(BlueprintPure, Category = "Crafting|Stations")
	bool HasStation(ECraftingStation Station) const;

	/**
	 * A appeler quand le joueur entre dans le rayon d'un poste.
	 *
	 * Les appels se COMPTENT : deux etablis dont les portees se chevauchent, ce
	 * qui arrive des qu'on construit une base un peu dense, s'ajoutent tous les
	 * deux. Sortir du premier ne doit pas retirer l'acces alors qu'on est encore
	 * devant le second.
	 */
	UFUNCTION(BlueprintCallable, Category = "Crafting|Stations")
	void AddAvailableStation(ECraftingStation Station);

	/** A appeler quand il s'en eloigne. Hands ne peut pas etre retire. */
	UFUNCTION(BlueprintCallable, Category = "Crafting|Stations")
	void RemoveAvailableStation(ECraftingStation Station);

	UPROPERTY(BlueprintAssignable, Category = "Crafting")
	FOnCraftSucceeded OnCraftSucceeded;

	UPROPERTY(BlueprintAssignable, Category = "Crafting")
	FOnAvailableStationsChanged OnAvailableStationsChanged;

	UPROPERTY(BlueprintAssignable, Category = "Crafting")
	FOnCraftOverflow OnCraftOverflow;

protected:
	/** Ton DA_RecipeBook. Sans lui le composant ne connait aucune recette. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Crafting")
	TObjectPtr<UCraftingRecipeBook> RecipeBook;

	/** Resolu une fois a l'initialisation : chercher le composant a chaque clic serait absurde. */
	UPROPERTY(Transient, BlueprintReadOnly, Category = "Crafting")
	TObjectPtr<UInventoryComponent> Inventory;

private:
	/** La fabrication demandee par un client, arbitree par le serveur. */
	UFUNCTION(Server, Reliable)
	void Server_Craft(UCraftingRecipeDataAsset* Recipe);

	/**
	 * Combien de postes de chaque type sont a portee, et non un simple
	 * "present / absent" -- voir AddAvailableStation.
	 *
	 * Pas une UPROPERTY : ni la cle ni la valeur ne sont des references a
	 * collecter, et cet etat n'a rien a regler dans l'editeur.
	 */
	TMap<ECraftingStation, int32> StationRefCounts;
};
