#pragma once

#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "UI/FrontEndTypes.h"
#include "UIFrontEndFlowSubsystem.generated.h"

class UUIFrontEndScreenSet;
class UUIManagerSubsystem;
class UUILoadingScreen_Base;
class UZeusLoadingProfilesDataAsset;
class UZeusNetworkSubsystem;
class UZeusCharServerSubsystem;
struct FZeusMapDef;

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnZeusFrontEndStateChanged, EZeusFrontEndState, NewState);

/**
 * Orquestrador do fluxo de front-end. UGameInstanceSubsystem porque o
 * GameInstance persiste o travel boot→mundo (a máquina de estados não pode
 * morrer no OpenLevel).
 *
 * Responsável por: (a) dirigir a conexão via UZeusNetworkSubsystem;
 * (b) manter EZeusFrontEndState; (c) resolver estado→tela pelo
 * DA_FrontEndScreenSet e pedir ao UUIManagerSubsystem para empurrar na
 * camada certa; (d) detectar a chegada ao mundo.
 *
 * As telas (WBP) ainda não existem — virão do Zeus UMG Forge (§4.8).
 * Enquanto não registradas no DA, cada transição faz no-op + log.
 */
UCLASS(Config = Game)
class ZMMO_API UUIFrontEndFlowSubsystem : public UGameInstanceSubsystem
{
	GENERATED_BODY()

public:
	// USubsystem
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	virtual void Deinitialize() override;

	/**
	 * Ponto de entrada chamado pelo AZeusFrontEndPlayerController::BeginPlay
	 * (garante que os subsistemas já existem — ver aviso no header do
	 * ZeusNetworkSubsystem sobre GameInstance::Init). Cria o root layout,
	 * liga os delegates de rede e dispara Boot→Connecting.
	 */
	UFUNCTION(BlueprintCallable, Category = "FrontEnd")
	void StartFrontEnd();

	/**
	 *  Garante que o root layout (UUIPrimaryGameLayout_Base) esta no viewport.
	 *  Chamado pelo UIInGameFlowSubsystem::StartInGame apos OpenLevel — o
	 *  `OpenLevel` invalida widgets no viewport ("InvalidateAllWidgets") e o
	 *  RootLayout fica orfao. Idempotente: CreateAndAddRootLayout do
	 *  UIManagerSubsystem checa `IsInViewport` antes de recriar.
	 *
	 *  C++ puro (sem UFUNCTION) — Live Coding nao registra UFUNCTIONs novas;
	 *  consumidores externos chamam direto via C++ (UIInGameFlowSubsystem).
	 */
	void EnsureRootLayout();

	UFUNCTION(BlueprintCallable, Category = "FrontEnd")
	void SetState(EZeusFrontEndState NewState);

	UFUNCTION(BlueprintPure, Category = "FrontEnd")
	EZeusFrontEndState GetCurrentState() const { return CurrentState; }

	/** Mostra uma página interna do Lobby ("switch principal"). */
	UFUNCTION(BlueprintCallable, Category = "FrontEnd")
	void ShowLobbyPage(EZeusLobbyPage Page);

	/** "Voltar". Retorna true se o fluxo consumiu (tela base encaminha aqui). */
	UFUNCTION(BlueprintCallable, Category = "FrontEnd")
	bool RequestBack();

	/** Chamado pela tela Boot quando o usuário clica "Iniciar" (já conectado). */
	UFUNCTION(BlueprintCallable, Category = "FrontEnd")
	void RequestEnterLogin();

	/** Chamado pela tela Login após autenticação OK no CharServer. */
	UFUNCTION(BlueprintCallable, Category = "FrontEnd")
	void RequestEnterServerSelect();

	/**
	 * Memoriza o Realm escolhido na ServerSelect (UUID v4 string). Lido pelo
	 * CharSelect/CharCreate na hora de filtrar/criar personagens. Limpo no
	 * logout/back para ServerSelect.
	 */
	UFUNCTION(BlueprintCallable, Category = "FrontEnd|Realms")
	void SetSelectedRealmId(const FString& RealmId);

