#include "UIFrontEndFlowSubsystem.h"

#include "ZMMO.h"
#include "Data/World/MapDef.h"
#include "UIFrontEndScreenSet.h"
#include "UIManagerSubsystem.h"
#include "UIPrimaryGameLayout_Base.h"
#include "UI/Common/UILoadingProfilesDataAsset.h"
#include "UI/Common/UILoadingScreen_Base.h"
#include "UI/UILayerTags.h"
#include "ZMMOGameInstance.h"
#include "ZMMOThemeSubsystem.h"
#include "ZeusNetworkSubsystem.h"
#include "ZeusCharServerSubsystem.h"
#include "CommonActivatableWidget.h"
#include "Engine/DataTable.h"
#include "Engine/GameInstance.h"
#include "Engine/LocalPlayer.h"
#include "UObject/UObjectGlobals.h"
#include "Engine/AssetManager.h"
#include "Engine/StreamableManager.h"
#include "Kismet/GameplayStatics.h"

void UUIFrontEndFlowSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	// Detecta chegada ao mundo após o OpenLevel disparado por travel.
	PostLoadMapHandle = FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(
		this, &UUIFrontEndFlowSubsystem::HandlePostLoadMap);
}

void UUIFrontEndFlowSubsystem::Deinitialize()
{
	if (PostLoadMapHandle.IsValid())
	{
		FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(PostLoadMapHandle);
		PostLoadMapHandle.Reset();
	}
	UnbindNetwork();
	Super::Deinitialize();
}

void UUIFrontEndFlowSubsystem::StartFrontEnd()
{
	EnsureRootLayout();
	BindNetwork();

	SetState(EZMMOFrontEndState::Boot);

	// Pré-login fala com o ZeusCharServer (WebSocket), NÃO com o world server
	// UDP. A tela Boot observa o CharServer e libera o botão ao conectar.
	if (UZeusCharServerSubsystem* Char = GetCharServer())
	{
		Char->ConnectToDefaultCharServer(); // lê CharServerUrl de Project Settings
	}
	else
	{
		UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: ZeusCharServerSubsystem indisponível; sem conexão de Boot."));
	}
}

void UUIFrontEndFlowSubsystem::EnsureRootLayout()
{
	UUIManagerSubsystem* Mgr = GetUIManager();
	if (!Mgr) { return; }
	if (Mgr->IsRootLayoutReady())
	{
		return;  // ja' no viewport
	}
	UUIFrontEndScreenSet* SS = GetScreenSet();
	if (!SS || SS->RootLayoutClass.IsNull())
	{
		UE_LOG(LogZMMO, Warning,
			TEXT("FrontEndFlow::EnsureRootLayout: RootLayoutClass ausente em ScreenSet"));
		return;
	}
	TSubclassOf<UUIPrimaryGameLayout_Base> LayoutClass = SS->RootLayoutClass.LoadSynchronous();
	Mgr->CreateAndAddRootLayout(LayoutClass);
	UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow::EnsureRootLayout: RootLayout recriado"));
}

void UUIFrontEndFlowSubsystem::SetState(EZMMOFrontEndState NewState)
{
	if (NewState == CurrentState)
	{
		return;
	}

	CurrentState = NewState;
	UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: estado → %s"),
		*UEnum::GetValueAsString(NewState));
	OnStateChanged.Broadcast(NewState);

	const FGameplayTag MenuLayer = ZMMOUITags::UI_Layer_Menu.GetTag();
	const FGameplayTag ModalLayer = ZMMOUITags::UI_Layer_Modal.GetTag();

	switch (NewState)
	{
	case EZMMOFrontEndState::Boot:
	case EZMMOFrontEndState::Connecting:
	case EZMMOFrontEndState::Login:
	case EZMMOFrontEndState::ServerSelect:
	case EZMMOFrontEndState::Lobby:
		// Telas do front-end vivem na camada Menu (uma por vez).
		if (UUIManagerSubsystem* Mgr = GetUIManager())
		{
			Mgr->ClearLayer(MenuLayer);
		}
		ResolveAndPushScreen(NewState);
		break;

	case EZMMOFrontEndState::EnteringWorld:
		// Loading/handoff por cima de tudo (camada Modal).
		ResolveAndPushScreen(NewState);
		break;

	case EZMMOFrontEndState::InWorld:
		// HUD de gameplay assume — limpa front-end e loading.
		if (UUIManagerSubsystem* Mgr = GetUIManager())
		{
			Mgr->ClearLayer(ModalLayer);
			Mgr->ClearLayer(MenuLayer);
		}
		break;

	case EZMMOFrontEndState::None:
	default:
		break;
	}
}

