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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Interactable.h"
#include "CookingStation.generated.h"

class UInventoryComponent;
class UItemDataAsset;
class UNiagaraComponent;
class UPointLightComponent;
class USoundBase;
class USphereComponent;
class UStaticMeshComponent;

/**
 * Ce qu'un emplacement du grill porte a l'instant t.
 *
 * Uniquement de l'etat : le DataAsset dit ce que l'objet EST, cette struct dit
 * ou il en est. C'est la meme separation que FInventorySlot, pour la meme raison.
 */
USTRUCT(BlueprintType)
struct FCookingSlotState
{
	GENERATED_BODY()

	/** L'objet pose, ou nullptr. Un seul exemplaire par emplacement, comme dans Raft. */
	UPROPERTY(BlueprintReadOnly, Category = "Cooking")
	TObjectPtr<UItemDataAsset> Item = nullptr;

	/**
	 * Charges restantes de l'exemplaire pose. -1 = plein.
	 * L'usure traverse la cuisson comme elle traverse le sol : ce qu'on
	 * recupere est le meme exemplaire, pas un neuf.
	 */
	UPROPERTY(BlueprintReadOnly, Category = "Cooking")
	int32 RemainingUses = -1;

	/**
	 * Compte a rebours de l'etape en cours. Pas une UPROPERTY : un FTimerHandle
	 * n'est qu'un index dans le gestionnaire de timers, il n'y a rien a
	 * collecter ni a serialiser.
	 */
	FTimerHandle Timer;

	bool IsEmpty() const { return Item == nullptr; }
};

/**
 * Une buche dans le foyer : QUEL objet a ete mis, et combien de temps il tient
 * encore.
 *
 * C'est le coeur du modele : le carburant n'est pas une jauge de secondes mais
 * une pile d'objets. Cinq buches, cinq objets -- une branche qui tient dix
 * secondes occupe exactement la meme place qu'une planche qui en tient cent
 * quatre-vingts. Le joueur voit donc ce qu'il a donne au feu, pas un
 * pourcentage abstrait, et melanger les combustibles reste lisible.
 */
USTRUCT(BlueprintType)
struct FFuelSlotState
{
	GENERATED_BODY()

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

	/** Secondes de combustion restantes pour CE morceau. */
	UPROPERTY(BlueprintReadOnly, Category = "Cooking")
	float RemainingSeconds = 0.f;
};

/** L'etat de la station a change : allumage, depot, transformation, retrait. */
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnCookingStationChanged);

/**
 * Ce que l'appui sur Interagir va declencher, compte tenu de ce que le joueur
 * vise et de ce qu'il tient.
 *
 * Pas un UENUM : c'est un detail d'implementation, rien a l'exterieur n'a
 * besoin de le nommer.
 */
enum class ECookingAction : uint8
{
	None,
	TakeItem,
	PlaceItem,
	AddFuel
};

/**
 * Resultat complet de la decision. Calcule UNE fois, par ResolveAction(), et
 * servi a la fois au texte du prompt et a l'execution : deux cascades de `if`
 * separees finiraient par diverger, et le joueur lirait "Light the fire" en
 * obtenant autre chose.
 *
 * Struct de pile a duree de vie tres courte, d'ou les pointeurs bruts : rien
 * ici ne peut etre collecte entre le calcul et son usage.
 */
struct FCookingInteraction
{
	ECookingAction Action = ECookingAction::None;

	/** Emplacement de cuisson vise, ou INDEX_NONE si le joueur regarde le foyer. */
	int32 SlotIndex = INDEX_NONE;

	/** Objet actif de la barre rapide. */
	UItemDataAsset* HeldItem = nullptr;

	/** Slot d'inventaire d'ou sortira le bois, et l'objet correspondant. */
	int32 FuelSourceSlot = INDEX_NONE;
	UItemDataAsset* FuelItem = nullptr;
};

