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

#pragma once

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

class UButton;
class UProgressBar;
class USlider;
class UTextBlock;

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSettingRowIndexChanged, int32, NewIndex);

/**
 * Une ligne de reglage : un libelle, une fleche gauche, une valeur, une fleche
 * droite, et une barre de remplissage optionnelle derriere la valeur.
 *
 * TOUT reglage est ramene a un index dans une liste de paliers, y compris les
 * nombres : "Champ de vision, 70 a 120 par pas de 5" devient un index de 0 a 10.
 * La ligne ne connait donc jamais que CurrentIndex et StepCount, et c'est ce qui
 * permet de n'avoir qu'UN SEUL WBP a dessiner pour tout le menu, qu'il s'agisse
 * d'un choix (Maintien / Bascule) ou d'une valeur continue.
 *
 * Elle ne sait pas ce qu'elle regle : elle previent via OnIndexChanged, et c'est
 * l'onglet qui traduit l'index en appel sur USurvivalUserSettings. Meme principe
 * que UConfirmDialogWidget, qui ignore ce qu'il confirme.
 *
 * Classe Abstract : on cree un WBP_SettingRow qui en herite.
 */
UCLASS(Abstract)
class EMBERWILD_API USettingRowWidget : public UUserWidget
{
	GENERATED_BODY()

public:
	virtual void NativeOnInitialized() override;

	/** Previent d'un changement venant du joueur, jamais d'un rafraichissement. */
	UPROPERTY(BlueprintAssignable, Category = "Reglages")
	FOnSettingRowIndexChanged OnIndexChanged;

	/**
	 * Mode choix : la valeur affichee est prise telle quelle dans Options.
	 * Sert aussi bien a un oui/non qu'a une liste de qualites.
	 */
	UFUNCTION(BlueprintCallable, Category = "Reglages")
	void SetupAsChoice(const FText& Label, const TArray<FText>& Options, int32 Index);

	/**
	 * Mode nombre : la valeur affichee est calculee a partir de l'index.
	 *
	 * @param Step      pas fin, celui du glissement. C'est lui qui definit la
	 *                  granularite de l'index.
	 * @param ArrowStep bond des fleches, forcement un multiple de Step. Les
	 *                  fleches se calent sur le multiple suivant plutot que
	 *                  d'ajouter betement leur bond : depuis 37 avec un bond de
	 *                  5 on arrive a 40, pas a 42.
	 * @param Suffix    unite ("%", "°"), peut rester vide.
	 */
	UFUNCTION(BlueprintCallable, Category = "Reglages")
	void SetupAsNumber(const FText& Label, float Min, float Max, float Step, float ArrowStep, float Value, const FText& Suffix);

	UFUNCTION(BlueprintPure, Category = "Reglages")
	int32 GetIndex() const { return CurrentIndex; }

	/** Reconvertit l'index en valeur reelle. Sans objet en mode choix. */
	UFUNCTION(BlueprintPure, Category = "Reglages")
	float GetNumericValue() const;

	/**
	 * Repositionne la ligne sans rien diffuser.
	 *
	 * A utiliser pour tout rafraichissement venant des reglages : diffuser ici
	 * ferait boucler l'onglet sur lui-meme (il reecrit le reglage, qui rafraichit
	 * la ligne, qui rediffuse...).
	 */
	UFUNCTION(BlueprintCallable, Category = "Reglages")
	void SetIndexSilently(int32 NewIndex);

	/** Repositionne la ligne sur une valeur reelle, sans rien diffuser. */
	UFUNCTION(BlueprintCallable, Category = "Reglages")
	void SetNumericValueSilently(float Value);

	/**
	 * Derniere valeur que le JOUEUR peut atteindre, les suivantes restant
	 * affichables par le code.
	 *
	 * Sert a "Personnalise" en qualite globale : c'est un etat dans lequel on
	 * tombe en bougeant un curseur individuel, jamais un choix qu'on fait.
	 * Sans ca la fleche droite proposerait de le "selectionner", ce qui ne veut
	 * rien dire. INDEX_NONE pour lever la limite.
	 */
	UFUNCTION(BlueprintCallable, Category = "Reglages")
	void SetMaxSelectableIndex(int32 InMaxSelectableIndex);

protected:
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UTextBlock> LabelText;

	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UTextBlock> ValueText;

	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UButton> LeftArrowButton;

	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UButton> RightArrowButton;

	/**
	 * Le REMPLISSAGE visible, et rien d'autre.
	 *
	 * Le Slider d'Unreal ne sait pas dessiner de portion remplie : il rend une
	 * barre uniforme plus une poignee. On empile donc les deux -- cette
	 * ProgressBar pour le rendu, le Slider transparent par-dessus pour le
	 * geste. C'est le montage habituel, pas un bricolage.
	 */
	UPROPERTY(meta = (BindWidgetOptional))
	TObjectPtr<UProgressBar> ValueBar;

	/**
	 * Le GESTE, et rien d'autre : a styler entierement transparent dans le WBP.
	 *
	 * Sa course est exprimee en INDEX de palier (0 a StepCount-1) et non dans
	 * l'unite du reglage -- c'est ce qui garantit qu'un glissement ne peut pas
	 * produire une valeur hors palier, que le setter refuserait ensuite.
	 *
	 * Les deux sont facultatifs : une ligne de choix (Maintien / Bascule) les
	 * replie tous les deux, il n'y a rien a doser.
	 */
	UPROPERTY(meta = (BindWidgetOptional))
	TObjectPtr<USlider> ValueSlider;

private:
	UFUNCTION()
	void HandleLeftClicked();

	UFUNCTION()
	void HandleRightClicked();

	/** Glissement souris : on arrondit sur le palier le plus proche. */
	UFUNCTION()
	void HandleSliderValueChanged(float NewValue);

	/** Deplace l'index de Delta paliers et diffuse, si le mouvement est possible. */
	void StepIndex(int32 Delta);

	/** Index vise par une fleche : le multiple de son bond suivant ou precedent. */
	int32 ArrowTargetIndex(int32 Direction) const;

	/** Plus grand index atteignable par le joueur. */
	int32 MaxSelectable() const;

	/** Reecrit le texte, la barre et l'etat des deux fleches. */
	void Refresh();

	/** Vide en mode nombre. */
	TArray<FText> ChoiceLabels;

	FText NumericSuffix;

	float NumericMin = 0.f;
	float NumericStep = 1.f;

	int32 CurrentIndex = 0;

	/** Nombre de paliers fins, donc d'index valides. Toujours au moins 1. */
	int32 StepCount = 1;

	/** Bond des fleches, exprime en nombre de paliers fins. 1 en mode choix. */
	int32 ArrowIndexDelta = 1;

	/** INDEX_NONE = aucune limite, tous les paliers sont selectionnables. */
	int32 MaxSelectableIndex = INDEX_NONE;

	bool bNumeric = false;

	/**
	 * Vrai pendant qu'on repositionne le curseur depuis le code.
	 *
	 * USlider::SetValue ne rediffuse pas son delegue aujourd'hui, mais s'en
	 * remettre a ce detail d'implementation coute une boucle infinie le jour ou
	 * il change.
	 */
	bool bRefreshingSlider = false;
};