void UUIFrontEndFlowSubsystem::ShowLobbyPage(EZMMOLobbyPage Page)
{
	UUIFrontEndScreenSet* SS = GetScreenSet();
	UUIManagerSubsystem* Mgr = GetUIManager();
	if (!SS || !Mgr)
	{
		return;
	}

	const TSoftClassPtr<UCommonActivatableWidget> Soft = SS->GetLobbyPage(Page);
	if (Soft.IsNull())
	{
		UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: página de Lobby %s não configurada (DA vazio)."),
			*UEnum::GetValueAsString(Page));
		return;
	}

	const FGameplayTag MenuLayer = ZMMOUITags::UI_Layer_Menu.GetTag();
	const FSoftObjectPath Path = Soft.ToSoftObjectPath();
	UAssetManager::GetStreamableManager().RequestAsyncLoad(
		Path,
		FStreamableDelegate::CreateWeakLambda(this, [this, Soft, MenuLayer]()
		{
			if (UUIManagerSubsystem* M = GetUIManager())
			{
				if (UClass* Cls = Soft.Get())
				{
					M->PushScreenToLayer(MenuLayer, Cls);
				}
			}
		}));
}

bool UUIFrontEndFlowSubsystem::RequestBack()
{
	switch (CurrentState)
	{
	case EZMMOFrontEndState::Login:
		// Volta à Boot (CharServer segue conectado; só re-mostra a tela).
		SetState(EZMMOFrontEndState::Boot);
		return true;
	case EZMMOFrontEndState::ServerSelect:
		SetState(EZMMOFrontEndState::Login);
		return true;
	case EZMMOFrontEndState::Lobby:
		SetState(EZMMOFrontEndState::ServerSelect);
		return true;
	default:
		return false;
	}
}

void UUIFrontEndFlowSubsystem::ResolveAndPushScreen(EZMMOFrontEndState State)
{
	UUIFrontEndScreenSet* SS = GetScreenSet();
	if (!SS)
	{
		UE_LOG(LogZMMO, Warning,
			TEXT("FrontEndFlow: ScreenSet não configurado; tela de %s ignorada."),
			*UEnum::GetValueAsString(State));
		return;
	}

	const TSoftClassPtr<UCommonActivatableWidget> Soft = SS->GetScreenForState(State);
	if (Soft.IsNull())
	{
		UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: Screen for state %s not configured."),
			*UEnum::GetValueAsString(State));
		return;
	}

	UUIManagerSubsystem* Mgr = GetUIManager();
	if (!Mgr)
	{
		return;
	}

	const FGameplayTag Layer = (State == EZMMOFrontEndState::EnteringWorld)
		? ZMMOUITags::UI_Layer_Modal.GetTag()
		: ZMMOUITags::UI_Layer_Menu.GetTag();

	const FSoftObjectPath Path = Soft.ToSoftObjectPath();
	UAssetManager::GetStreamableManager().RequestAsyncLoad(
		Path,
		FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer, State]()
		{
			if (UUIManagerSubsystem* M = GetUIManager())
			{
				if (UClass* Cls = Soft.Get())
				{
					UCommonActivatableWidget* Pushed = M->PushScreenToLayer(Layer, Cls);

					// Loading: configura logo após o push e marca etapa "Travel"
					// (chegamos aqui via HandleServerTravelRequested → SetState).
					if (State == EZMMOFrontEndState::EnteringWorld)
					{
						if (UUILoadingScreen_Base* Loading = Cast<UUILoadingScreen_Base>(Pushed))
						{
							ActiveLoadingScreen = Loading;
							Loading->Configure(EZMMOLoadingContext::FrontEndEnteringWorld,
								GetLoadingProfiles(), GetLoadingTips());
							Loading->OnLoadingComplete.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleLoadingComplete);
							Loading->MarkStepDone(TEXT("Travel"));

							// Drena steps que dispararam ANTES do widget existir
							// (HandlePostLoadMap/HandlePlayerSpawned podem ter
							// rodado durante o RequestAsyncLoad). Sem isso o
							// loading fica eterno se o servidor for rápido demais.
							for (const FName StepId : PendingLoadingSteps_)
							{
								Loading->MarkStepDone(StepId);
							}
							PendingLoadingSteps_.Reset();
						}
					}
				}
			}
		}));
}

