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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "MainMenuPlayerController.generated.h"

class ACharacterPreviewActor;
class AMenuCameraSpot;
class UCharacterCustomizationWidget;
class UConfirmDialogWidget;
class UMainMenuWidget;
class UScreenFadeComponent;
class USettingsMenuWidget;

/**
 * Ce qu'on fait une fois l'ecran devenu noir.
 *
 * Quitter n'y figure pas : la sortie est seche, sans fondu, une fois la
 * confirmation obtenue.
 */
enum class EMenuPendingAction : uint8
{
	None,

	/**
	 * Le fondu part AVANT la creation de la session, qui est asynchrone. La map
	 * n'est donc chargee qu'a la reponse du service en ligne -- ou tout de suite
	 * si celui-ci refuse, puisqu'un echec reseau ne doit pas empecher de jouer.
	 */
	StartGame,

	/**
	 * On ne quitte PAS la map : le mannequin est pose dans MenuScene, on ne fait
	 * que porter la vue sur lui. Le fondu sert a masquer la bascule -- un
	 * travelling depuis l'angle de menu traverserait le decor.
	 */
	OpenCustomization,

	/** Retour du mannequin vers un angle de menu, meme mecanique en sens inverse. */
	CloseCustomization
};

/**
 * Controller du menu principal. Il ne possede aucun pawn : il pose simplement
 * sa vue sur une AMenuCameraSpot tiree au hasard et affiche le menu par-dessus.
 *
 * Comme dans le jeu, l'UI vit sur le controller et pas sur un acteur du monde.
 */