	UFUNCTION(BlueprintPure, Category = "FrontEnd|Realms")
	FString GetSelectedRealmId() const { return SelectedRealmId; }

	UFUNCTION(BlueprintCallable, Category = "FrontEnd|Realms")
	void ClearSelectedRealm();

	/**
	 *  Resolve `MapId` (uint16 recebido do CharServer) → row do DT_Maps.
	 *  Carrega o asset sync (PIE/dev OK; em prod considerar StreamableManager).
	 *  Retorna nullptr se MapId=0, DT_Maps nao configurado, ou mapId nao
	 *  encontrado.
	 */
	const FZeusMapDef* FindMapDef(int32 MapId) const;

	/**
	 *  Helper: resolve `MapId` no DT_Maps e retorna `ClientLevel.ToSoftObjectPath().GetLongPackageName()`
	 *  (ex.: `/Game/ThirdPerson/TestWorld`). String vazia se nao encontrado.
	 */
	UFUNCTION(BlueprintPure, Category = "FrontEnd|Maps")
	FString ResolveLevelPathByMapId(int32 MapId) const;

	/**
	 *  Inicia transicao pra mundo dado um `MapId`: faz `OpenLevel` com
	 *  `?game=/Script/ZMMO.ZeusGameMode`. No-op se `MapId` invalido ou
	 *  DT_Maps nao tem entrada.
	 */
	UFUNCTION(BlueprintCallable, Category = "FrontEnd|Maps")
	bool TravelToMapById(int32 MapId);

	/**
	 *  Memoriza a pose autoritativa do char vinda no `S_CHAR_SELECT_OK`
	 *  (pos salva no DB + yaw). Sobrevive ao travel porque vivemos no
	 *  GameInstance. Lida pelo `AZeusCharacter::BeginPlay` pra
	 *  reposicionar o pawn local em vez de spawnar no PlayerStart default
	 *  do level. Limpa apos consumo (a proxima sessao reenvia).
	 */
	UFUNCTION(BlueprintCallable, Category = "FrontEnd|Spawn")
	void SetPendingSpawnPose(FVector PosCm, float YawDeg);

	UFUNCTION(BlueprintCallable, Category = "FrontEnd|Spawn")
	bool ConsumePendingSpawnPose(FVector& OutPosCm, float& OutYawDeg);

	UPROPERTY(BlueprintAssignable, Category = "FrontEnd")
	FOnZeusFrontEndStateChanged OnStateChanged;

protected:
	/** DA com o mapa estado→tela. Config em DefaultGame.ini. */
	UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd")
	TSoftObjectPtr<UUIFrontEndScreenSet> ScreenSetAsset;

	/**
	 *  DataTable com FZeusMapDef. Source of truth client-side para
	 *  `MapId -> ClientLevel/spawns/displayName`. Configure em DefaultGame.ini:
	 *      [/Script/ZMMO.UIFrontEndFlowSubsystem]
	 *      MapsTableAsset=/Game/ZMMO/Data/World/DT_Maps.DT_Maps
	 */
	UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Maps")
	TSoftObjectPtr<UDataTable> MapsTableAsset;

	/**
	 *  DataAsset com os perfis de loading (etapas por contexto). Configure em
	 *  DefaultGame.ini:
	 *      [/Script/ZMMO.UIFrontEndFlowSubsystem]
	 *      LoadingProfilesAsset=/Game/ZMMO/Data/UI/Loading/DA_LoadingProfiles.DA_LoadingProfiles
	 */
	UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Loading")
	TSoftObjectPtr<UZeusLoadingProfilesDataAsset> LoadingProfilesAsset;