UZeusNetworkSubsystem* UUIFrontEndFlowSubsystem::GetZeusNetwork() const
{
	const UGameInstance* GI = GetGameInstance();
	return GI ? GI->GetSubsystem<UZeusNetworkSubsystem>() : nullptr;
}

UZeusCharServerSubsystem* UUIFrontEndFlowSubsystem::GetCharServer() const
{
	const UGameInstance* GI = GetGameInstance();
	return GI ? GI->GetSubsystem<UZeusCharServerSubsystem>() : nullptr;
}

UUIManagerSubsystem* UUIFrontEndFlowSubsystem::GetUIManager() const
{
	if (const UGameInstance* GI = GetGameInstance())
	{
		if (ULocalPlayer* LP = GI->GetFirstGamePlayer())
		{
			return LP->GetSubsystem<UUIManagerSubsystem>();
		}
	}
	return nullptr;
}

UUIFrontEndScreenSet* UUIFrontEndFlowSubsystem::GetScreenSet()
{
	if (ScreenSet)
	{
		return ScreenSet;
	}
	if (!ScreenSetAsset.IsNull())
	{
		ScreenSet = ScreenSetAsset.LoadSynchronous();
	}
	else
	{
		UE_LOG(LogZMMO, Warning,
			TEXT("FrontEndFlow: ScreenSetAsset não setado em DefaultGame.ini; telas farão no-op."));
	}
	return ScreenSet;
}

void UUIFrontEndFlowSubsystem::BindNetwork()
{
	if (bNetBound)
	{
		return;
	}
	// CharServer (WebSocket) dirige o pré-login: Boot→Login.
	if (UZeusCharServerSubsystem* Char = GetCharServer())
	{
		Char->OnConnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnected);
		Char->OnConnectionFailed.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed);
		Char->OnDisconnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleCharDisconnected);
	}
	// UDP (world server) só interessa para o handoff de travel.
	if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
	{
		Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
		Zeus->OnPlayerSpawned.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
	}
	bNetBound = true;
}

void UUIFrontEndFlowSubsystem::UnbindNetwork()
{
	if (!bNetBound)
	{
		return;
	}
	if (UZeusCharServerSubsystem* Char = GetCharServer())
	{
		Char->OnConnected.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnected);
		Char->OnConnectionFailed.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed);
		Char->OnDisconnected.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleCharDisconnected);
	}
	if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
	{
		Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
		Zeus->OnPlayerSpawned.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
	}
	bNetBound = false;
}

void UUIFrontEndFlowSubsystem::HandleConnected()
{
	// NÃO auto-avança: a tela Boot libera o botão "Iniciar"; o usuário clica
	// (→ RequestEnterLogin). Mantém o estado Boot.
	UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: CharServer conectado (estado %s)."),
		*UEnum::GetValueAsString(CurrentState));
}

void UUIFrontEndFlowSubsystem::HandleConnectionFailed(FString Reason)
{
	// Permanece em Boot; a tela mostra erro e o CharServer pode re-tentar.
	UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: conexão ao CharServer falhou (%s)."), *Reason);
}

void UUIFrontEndFlowSubsystem::HandleCharDisconnected(int32 StatusCode, FString Reason)
{
	UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: CharServer desconectou (code=%d, %s)."),
		StatusCode, *Reason);
	// Se caiu antes de entrar no mundo, volta a Boot para reconectar.
	if (CurrentState != EZMMOFrontEndState::InWorld &&
		CurrentState != EZMMOFrontEndState::EnteringWorld &&
		CurrentState != EZMMOFrontEndState::Boot)
	{
		SetState(EZMMOFrontEndState::Boot);
		if (UZeusCharServerSubsystem* Char = GetCharServer())
		{
			Char->ConnectToDefaultCharServer();
		}
	}
}