UCLASS()
class EMBERWILD_API AMainMenuPlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	AMainMenuPlayerController();

	/**
	 * Fondu au noir, creation de la session, puis ouverture de la map en
	 * serveur d'ecoute.
	 *
	 * Il n'existe qu'UN seul bouton pour lancer une partie : toute partie est
	 * une partie en ligne, meme jouee seul. Distinguer "solo" et "heberger"
	 * obligerait le joueur a decider avant de savoir si un ami se connectera,
	 * et surtout a relancer sa partie pour changer d'avis.
	 *
	 * On ne rejoint jamais depuis ce menu : l'arrivee se fait uniquement par
	 * invitation Steam, geree par USessionSubsystem quelle que soit la map
	 * affichee a cet instant.
	 */
	UFUNCTION(BlueprintCallable, Category = "Menu")
	void StartGame();

	/** Ouvre la demande de confirmation. Ne quitte rien par elle-meme. */
	UFUNCTION(BlueprintCallable, Category = "Menu")
	void QuitGame();

	/** Ouvre l'ecran de reglages par-dessus le menu. */
	UFUNCTION(BlueprintCallable, Category = "Menu")
	void OpenSettings();

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

	/**
	 * Ta map de jeu.
	 *
	 * TSoftObjectPtr et non une reference dure : une reference dure ferait
	 * charger la map de jeu EN MEME TEMPS que le menu, alors qu'on ne la veut
	 * qu'au clic sur Jouer. C'est l'equivalent d'une Addressable en Unity, par
	 * opposition a un champ public qui tire tout l'asset avec lui.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu")
	TSoftObjectPtr<UWorld> GameLevel;

	/** Ton WBP_MainMenu. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu")
	TSubclassOf<UMainMenuWidget> MainMenuClass;

	/**
	 * Ton WBP_ConfirmDialog. Si tu le laisses vide, le bouton Quitter ferme le
	 * jeu directement -- pratique pour tester, mais laisse-le assigne.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu")
	TSubclassOf<UConfirmDialogWidget> QuitDialogClass;

	/** La question posee avant de fermer le jeu. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu")
	FText QuitDialogMessage;

	/** Ton WBP_Settings, exactement le meme que celui du menu de pause. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu")
	TSubclassOf<USettingsMenuWidget> SettingsMenuClass;

	/**
	 * Ton WBP_CharacterCustomization. Laisse-le vide pour retrouver l'ancien
	 * comportement -- Jouer lance directement la partie. Pratique pour tester
	 * autre chose sans traverser l'ecran a chaque fois.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu|Customization")
	TSubclassOf<UCharacterCustomizationWidget> CustomizationMenuClass;

	/**
	 * Le mannequin pose dans MenuScene. Cherche par classe au BeginPlay plutot
	 * qu'assigne ici : une reference d'acteur dans un CDO de controller ne
	 * survit pas au chargement de la map.
	 */
	UPROPERTY(Transient, BlueprintReadOnly, Category = "Menu|Customization")
	TObjectPtr<ACharacterPreviewActor> CharacterPreview;

	UPROPERTY(Transient, BlueprintReadOnly, Category = "Menu|Customization")
	TObjectPtr<UCharacterCustomizationWidget> CustomizationMenu;

	/**
	 * Duree de CHAQUE moitie du fondu entre le menu et l'ecran de customisation.
	 * Le passage complet dure donc le double.
	 *
	 * Plus court que le fondu de depart vers la partie : on ne change pas de
	 * map, il n'y a rien a charger, et une attente ici ne serait qu'une attente.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu|Customization", meta = (ClampMin = "0.05"))
	float CustomizationFadeDuration = 0.35f;

	/** Fondu d'arrivee sur le menu, au lancement du jeu. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu", meta = (ClampMin = "0.1"))
	float OpeningFadeDuration = 1.5f;

	/** Fondu de depart, quand on quitte le menu pour la partie. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu", meta = (ClampMin = "0.1"))
	float LeaveFadeDuration = 1.f;

	/** Le fondu, partage avec AFpsPlayerController. Assigne-lui ton WBP_Fade. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Menu")
	TObjectPtr<UScreenFadeComponent> ScreenFade;

	UPROPERTY(Transient, BlueprintReadOnly, Category = "Menu")
	TObjectPtr<UMainMenuWidget> MainMenu;

	UPROPERTY(Transient, BlueprintReadOnly, Category = "Menu")
	TObjectPtr<UConfirmDialogWidget> QuitDialog;

	UPROPERTY(Transient, BlueprintReadOnly, Category = "Menu")
	TObjectPtr<USettingsMenuWidget> SettingsMenu;

private:
	/** Tire un angle de vue au hasard, en evitant celui du dernier passage. */
	AMenuCameraSpot* PickCameraSpot();

	/** L'eclaircissement lui-meme, apres la chauffe du decor. */
	void StartOpeningFade();

	/** Le fondu vient de se terminer : on execute l'action mise de cote. */
	UFUNCTION()
	void HandleFadeFinished();

	/** Oui : on ferme le jeu, immediatement. */
	UFUNCTION()
	void HandleQuitConfirmed();

	/** Non : on referme la boite et on rend la main au menu. */
	UFUNCTION()
	void HandleQuitCancelled();

	/** L'ecran de reglages demande a se fermer : on rend la main au menu. */
	UFUNCTION()
	void HandleSettingsCloseRequested();

	/**
	 * Ouvre l'ecran de customisation et pose la vue sur le mannequin.
	 *
	 * L'ordre compte : SetViewTarget d'abord, widget ensuite. L'inverse
	 * afficherait la colonne par-dessus l'ancien cadrage pendant toute la duree
	 * du blend.
	 */
	void OpenCustomization();

	/** Le joueur a valide : on soumet l'apparence, puis on lance la partie. */
	UFUNCTION()
	void HandleCustomizationConfirmed();

	/** Le joueur renonce : retour au menu, rien n'est ecrit. */
	UFUNCTION()
	void HandleCustomizationCancelled();

	/** Referme l'ecran et rend la vue a l'angle de menu. */
	void CloseCustomization();

	/**
	 * La session n'a pas pu etre creee -- Steam eteint, hors ligne, service en
	 * panne. On charge la map QUAND MEME.
	 *
	 * C'est un choix delibere : un joueur qui clique Jouer veut jouer. Le rendre
	 * a son menu avec un message d'erreur reseau alors que rien ne l'empeche de
	 * jouer seul serait une punition pour une panne qui n'est pas la sienne. Il
	 * perd seulement la possibilite d'inviter, et le bouton Inviter le dira.
	 */
	UFUNCTION()
	void HandleHostFailed(FText Reason);

	/**
	 * Echec de connexion sur invitation : on remonte le fondu et on rend le menu.
	 * Ici pas de repli possible, la partie visee est celle d'un ami.
	 */
	UFUNCTION()
	void HandleJoinFailed(FText Reason);

	/**
	 * Une invitation vient d'aboutir : on fond au noir avant que le voyage
	 * parte. C'est le seul chemin d'entree en partie qui ne passait pas par le
	 * bouton Jouer, et donc le seul qui coupait l'image d'un coup.
	 */
	UFUNCTION()
	void HandleTravelPending();

	/** Ferme le jeu sans rien demander. */
	void CloseGame();

	EMenuPendingAction PendingAction = EMenuPendingAction::None;
};