/**
 * Poste de cuisson pose dans le monde : feu de camp, et plus tard fourneau ou
 * sechoir en changeant simplement ses reglages.
 *
 * Modele Raft assume : AUCUNE interface. Le joueur tient sa viande crue dans la
 * barre rapide, vise un emplacement du grill, appuie sur Interagir, et regarde
 * le mesh changer.
 *
 * Le poste ne commente jamais son propre etat : le prompt n'affiche QUE des
 * actions realisables, exactement comme un objet a ramasser. Ce qui cuit, ce
 * qui manque et ce qui brule se lit sur les flammes, sur le nombre de buches
 * et sur le mesh de la nourriture -- l'ecrire en plus reconstruirait une
 * interface par-dessus un monde qui se suffit a lui-meme.
 *
 * Aucun Tick. Un timer par emplacement de cuisson, un seul pour le carburant :
 * celui de la buche en train de bruler. Eteindre le feu met ces timers en
 * pause, et la cuisson reprend exactement ou elle s'etait arretee sans qu'une
 * ligne ne compte le temps a la main.
 *
 * Le feu ne s'allume PAS a la main : il part des qu'il a du bois et quelque
 * chose a transformer, et s'arrete des qu'il n'a plus rien a faire. Le bois ne
 * part donc jamais en fumee pour rien -- ce qui evite au joueur d'avoir a
 * penser a eteindre derriere lui, une corvee qui n'apporte aucune decision
 * interessante.
 */
UCLASS()
class EMBERWILD_API ACookingStation : public AActor, public IInteractable
{
	GENERATED_BODY()

public:
	ACookingStation();

	// --- IInteractable ---
	virtual FText GetInteractionPrompt_Implementation(AActor* Interactor) const override;
	virtual bool CanInteract_Implementation(AActor* Interactor) const override;
	virtual void Interact_Implementation(AActor* Interactor) override;
	virtual void OnBeginFocus_Implementation() override;
	virtual void OnEndFocus_Implementation() override;

	/**
	 * Allume le feu s'il reste du bois dans le foyer.
	 *
	 * En temps normal, personne n'appelle ceci : UpdateFireState() decide seul.
	 * Reste exposee pour un futur allume-feu ou un evenement scenarise.
	 * @return false si le foyer est vide.
	 */
	UFUNCTION(BlueprintCallable, Category = "Cooking")
	bool Light();

	/** Eteint le feu. Les cuissons en cours se figent, elles ne repartent pas de zero. */
	UFUNCTION(BlueprintCallable, Category = "Cooking")
	void Extinguish();

	/** Vrai si au moins un emplacement porte quelque chose qui a encore une etape a franchir. */
	UFUNCTION(BlueprintPure, Category = "Cooking")
	bool HasPendingWork() const;

	UFUNCTION(BlueprintPure, Category = "Cooking")
	bool IsLit() const { return bIsLit; }

	/**
	 * Combien de morceaux de bois le foyer peut contenir.
	 *
	 * C'est le nombre de buches taguees dans le Blueprint : la capacite est une
	 * decision de mise en scene, et la regler a deux endroits garantirait qu'ils
	 * finissent par se contredire. FallbackFuelCapacity ne sert qu'aux postes
	 * sans buche visible, un four ferme par exemple.
	 */
	UFUNCTION(BlueprintPure, Category = "Cooking")
	int32 GetFuelCapacity() const { return FuelIndicators.Num() > 0 ? FuelIndicators.Num() : FallbackFuelCapacity; }

	/** Morceaux de bois actuellement dans le foyer. */
	UFUNCTION(BlueprintPure, Category = "Cooking")
	int32 GetFuelCount() const { return Fuel.Num(); }

	/** Secondes de combustion cumulees sur tout ce qui est dans le foyer. */
	UFUNCTION(BlueprintPure, Category = "Cooking")
	float GetFuelRemaining() const;

	/** Secondes avant la prochaine transformation, ou 0 si l'emplacement n'attend rien. */
	UFUNCTION(BlueprintPure, Category = "Cooking")
	float GetSlotTimeRemaining(int32 SlotIndex) const;

	UFUNCTION(BlueprintPure, Category = "Cooking")
	const TArray<FCookingSlotState>& GetSlots() const { return Slots; }

	UPROPERTY(BlueprintAssignable, Category = "Cooking")
	FOnCookingStationChanged OnStationChanged;

protected:
	virtual void BeginPlay() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