void UUIFrontEndFlowSubsystem::RequestEnterLogin()
{
	if (CurrentState == EZMMOFrontEndState::Boot)
	{
		SetState(EZMMOFrontEndState::Login);
	}
}

void UUIFrontEndFlowSubsystem::RequestEnterServerSelect()
{
	if (CurrentState == EZMMOFrontEndState::Login)
	{
		SetState(EZMMOFrontEndState::ServerSelect);
	}
}

void UUIFrontEndFlowSubsystem::SetSelectedWorldId(const FString& WorldId)
{
	SelectedWorldId = WorldId;
	UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: mundo selecionado = %s"), *SelectedWorldId);
}

void UUIFrontEndFlowSubsystem::ClearSelectedWorld()
{
	if (!SelectedWorldId.IsEmpty())
	{
		UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: limpando mundo selecionado (%s)"), *SelectedWorldId);
		SelectedWorldId.Reset();
	}
}

void UUIFrontEndFlowSubsystem::HandleServerTravelRequested(const FString& MapName, const FString& MapPath)
{
	UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: travel pedido pelo servidor → %s (%s)."), *MapName, *MapPath);
	bTravelingToWorld = true;
	SetState(EZMMOFrontEndState::EnteringWorld);

	if (!MapPath.IsEmpty())
	{
		// Forca ZMMOGameMode em qualquer mapa que venha do WorldServer.
		// World Settings do .umap podem ter override herdado de L_FrontEnd
		// (GameModeOverride=ZMMOFrontEndGameMode) — sobrescrevemos via URL
		// option pra nao depender de cada artista configurar.
		// Evolucao natural: quando o WorldServer enviar GameMode no
		// S_TRAVEL_TO_MAP (wire estendido), trocar este literal por
		// `?game=` + valor recebido do server.
		const FString Options = TEXT("game=/Script/ZMMO.ZMMOGameMode");
		UGameplayStatics::OpenLevel(GetGameInstance(), FName(*MapPath), /*bAbsolute=*/true, Options);
	}
}

void UUIFrontEndFlowSubsystem::HandlePostLoadMap(UWorld* /*LoadedWorld*/)
{
	if (!bTravelingToWorld)
	{
		return;
	}

	// Loading dinâmico: marca a etapa "MapLoaded" e espera o spawn antes de
	// liberar o InWorld (a tela limpa via OnLoadingComplete). Fallback: se
	// não há screen ativa (DA não configurado), comportamento legado —
	// transita já pra InWorld pra não travar.
	if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
	{
		Loading->MarkStepDone(TEXT("MapLoaded"));
		return;
	}

	// Race fix: widget ainda em RequestAsyncLoad; memoiza pra drenar quando
	// a tela for criada (ver lambda em ResolveAndPushScreen). Não fallback
	// pra InWorld — esperamos a tela existir e o Spawn chegar.
	PendingLoadingSteps_.Add(TEXT("MapLoaded"));
}

void UUIFrontEndFlowSubsystem::HandlePlayerSpawned(int32 /*EntityId*/, bool bIsLocal,
                                                    FVector /*PosCm*/, float /*YawDeg*/,
                                                    int64 /*ServerTimeMs*/)
{
	if (!bIsLocal) return;
	if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
	{
		Loading->MarkStepDone(TEXT("Spawn"));
		return;
	}

	// Race fix: spawn pode chegar ANTES do widget existir se o servidor for
	// rápido (TryFinalizeEnterWorld) e o RequestAsyncLoad lento. Memoiza
	// pra drenar no callback do async load.
	if (bTravelingToWorld)
	{
		PendingLoadingSteps_.Add(TEXT("Spawn"));
	}
}

void UUIFrontEndFlowSubsystem::HandleLoadingComplete()
{
	if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
	{
		Loading->OnLoadingComplete.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleLoadingComplete);
	}
	ActiveLoadingScreen.Reset();
	PendingLoadingSteps_.Reset();
	bTravelingToWorld = false;
	SetState(EZMMOFrontEndState::InWorld);
}

