// 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 "StorageContainer.generated.h"

class UBoxComponent;
class UInventoryComponent;
class UItemDataAsset;
class USoundBase;
class UStaticMeshComponent;

/**
 * Contenu initial d'un coffre pose dans le niveau.
 *
 * De la donnee de level design, pas du gameplay : un coffre de depart, une
 * cache a trouver. Verse une seule fois au BeginPlay, jamais relu ensuite.
 */
USTRUCT(BlueprintType)
struct FStorageStartingItem
{
	GENERATED_BODY()

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

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage", meta = (ClampMin = "1"))
	int32 Quantity = 1;
};

/**
 * Coffre pose dans le monde.
 *
 * Il ne contient AUCUN modele de donnees a lui : son contenu est un
 * UInventoryComponent, exactement le meme que celui du joueur, simplement
 * configure sans barre rapide. C'est ce qui rend le glisser-deposer entre le
 * sac et le coffre gratuit -- ce sont les memes cases, le meme FInventorySlot,
 * et donc la durabilite d'une hache traverse le coffre sans une ligne de code.
 *
 * Toute la collision tient dans UNE boite, et les deux meshes n'en portent
 * aucune. C'est la difference avec ACraftingStation, qui se contente de son
 * mesh : un coffre est en deux morceaux dont l'un TOURNE. Se reposer sur leurs
 * collisions imposerait que le pack en fournisse deux correctes, et laisserait
 * surtout un trou dans la surface visable des que le couvercle se leve --
 * viser le haut du coffre ouvert ne toucherait plus rien.
 *
 * La boite est fille de la caisse et ne bouge donc jamais : le coffre se vise
 * exactement pareil ouvert ou ferme. Elle bloque aussi le joueur physiquement,
 * ce qui evite d'avoir a se demander si le mesh du pack sait le faire.
 *
 * Aucun Tick au repos : il ne s'active que le temps d'animer le couvercle, et
 * se rendort des que l'angle est atteint.
 */
UCLASS()
class EMBERWILD_API AStorageContainer : public AActor, public IInteractable
{
	GENERATED_BODY()

public:
	AStorageContainer();

	virtual void Tick(float DeltaTime) override;

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

	/** Le couvercle vient de changer d'etat cote serveur : on joue l'animation. */
	UFUNCTION()
	void OnRep_Open();

	/**
	 * Son et relance du tick d'animation. Partage par SetOpen (serveur) et
	 * OnRep_Open (clients) : deux copies finiraient par diverger, et le
	 * symptome serait un couvercle muet chez les invites.
	 */
	void ApplyOpenState();

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

	UFUNCTION(BlueprintPure, Category = "Storage")
	UInventoryComponent* GetStorage() const { return Storage; }

	/** Nom affiche en titre de l'ecran de coffre. */
	UFUNCTION(BlueprintPure, Category = "Storage")
	FText GetDisplayName() const { return DisplayName; }

	/** Cases par ligne souhaitees pour la grille de l'ecran. */
	UFUNCTION(BlueprintPure, Category = "Storage")
	int32 GetColumnCount() const { return ColumnCount; }

	/**
	 * Ouvre ou ferme le couvercle.
	 *
	 * Appele par le controller, pas par le coffre lui-meme : c'est lui qui sait
	 * quand l'ecran se referme, que ce soit par Echap, par la mort du joueur ou
	 * parce qu'il s'est trop eloigne. Un coffre qui deciderait seul finirait
	 * ouvert dans la moitie de ces cas.
	 */
	/**
	 * Leve ou referme le couvercle. **Serveur uniquement** : le couvercle est
	 * un etat du MONDE, les quatre joueurs doivent le voir bouger. Un client
	 * qui l'appellerait verrait son coffre s'ouvrir tout seul chez lui.
	 */
	UFUNCTION(BlueprintCallable, Category = "Storage")
	void SetOpen(bool bInOpen);

	UFUNCTION(BlueprintPure, Category = "Storage")
	bool IsOpen() const { return bOpen; }

protected:
	/**
	 * Dimensionne le composant AVANT que son InitializeComponent() fige la
	 * taille par defaut. PostInitializeComponents serait deja trop tard, et le
	 * constructeur trop tot -- il ne connait pas les valeurs reglees sur
	 * l'instance posee dans le niveau.
	 */
	virtual void PreInitializeComponents() override;

	virtual void BeginPlay() override;

	/** Redimensionne la boite quand un mesh change dans le Blueprint. */
	virtual void OnConstruction(const FTransform& Transform) override;

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