	/**
	 * Nombre d'emplacements de cuisson existant dans la classe. Les composants
	 * sont crees au constructeur, donc ce nombre est fige a la compilation --
	 * c'est le prix a payer pour pouvoir les positionner A LA SOURIS dans le
	 * viewport du Blueprint. Des composants engendres a l'execution ne seraient
	 * reglables qu'en tapant des coordonnees a l'aveugle.
	 */
	static constexpr int32 MaxCookingSlots = 4;

	// ------------------------------------------------------------------
	// Composants
	// ------------------------------------------------------------------

	/** Le foyer lui-meme : bois, pierres, structure. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Cooking")
	TObjectPtr<UStaticMeshComponent> BaseMesh;

	/**
	 * Ce que le trace d'interaction vient toucher. Sphere dediee et non le mesh,
	 * pour la meme raison que sur APickupItem : rien ne garantit qu'un mesh
	 * d'asset pack ait une collision.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Cooking")
	TObjectPtr<USphereComponent> InteractionSphere;

	/**
	 * Un mesh par emplacement de cuisson, qui porte la nourriture posee. Sa
	 * transform sert a la fois a l'afficher et a savoir ce que le joueur vise :
	 * pas besoin d'une collision par emplacement, donc pas une requete physique
	 * de plus.
	 *
	 * Positionne-les dans le viewport du Blueprint. Ils sont caches au demarrage
	 * tant qu'ils sont vides, mais restent visibles dans l'editeur pour que tu
	 * puisses les placer.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Cooking")
	TArray<TObjectPtr<UStaticMeshComponent>> SlotMeshes;

	/** Flamme. Active tant que le feu brule, coupee des qu'il s'eteint. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Cooking")
	TObjectPtr<UNiagaraComponent> FireEffect;

	/**
	 * Halo du foyer, allume en meme temps que la flamme.
	 *
	 * Regle intensite, couleur et rayon directement sur le composant dans le
	 * Blueprint : ce sont des proprietes natives, les redeclarer ici en
	 * doublon garantirait qu'un jour les deux se contredisent.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Cooking")
	TObjectPtr<UPointLightComponent> FireLight;

	// ------------------------------------------------------------------
	// Reglages
	// ------------------------------------------------------------------

	/** Emplacements de cuisson reellement utilises. Les autres restent caches. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking", meta = (ClampMin = "1", ClampMax = "4"))
	int32 CookingSlotCount = 2;

	/**
	 * Echelle appliquee au mesh de la nourriture posee.
	 * Les meshes d'objets sont dimensionnes pour etre poses au sol ; sur un
	 * grill ils sont souvent trop gros.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking", meta = (ClampMin = "0.01"))
	float CookingMeshScale = 1.f;

	/**
	 * Demi-angle du cone de visee autour de chaque emplacement, en degres.
	 *
	 * Au-dela, on considere que le joueur vise le foyer et pas un emplacement :
	 * c'est ce qui lui permet d'ajouter du bois ou d'allumer le feu sans devoir
	 * eviter le grill du regard.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking", meta = (ClampMin = "1", ClampMax = "89"))
	float SlotAimHalfAngle = 25.f;

	/** Multiplie la vitesse de cuisson. Un fourneau va plus vite que des braises. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking", meta = (ClampMin = "0.01"))
	float CookSpeedMultiplier = 1.f;

	/**
	 * Autorise a reprendre un objet qui n'a pas fini de cuire.
	 *
	 * Decoche par defaut : ce qu'on a mis a cuire est engage, on attend. Un
	 * objet qui a fini sa cuisson reste toujours recuperable, y compris pendant
	 * qu'il se dirige vers le brule -- sans quoi il serait impossible de sauver
	 * sa viande a temps, ce qui viderait la sur-cuisson de tout son sens.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking")
	bool bAllowTakingRawItems = false;

	/**
	 * Ajoute les secondes restantes aux textes du prompt.
	 *
	 * Decoche en jeu : un chiffre qui s'egrene est de l'interface deguisee, et
	 * le joueur doit lire l'avancement sur le mesh, pas sur un compteur. Coche
	 * le temps d'un reglage pour verifier tes durees.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking")
	bool bShowRemainingTimeInPrompt = false;

	/** Active le Custom Depth quand le poste est vise, comme les ramassables. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking")
	bool bHighlightOnFocus = true;

	// ------------------------------------------------------------------
	// Reglages : carburant
	// ------------------------------------------------------------------

	/**
	 * Tag a poser sur les meshes de buches qui figurent le contenu du foyer.
	 * Leur NOMBRE fait la capacite du feu, et leur ordre alphabetique l'ordre
	 * d'apparition : la derniere posee est la premiere a bruler.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking|Fuel")
	FName FuelIndicatorTag = TEXT("FuelLog");

	/** Capacite des postes qui n'ont aucune buche visible. Ignore des qu'il y en a une. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking|Fuel", meta = (ClampMin = "1"))
	int32 FallbackFuelCapacity = 5;

	/**
	 * Chaque buche prend le mesh de l'objet qu'elle represente.
	 *
	 * Decoche par defaut : les buches d'un feu de camp sont posees a la main
	 * dans le Blueprint, avec leur orientation et leur imbrication soignees, et
	 * y substituer un mesh d'objet gacherait ce travail. Coche-le pour un
	 * support ou l'on veut distinguer d'un coup d'oeil branches et planches.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking|Fuel")
	bool bFuelIndicatorsUseItemMesh = false;

	/** Bois deja dans le foyer au demarrage. Laisse vide pour un feu a alimenter. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking|Fuel")
	TObjectPtr<UItemDataAsset> StartingFuelItem;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking|Fuel", meta = (ClampMin = "0"))
	int32 StartingFuelCount = 0;

	// ------------------------------------------------------------------
	// Sons. Tous facultatifs : un champ vide ne joue rien.
	// ------------------------------------------------------------------

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking|Sound")
	TObjectPtr<USoundBase> LightSound;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking|Sound")
	TObjectPtr<USoundBase> ExtinguishSound;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Cooking|Sound")
	TObjectPtr<USoundBase> PlaceItemSound;

private:
	/**
	 * Quel emplacement de cuisson le joueur vise, ou INDEX_NONE s'il regarde le
	 * foyer. Se base sur le point de vue reel du pawn, donc sur ce qu'il voit a
	 * l'ecran, et non sur la position de son corps.
	 */
	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