UZMMOLoadingProfilesDataAsset* UUIFrontEndFlowSubsystem::GetLoadingProfiles()
{
	if (LoadingProfiles) return LoadingProfiles;
	if (!LoadingProfilesAsset.IsNull())
	{
		LoadingProfiles = LoadingProfilesAsset.LoadSynchronous();
	}
	return LoadingProfiles;
}

UDataTable* UUIFrontEndFlowSubsystem::GetLoadingTips()
{
	if (LoadingTips) return LoadingTips;
	if (!LoadingTipsAsset.IsNull())
	{
		LoadingTips = LoadingTipsAsset.LoadSynchronous();
	}
	return LoadingTips;
}

void UUIFrontEndFlowSubsystem::HandleServerHelloTheme(FName ThemeId)
{
	// Dívida técnica (D5): ainda não há gancho no ZeusNetworkSubsystem para o
	// ThemeId do ServerHello. Mantido pronto para quando o plugin expuser.
	if (const UGameInstance* GI = GetGameInstance())
	{
		if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
		{
			Theme->ApplyServerTheme(ThemeId);
		}
	}
}

const FZMMOMapDef* UUIFrontEndFlowSubsystem::FindMapDef(int32 MapId) const
{
	if (MapId <= 0) return nullptr;
	if (MapsTableAsset.IsNull())
	{
		UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: MapsTableAsset nao configurado — adicione DT_Maps em DefaultGame.ini"));
		return nullptr;
	}
	const UDataTable* Table = MapsTableAsset.LoadSynchronous();
	if (!Table) return nullptr;

	for (const TPair<FName, uint8*>& Row : Table->GetRowMap())
	{
		const FZMMOMapDef* Def = reinterpret_cast<const FZMMOMapDef*>(Row.Value);
		if (Def && Def->MapId == MapId)
		{
			return Def;
		}
	}
	return nullptr;
}

FString UUIFrontEndFlowSubsystem::ResolveLevelPathByMapId(int32 MapId) const
{
	const FZMMOMapDef* Def = FindMapDef(MapId);
	if (!Def) return FString();
	const FSoftObjectPath ObjPath = Def->ClientLevel.ToSoftObjectPath();
	if (!ObjPath.IsValid()) return FString();
	return ObjPath.GetLongPackageName();
}

void UUIFrontEndFlowSubsystem::SetPendingSpawnPose(FVector PosCm, float YawDeg)
{
	PendingSpawnPosCm = PosCm;
	PendingSpawnYawDeg = YawDeg;
	bHasPendingSpawnPose = true;
	UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: PendingSpawnPose set pos=(%.1f,%.1f,%.1f) yaw=%.1f"),
		PosCm.X, PosCm.Y, PosCm.Z, YawDeg);
}

bool UUIFrontEndFlowSubsystem::ConsumePendingSpawnPose(FVector& OutPosCm, float& OutYawDeg)
{
	if (!bHasPendingSpawnPose) return false;
	OutPosCm = PendingSpawnPosCm;
	OutYawDeg = PendingSpawnYawDeg;
	bHasPendingSpawnPose = false;
	UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: PendingSpawnPose consumido pos=(%.1f,%.1f,%.1f) yaw=%.1f"),
		OutPosCm.X, OutPosCm.Y, OutPosCm.Z, OutYawDeg);
	return true;
}

bool UUIFrontEndFlowSubsystem::TravelToMapById(int32 MapId)
{
	const FString MapPath = ResolveLevelPathByMapId(MapId);
	if (MapPath.IsEmpty())
	{
		UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: TravelToMapById(%d) falhou — sem entrada no DT_Maps"), MapId);
		return false;
	}
	UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: TravelToMapById(%d) -> %s"), MapId, *MapPath);
	bTravelingToWorld = true;
	SetState(EZMMOFrontEndState::EnteringWorld);
	const FString Options = TEXT("game=/Script/ZMMO.ZMMOGameMode");
	UGameplayStatics::OpenLevel(GetGameInstance(), FName(*MapPath), /*bAbsolute=*/true, Options);
	return true;
}
