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

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "InventoryWidget.generated.h"

class UInventoryComponent;
class UInventorySlotWidget;
class UItemDetailsWidget;
class UUniformGridPanel;

/**
 * Grille de cases. Cree ses cases dynamiquement a partir du nombre de slots du
 * composant, et se remet a jour uniquement quand l'inventaire change.
 *
 * Elle ne suppose PLUS qu'elle affiche le sac du joueur : le meme widget sert
 * de grille de coffre en lui donnant simplement un autre UInventoryComponent.
 * C'est ce qui permet d'avoir un seul WBP_Inventory pose des deux cotes de
 * l'ecran de coffre -- et donc la meme apparence, sans duplication.
 *
 * Le nombre de cases n'est pas fige : quand un sac ajoutera des slots, la
 * grille s'agrandira toute seule au prochain rafraichissement.
 */
UCLASS(Abstract)
class EMBERWILD_API UInventoryWidget : public UUserWidget
{
	GENERATED_BODY()

public:
	virtual void NativeConstruct() override;
	virtual void NativeDestruct() override;

	/** Reconstruit si necessaire, puis pousse le contenu dans chaque case. */
	UFUNCTION(BlueprintCallable, Category = "Inventaire")
	void Refresh();

	/** Index de la case sous le curseur, ou INDEX_NONE. */
	UFUNCTION(BlueprintPure, Category = "Inventaire")
	int32 GetHoveredSlotIndex() const { return HoveredSlotIndex; }

	/**
	 * Case survolee ET inventaire auquel elle appartient.
	 *
	 * L'index seul ne suffit plus : la touche "jeter" doit savoir de quel
	 * conteneur sortir l'objet, et la case 3 existe aussi bien dans le sac que
	 * dans le coffre.
	 */
	bool GetHoveredSlot(UInventoryComponent*& OutInventory, int32& OutIndex) const;

	/** Abonne aux cases : le curseur entre ou sort de l'une d'elles. */
	UFUNCTION()
	void NotifySlotHovered(int32 Index, bool bHovered);

	/**
	 * Survol d'une case qui n'appartient PAS a cette grille -- la barre rapide.
	 *
	 * Ne touche pas a HoveredSlotIndex : celui-ci sert a la touche "jeter", qui
	 * doit continuer de viser la grille et elle seule. Seul le panneau de detail
	 * repond, parce qu'il n'y en a qu'un a l'ecran et qu'il appartient a la
	 * grille du sac.
	 */
	void NotifyExternalSlotHovered(UInventoryComponent* InInventory, int32 InSlotIndex, bool bHovered);

	/**
	 * Autorise ou coupe le panneau de detail de cette grille.
	 *
	 * C'est l'ECRAN qui decide, jamais une case a cocher dans la Designer : le
	 * WBP_Inventory est instancie deux fois sur l'ecran de coffre, et un reglage
	 * qu'on peut oublier de decocher sur l'une des deux instances est un bug qui
	 * attend. Coupe : la grille de coffre n'en a jamais, et le sac perd le sien
	 * tant qu'un coffre est ouvert -- l'ecran est deja assez large a trois
	 * colonnes.
	 */
	void SetItemDetailsEnabled(bool bEnabled);

	/**
	 * (Re)branche le widget sur l'inventaire du pawn actuellement possede.
	 * Indispensable apres un respawn : le nouveau pawn a un nouveau composant,
	 * et l'ancien abonnement pointerait vers un objet detruit.
	 */
	void BindToOwningPawn();

	/**
	 * (Re)branche le widget sur un inventaire quelconque -- celui d'un coffre,
	 * typiquement. nullptr vide la grille : une grille de coffre referme ne doit
	 * pas continuer d'afficher un contenu qu'on ne pilote plus.
	 */
	void BindToInventory(UInventoryComponent* InInventory);

	UFUNCTION(BlueprintPure, Category = "Inventaire")
	UInventoryComponent* GetBoundInventory() const { return BoundInventory.Get(); }

	/** Destination du clic droit pour toutes les cases de cette grille. */
	void SetQuickTransferTarget(UInventoryComponent* InTarget);

	/**
	 * Change le nombre de colonnes et redispose les cases.
	 *
	 * Sert a la grille de coffre : c'est le coffre pose dans le niveau qui
	 * decide de sa forme, pas le Widget Blueprint -- un coffre de 12 cases et
	 * un de 30 ne se lisent pas bien avec le meme nombre de colonnes.
	 */
	UFUNCTION(BlueprintCallable, Category = "Inventaire")
	void SetColumnCount(int32 NewColumnCount);

protected:
	/** A nommer exactement "SlotGrid" dans le Widget Blueprint. */
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UUniformGridPanel> SlotGrid;

	/** Le widget de case a instancier : ton WBP_InventorySlot. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventaire")
	TSubclassOf<UInventorySlotWidget> SlotWidgetClass;

	/** Nombre de cases par ligne. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventaire", meta = (ClampMin = "1"))
	int32 ColumnCount = 6;

	/**
	 * La grille va chercher toute seule l'inventaire du pawn a sa construction.
	 *
	 * DECOCHE-LE sur la grille de coffre. Sans ca elle s'accrocherait au pawn
	 * dans son NativeConstruct -- qui s'execute avant que l'ecran ait eu le
	 * temps de lui donner le conteneur -- et afficherait le sac du joueur des
	 * deux cotes de l'ecran.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventaire")
	bool bAutoBindToPawn = true;

	/**
	 * Le panneau de detail pose a cote de la grille, dans le meme Widget
	 * Blueprint. A nommer "ItemDetailsPanel".
	 *
	 * Optionnel : la grille doit rester valide dans un WBP_Inventory ou ce
	 * panneau n'existe pas encore, sinon ajouter cette fonctionnalite casserait
	 * la compilation de l'inventaire entier tant que la mise en page n'est pas
	 * faite.
	 */
	UPROPERTY(meta = (BindWidgetOptional))
	TObjectPtr<UItemDetailsWidget> ItemDetailsPanel;

protected:
	/**
	 * Absorbe les depots qui ne visent aucune case. Sans ca, lacher un objet
	 * sur le fond du panneau declencherait OnDragCancelled -- et si un jour on
	 * y branche le drop au sol, le joueur jetterait son butin par accident.
	 */
	virtual bool NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation) override;

private:
	UFUNCTION()
	void HandleInventoryChanged();

	/** Cree ou supprime des cases pour coller au nombre de slots. */
	void RebuildGrid(int32 DesiredCount);

	/** Detruit toutes les cases : leur inventaire a change, elles sont perimees. */
	void ClearSlotWidgets();

	/** Retrouve l'inventaire du pawn possede. */
	UInventoryComponent* ResolveInventory() const;

	/**
	 * Point de passage UNIQUE du survol : met a jour l'index et le panneau de
	 * detail. Ne fait rien si l'index ne change pas.
	 */
	void SetHoveredSlot(int32 NewIndex);

	/** Pousse la case survolee dans le panneau, ou l'efface. */
	void UpdateItemDetails();

	TWeakObjectPtr<UInventoryComponent> BoundInventory;

	/** Destination du clic droit, recopiee sur chaque case creee. */
	TWeakObjectPtr<UInventoryComponent> QuickTransferTarget;

	int32 HoveredSlotIndex = INDEX_NONE;

	/** Voir SetItemDetailsEnabled(). */
	bool bItemDetailsEnabled = true;

	/** UPROPERTY obligatoire : sans ca le GC collecterait les cases. */
	UPROPERTY(Transient)
	TArray<TObjectPtr<UInventorySlotWidget>> SlotWidgets;
};