	int32 ResolveAimedSlot(const AActor* Interactor) const;

	/** Decide ce que ferait un appui sur Interagir, sans rien modifier. */
	FCookingInteraction ResolveAction(AActor* Interactor) const;

	/** Depose l'objet actif de la barre rapide sur l'emplacement de cuisson. */
	bool PlaceItemFromHand(int32 SlotIndex, AActor* Interactor);

	/** Rend l'objet a l'inventaire. Refuse et laisse tout en place si c'est plein. */
	bool TakeItemToInventory(int32 SlotIndex, AActor* Interactor);

	/**
	 * Met UN morceau de bois dans le foyer, pris dans un slot d'inventaire.
	 *
	 * Un exemplaire par appui, jamais la pile entiere : donner du bois est un
	 * geste irreversible, et vider vingt branches d'un coup sur un feu qui n'en
	 * demandait qu'une ne se rattrape pas. Le joueur dose lui-meme.
	 */
	bool AddFuelFrom(int32 InventorySlotIndex, AActor* Interactor);

	/**
	 * Ou prendre du bois : la main d'abord, sinon le premier slot qui en
	 * contient. La main est prioritaire parce que c'est le seul moyen pour le
	 * joueur de CHOISIR ce qu'il brule.
	 */
	static int32 FindFuelSource(const UInventoryComponent* Inventory, UItemDataAsset*& OutItem);

	/** Peut-on retirer ce qui se trouve sur cet emplacement de cuisson. */
	bool CanTakeFromSlot(int32 SlotIndex) const;

	/** Arme le compte a rebours de l'etape courante de l'emplacement. */
	void StartSlotTimer(int32 SlotIndex);

	/** Fin d'etape : l'objet devient son resultat, et on enchaine s'il y a une suite. */
	void HandleSlotFinished(int32 SlotIndex);

	/** Recopie le WorldMesh de l'objet pose, ou cache le composant si l'emplacement est vide. */
	void RefreshSlotMesh(int32 SlotIndex);

