#include "UIFrontEndFlowSubsystem.h"

#include "ZMMO.h"
#include "UIFrontEndScreenSet.h"
#include "UIManagerSubsystem.h"
#include "UIPrimaryGameLayout_Base.h"
#include "UI/UILayerTags.h"
#include "ZMMOGameInstance.h"
#include "ZMMOThemeSubsystem.h"
#include "ZeusNetworkSubsystem.h"
#include "ZeusCharServerSubsystem.h"
#include "CommonActivatableWidget.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()
{
	UUIFrontEndScreenSet* SS = GetScreenSet();

	if (UUIManagerSubsystem* Mgr = GetUIManager())
	{
		TSubclassOf<UUIPrimaryGameLayout_Base> LayoutClass;
		if (SS && !SS->RootLayoutClass.IsNull())
		{
			LayoutClass = SS->RootLayoutClass.LoadSynchronous();
		}
		Mgr->CreateAndAddRootLayout(LayoutClass);
	}

	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::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::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]()
		{
			if (UUIManagerSubsystem* M = GetUIManager())
			{
				if (UClass* Cls = Soft.Get())
				{
					M->PushScreenToLayer(Layer, Cls);
				}
			}
		}));
}

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);
	}
	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);
	}
	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::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())
	{
		UGameplayStatics::OpenLevel(GetGameInstance(), FName(*MapPath));
	}
}

void UUIFrontEndFlowSubsystem::HandlePostLoadMap(UWorld* /*LoadedWorld*/)
{
	if (bTravelingToWorld)
	{
		bTravelingToWorld = false;
		SetState(EZMMOFrontEndState::InWorld);
	}
}

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);
		}
	}
}