	/**
	 *  DataTable com dicas (rows = FZeusLoadingTipRow). Opcional — vazio
	 *  significa "sem dicas". Configure em DefaultGame.ini:
	 *      [/Script/ZMMO.UIFrontEndFlowSubsystem]
	 *      LoadingTipsAsset=/Game/ZMMO/Data/UI/Loading/DT_LoadingTips.DT_LoadingTips
	 */
	UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Loading")
	TSoftObjectPtr<UDataTable> LoadingTipsAsset;

private:
	void BindNetwork();
	void UnbindNetwork();
	UZeusNetworkSubsystem* GetZeusNetwork() const;
	UZeusCharServerSubsystem* GetCharServer() const;
	UUIManagerSubsystem* GetUIManager() const;
	UUIFrontEndScreenSet* GetScreenSet();

	/** Resolve a soft class do estado e empurra na camada Menu (no-op+log se vazio). */
	void ResolveAndPushScreen(EZeusFrontEndState State);

	UFUNCTION()
	void HandleConnected();

	UFUNCTION()
	void HandleConnectionFailed(FString Reason);

	UFUNCTION()
	void HandleCharDisconnected(int32 StatusCode, FString Reason);

	UFUNCTION()
	void HandleServerTravelRequested(const FString& MapName, const FString& MapPath);

	/**
	 *  Spawn do player local — fim da última etapa do loading EnteringWorld.
	 *  Assinatura espelha FZeusOnPlayerSpawned (FiveParams).
	 */
	UFUNCTION()
	void HandlePlayerSpawned(int64 EntityId, bool bIsLocal, FVector PosCm,
	                          float YawDeg, int64 ServerTimeMs);

	/** Disparado pela tela de loading quando todas as etapas viram Done. */
	UFUNCTION()
	void HandleLoadingComplete();

	void HandlePostLoadMap(UWorld* LoadedWorld);

	/** Resolve+carrega o DA de perfis (sync). */
	UZeusLoadingProfilesDataAsset* GetLoadingProfiles();

	/** Resolve+carrega o DT de dicas (sync). */
	UDataTable* GetLoadingTips();

	/**
	 * Dívida técnica (D5): ServerHello traz ThemeId, mas o UZeusNetworkSubsystem
	 * ainda não expõe um delegate/getter dedicado. Quando expuser, ligar aqui
	 * → UZeusThemeSubsystem::ApplyServerTheme. Sem isto, o tema resolve por
	 * calendário/Default (ARQUITETURA.md §1.10).
	 */
	void HandleServerHelloTheme(FName ThemeId);

	UPROPERTY(Transient)
	EZeusFrontEndState CurrentState = EZeusFrontEndState::None;

	UPROPERTY(Transient)
	FString SelectedRealmId;

	/** Pose autoritativa pendente (do S_CHAR_SELECT_OK). */
	UPROPERTY(Transient)
	FVector PendingSpawnPosCm = FVector::ZeroVector;

	UPROPERTY(Transient)
	float PendingSpawnYawDeg = 0.f;

	UPROPERTY(Transient)
	bool bHasPendingSpawnPose = false;

	UPROPERTY(Transient)
	TObjectPtr<UUIFrontEndScreenSet> ScreenSet;

	UPROPERTY(Transient)
	TObjectPtr<UZeusLoadingProfilesDataAsset> LoadingProfiles;

	UPROPERTY(Transient)
	TObjectPtr<UDataTable> LoadingTips;

	/**
	 *  Tela de loading atualmente em Modal. Weak porque o stack do CommonUI
	 *  é dono — se popar por outro caminho não queremos dangling.
	 */
	TWeakObjectPtr<UUILoadingScreen_Base> ActiveLoadingScreen;

	/**
	 *  Race fix: o widget de loading é criado via RequestAsyncLoad (assíncrono).
	 *  Se o servidor for rápido (TryFinalizeEnterWorld), HandlePostLoadMap e/ou
	 *  HandlePlayerSpawned podem disparar ANTES de ActiveLoadingScreen existir
	 *  — nesse caso o MarkStepDone vira no-op e o loading fica eterno.
	 *  Memoizamos os StepIds aqui e drenamos quando a tela for criada.
	 */
	TSet<FName> PendingLoadingSteps_;

	bool bNetBound = false;
	bool bTravelingToWorld = false;
	FDelegateHandle PostLoadMapHandle;
};