	/**
	 * Allume ou eteint selon qu'il y ait, ou non, du bois ET du travail.
	 *
	 * Point de decision UNIQUE : toutes les actions qui changent le contenu du
	 * foyer ou du grill finissent ici. Laisser chaque action decider elle-meme
	 * d'allumer aurait fini par oublier un cas -- typiquement le dernier objet
	 * retire, qui laisserait le feu bruler dans le vide.
	 */
	void UpdateFireState();

	/** La buche du dessus a fini de bruler : on passe a la suivante. */
	void HandleFuelConsumed();

	/** Arme le timer sur la buche en cours de combustion. */
	void StartFuelTimer();

	/** Recopie le restant du timer dans la buche courante avant de le modifier. */
	void SyncFuelFromTimer();

	/**
	 * Allume ou coupe tout ce qui se voit quand le feu brule.
	 *
	 * Un seul endroit pour la flamme et la lumiere : le jour ou s'ajoute une
	 * boucle sonore ou un panache de fumee, il n'y aura toujours qu'un point a
	 * modifier au lieu de deux appels a retrouver dans Light() et Extinguish().
	 */
	void ApplyFireVisuals(bool bActive);

	/** Retrouve les buches par leur tag, une fois pour toutes, au demarrage. */
	void CacheFuelIndicators();

	/**
	 * Montre exactement autant de buches qu'il y a de morceaux dans le foyer.
	 *
	 * Plus aucun calcul de palier ni de timer d'affichage depuis que le
	 * carburant est une pile d'objets : une buche visible EST un objet stocke.
	 */
	void RefreshFuelIndicators();

	/**
	 * Ce que cet objet va devenir sur le feu, et en combien de temps.
	 * La cuisson passe avant la combustion : voir le commentaire de bCanBurn
	 * sur UItemDataAsset.
	 */
	static UItemDataAsset* GetNextStage(const UItemDataAsset* Item, float& OutDuration);

	/** Inventaire du pawn qui interagit, ou nullptr. */
	static UInventoryComponent* GetInventoryOf(AActor* Interactor);

	/**
	 * Etat courant de chaque emplacement de cuisson. Toujours dimensionne a
	 * MaxCookingSlots.
	 *
	 * Replique : c'est ce qui fait changer le mesh de la nourriture sous les
	 * yeux de tous. Le FTimerHandle que porte chaque slot n'est pas une
	 * UPROPERTY et ne part donc pas sur le reseau -- c'est voulu, la cuisson se
	 * compte uniquement sur le serveur et les clients n'en voient que le
	 * resultat.
	 */
	UPROPERTY(Transient, ReplicatedUsing = OnRep_Slots)
	TArray<FCookingSlotState> Slots;

	/**
	 * Le contenu du foyer, dans l'ordre ou il a ete donne. L'index 0 est celui
	 * qui brule ; quand il est consomme, tout le monde avance d'un cran et la
	 * derniere buche disparait du foyer.
	 */
	/**
	 * Replique : le nombre de buches visibles EST la jauge de carburant, c'est
	 * la seule facon pour un joueur de savoir combien il reste de feu. Les
	 * secondes restantes partent avec, mais seul le serveur les decompte.
	 */
	UPROPERTY(Transient, ReplicatedUsing = OnRep_Fuel)
	TArray<FFuelSlotState> Fuel;

	FTimerHandle FuelTimer;

	/**
	 * Buches de mise en scene, resolues au BeginPlay et triees par nom.
	 * Transient : ces pointeurs designent des composants du Blueprint, il n'y a
	 * rien a sauvegarder, seulement a garder du GC le temps de la partie.
	 */
	UPROPERTY(Transient)
	TArray<TObjectPtr<UStaticMeshComponent>> FuelIndicators;

	UPROPERTY(ReplicatedUsing = OnRep_IsLit)
	bool bIsLit = false;

	/** Le contenu du grill a change cote serveur : on repose tous les meshes. */
	UFUNCTION()
	void OnRep_Slots();

	UFUNCTION()
	void OnRep_Fuel();

	UFUNCTION()
	void OnRep_IsLit();
};