	/** La caisse : SM_Chest_Bottom. Racine, purement visuelle. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Storage")
	TObjectPtr<UStaticMeshComponent> BaseMesh;

	/**
	 * L'unique collision du coffre : ce que le trace d'interaction vient
	 * toucher, et ce qui empeche le joueur de traverser.
	 *
	 * Fille de la caisse et non du couvercle : elle ne suit surtout pas ce
	 * dernier. Un coffre ouvert doit se viser au meme endroit qu'un coffre
	 * ferme, sinon le prompt disparait au moment precis ou l'on s'en sert.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Storage")
	TObjectPtr<UBoxComponent> InteractionBox;

	/**
	 * Le couvercle : SM_Chest_Top, enfant direct de la caisse.
	 *
	 * C'est LUI qu'on tourne, donc la charniere est le pivot que l'artiste a
	 * donne au mesh -- un StaticMeshComponent tourne toujours autour de
	 * l'origine de son mesh. Rien a placer a la main, mais rien a rattraper non
	 * plus : si un pack posait ce pivot au centre du couvercle, l'ouverture se
	 * verrait de travers et il faudrait corriger le pivot dans l'editeur de
	 * mesh, pas ici.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Storage")
	TObjectPtr<UStaticMeshComponent> LidMesh;

	/** Le contenu. Un inventaire ordinaire, sans barre rapide. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Storage")
	TObjectPtr<UInventoryComponent> Storage;

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

	/**
	 * Nombre de cases. Reglable PAR INSTANCE : le meme Blueprint donne un petit
	 * coffre de 12 et un grand de 30 selon celui qu'on pose.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage", meta = (ClampMin = "1", ClampMax = "60"))
	int32 SlotCount = 20;

	/** Cases par ligne dans l'ecran. Purement visuel. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage", meta = (ClampMin = "1"))
	int32 ColumnCount = 6;

	/** Nom affiche en titre de l'ecran. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage")
	FText DisplayName;

	/** Texte affiche quand le joueur vise le coffre. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage")
	FText InteractionPrompt;

	/** Verse dans le coffre au demarrage. Laisse vide pour un coffre a remplir. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage")
	TArray<FStorageStartingItem> StartingItems;

	// ------------------------------------------------------------------
	// Collision
	// ------------------------------------------------------------------

	/**
	 * La boite epouse toute seule les deux meshes, couvercle ferme.
	 *
	 * Coche par defaut : changer SM_Chest_v1 pour le v3 doit suffire, sans
	 * avoir a retailler une boite a la main derriere. Decoche-le seulement si
	 * tu veux une zone volontairement differente du volume du coffre.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage|Collision")
	bool bAutoFitInteractionBox = true;

	/**
	 * Marge ajoutee autour des meshes par l'ajustement automatique.
	 *
	 * Garde-la petite. Le trace d'interaction est deja un balayage spherique
	 * tolerant : gonfler la boite ne rendrait pas le coffre plus facile a viser,
	 * ca l'empecherait juste de se coller a un mur.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage|Collision", meta = (ClampMin = "0", Units = "cm"))
	float InteractionBoxPadding = 0.f;

	// ------------------------------------------------------------------
	// Couvercle
	// ------------------------------------------------------------------

	/**
	 * Rotation RELATIVE du couvercle ouvert. Le repos vaut toujours zero.
	 *
	 * Un FRotator complet et pas un simple angle : selon l'orientation dans
	 * laquelle le mesh a ete exporte, la charniere peut tomber sur le pitch, le
	 * yaw ou le roll. Regle les trois a l'oeil dans le viewport plutot que
	 * d'aller comprendre quel axe local du mesh pointe ou.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage|Lid")
	FRotator OpenRotation = FRotator(-100.f, 0.f, 0.f);

	/** Duree de l'ouverture, en secondes. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage|Lid", meta = (ClampMin = "0.05", Units = "s"))
	float LidDuration = 0.35f;

	// ------------------------------------------------------------------
	// Sons. Facultatifs : un champ vide ne joue rien.
	// ------------------------------------------------------------------

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

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

private:
	/** Recopie LidAlpha dans la rotation du couvercle. */
	void ApplyLidRotation();

	/** Cale la boite sur le volume combine des deux meshes, couvercle ferme. */
	void FitInteractionBox();

	/**
	 * Repliqué à tout le monde : voir le couvercle d'un coffre se lever est le
	 * seul signe qu'un coéquipier est en train de fouiller dedans.
	 */
	UPROPERTY(ReplicatedUsing = OnRep_Open)
	bool bOpen = false;

	/** 0 = ferme, 1 = ouvert. Interpole, jamais pose directement. */
	float LidAlpha = 0.f;
};
