#include "HyperTwistTraining/HyperTwistFirstRunLaunchPlayerController.h"

#include "Blueprint/UserWidget.h"
#include "Engine/Engine.h"
#include "EngineUtils.h"
#include "HyperTwistDiagnostics/HyperTwistRuntimeDiagnostics.h"
#include "HyperTwistTraining/HyperTwistCoachDashboardActor.h"
#include "HyperTwistTraining/HyperTwistFirstRunLaunchLibrary.h"
#include "HyperTwistTraining/HyperTwistFirstRunLaunchWidget.h"
#include "Kismet/GameplayStatics.h"
#include "Misc/CommandLine.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "TimerManager.h"

DEFINE_LOG_CATEGORY_STATIC(LogHyperTwistFirstRunLaunch, Log, All);

AHyperTwistFirstRunLaunchPlayerController::AHyperTwistFirstRunLaunchPlayerController()
{
	bShowMouseCursor = true;
	bEnableClickEvents = true;
	bEnableMouseOverEvents = true;
}

void AHyperTwistFirstRunLaunchPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();
	if (InputComponent == nullptr)
	{
		return;
	}

	InputComponent->BindKey(
		EKeys::Escape,
		IE_Pressed,
		this,
		&AHyperTwistFirstRunLaunchPlayerController::ReturnFromAdvancedDashboard);
	InputComponent->BindKey(
		EKeys::F10,
		IE_Pressed,
		this,
		&AHyperTwistFirstRunLaunchPlayerController::ReturnFromAdvancedDashboard);
}

void AHyperTwistFirstRunLaunchPlayerController::BeginPlay()
{
	Super::BeginPlay();

	InitializeStartupDiagnostics();
	AppendStartupDiagnosticsLine(
		TEXT("BeginPlay"),
		FString::Printf(TEXT("First-run controller booted on world '%s'."), *DescribeCurrentWorldName())
	);

	ApplyFirstRunInputMode();
	if (TryOpenCommandLineStartupRoute())
	{
		return;
	}

	if (bShowFirstRunLaunchOnBeginPlay)
	{
		if (ShowFirstRunLaunchMenu() == nullptr)
		{
			TryRecoverFromFirstRunFailure(TEXT("Failed to create the first-run launch widget on begin play."));
			return;
		}

		return;
	}

	AppendStartupDiagnosticsLine(
		TEXT("BeginPlay"),
		TEXT("The first-run launch menu was intentionally disabled on begin play.")
	);
	PersistStartupDiagnostics(TEXT("launch-menu-disabled"));
}

bool AHyperTwistFirstRunLaunchPlayerController::TryOpenCommandLineStartupRoute()
{
	FHyperTwistFirstRunLaunchRoute Route;
	FString FailureReason;
	const EHyperTwistPackagedStartupRouteParseResult ParseResult =
		UHyperTwistFirstRunLaunchLibrary::ParsePackagedStartupRouteArgument(
			FCommandLine::Get(),
			Route,
			FailureReason
		);
	if (ParseResult == EHyperTwistPackagedStartupRouteParseResult::NotPresent)
	{
		return false;
	}
	if (ParseResult == EHyperTwistPackagedStartupRouteParseResult::Invalid)
	{
		const FString Message = FString::Printf(
			TEXT("Rejected packaged startup-route override: %s Continuing with the visible first-run menu."),
			FailureReason.IsEmpty() ? TEXT("invalid argument.") : *FailureReason
		);
		AppendStartupDiagnosticsLine(TEXT("PackagedStartupRoute"), Message, true);
		HyperTwistRuntimeDiagnostics::AppendEvent(TEXT("PackagedStartupRoute"), Message);
		PersistStartupDiagnostics(TEXT("command-line-route-rejected"));
		return false;
	}

	const FString AcceptedMessage = FString::Printf(
		TEXT("Accepted packaged startup route '%s': map='%s', game_mode='%s'."),
		*Route.RouteId,
		Route.MapAssetPath.IsEmpty() ? TEXT("<dashboard>") : *Route.MapAssetPath,
		Route.GameModeClassPath.IsEmpty() ? TEXT("<none>") : *Route.GameModeClassPath
	);
	AppendStartupDiagnosticsLine(TEXT("PackagedStartupRoute"), AcceptedMessage);
	HyperTwistRuntimeDiagnostics::AppendEvent(TEXT("PackagedStartupRoute"), AcceptedMessage);
	if (OpenFirstRunRoute(Route.RouteId))
	{
		const FString OpenedMessage = FString::Printf(
			TEXT("Opened packaged startup route '%s' through first-run product authority."),
			*Route.RouteId
		);
		AppendStartupDiagnosticsLine(TEXT("PackagedStartupRoute"), OpenedMessage);
		HyperTwistRuntimeDiagnostics::AppendEvent(TEXT("PackagedStartupRoute"), OpenedMessage);
		PersistStartupDiagnostics(TEXT("command-line-route-opened"));
		return true;
	}

	const FString OpenFailureMessage = FString::Printf(
		TEXT("Could not open packaged startup route '%s'; continuing with the visible first-run menu."),
		*Route.RouteId
	);
	AppendStartupDiagnosticsLine(TEXT("PackagedStartupRoute"), OpenFailureMessage, true);
	HyperTwistRuntimeDiagnostics::AppendEvent(
		TEXT("PackagedStartupRoute"),
		OpenFailureMessage
	);
	PersistStartupDiagnostics(TEXT("command-line-route-open-failed"));
	return false;
}

UHyperTwistFirstRunLaunchWidget*
AHyperTwistFirstRunLaunchPlayerController::ShowFirstRunLaunchMenu()
{
	if (ActiveFirstRunLaunchWidget != nullptr)
	{
		ActiveFirstRunLaunchWidget->RebuildFirstRunLaunchSurface();
		if (!ActiveFirstRunLaunchWidget->IsFirstRunLaunchSurfaceReady())
		{
			AppendStartupDiagnosticsLine(
				TEXT("ShowFirstRunLaunchMenu"),
				TEXT("The existing first-run widget has no renderable root tree."),
				true
			);
			PersistStartupDiagnostics(TEXT("launch-widget-root-missing"));
			return nullptr;
		}
		if (!ActiveFirstRunLaunchWidget->IsInViewport())
		{
			ActiveFirstRunLaunchWidget->AddToViewport(FirstRunLaunchZOrder);
		}
		AppendStartupDiagnosticsLine(
			TEXT("ShowFirstRunLaunchMenu"),
			TEXT("Prepared the existing first-run launch widget for layout validation.")
		);
		ScheduleFirstRunSurfaceValidation();
		return ActiveFirstRunLaunchWidget;
	}

	TSubclassOf<UHyperTwistFirstRunLaunchWidget> ResolvedWidgetClass = FirstRunLaunchWidgetClass;
	if (*ResolvedWidgetClass == nullptr)
	{
		ResolvedWidgetClass = UHyperTwistFirstRunLaunchWidget::StaticClass();
		AppendStartupDiagnosticsLine(
			TEXT("ShowFirstRunLaunchMenu"),
			TEXT("Fell back to the native first-run widget class.")
		);
	}
	if (*ResolvedWidgetClass == nullptr)
	{
		AppendStartupDiagnosticsLine(
			TEXT("ShowFirstRunLaunchMenu"),
			TEXT("No first-run widget class was available."),
			true
		);
		PersistStartupDiagnostics(TEXT("launch-widget-class-missing"));
		return nullptr;
	}

	ActiveFirstRunLaunchWidget = CreateWidget<UHyperTwistFirstRunLaunchWidget>(
		this,
		ResolvedWidgetClass
	);
	if (ActiveFirstRunLaunchWidget == nullptr)
	{
		AppendStartupDiagnosticsLine(
			TEXT("ShowFirstRunLaunchMenu"),
			TEXT("CreateWidget returned null for the first-run launch surface."),
			true
		);
		PersistStartupDiagnostics(TEXT("launch-widget-create-failed"));
		return nullptr;
	}

	ActiveFirstRunLaunchWidget->OnFirstRunRouteRequested.AddDynamic(
		this,
		&AHyperTwistFirstRunLaunchPlayerController::HandleFirstRunRouteRequested
	);
	ActiveFirstRunLaunchWidget->RebuildFirstRunLaunchSurface();
	if (!ActiveFirstRunLaunchWidget->IsFirstRunLaunchSurfaceReady())
	{
		AppendStartupDiagnosticsLine(
			TEXT("ShowFirstRunLaunchMenu"),
			TEXT("The first-run widget could not construct its renderable root tree."),
			true
		);
		ActiveFirstRunLaunchWidget = nullptr;
		PersistStartupDiagnostics(TEXT("launch-widget-root-missing"));
		return nullptr;
	}

	ActiveFirstRunLaunchWidget->AddToViewport(FirstRunLaunchZOrder);
	AppendStartupDiagnosticsLine(
		TEXT("ShowFirstRunLaunchMenu"),
		FString::Printf(
			TEXT("Added the first-run launch widget to the viewport at z-order %d."),
			FirstRunLaunchZOrder
		)
	);
	ApplyFirstRunInputMode();
	ScheduleFirstRunSurfaceValidation();
	return ActiveFirstRunLaunchWidget;
}

void AHyperTwistFirstRunLaunchPlayerController::RemoveFirstRunLaunchMenu()
{
	if (ActiveFirstRunLaunchWidget != nullptr)
	{
		GetWorldTimerManager().ClearTimer(FirstRunSurfaceValidationTimerHandle);
		FirstRunSurfaceValidationAttempt = 0;
		ActiveFirstRunLaunchWidget->RemoveFromParent();
		ActiveFirstRunLaunchWidget = nullptr;
		AppendStartupDiagnosticsLine(
			TEXT("RemoveFirstRunLaunchMenu"),
			TEXT("Removed the active first-run launch widget from the viewport.")
		);
		PersistStartupDiagnostics(TEXT("launch-widget-removed"));
	}
}

bool AHyperTwistFirstRunLaunchPlayerController::OpenFirstRunRoute(const FString& RouteId)
{
	FHyperTwistFirstRunLaunchRoute Route;
	if (!UHyperTwistFirstRunLaunchLibrary::TryFindFirstRunLaunchRoute(RouteId, Route)
		|| !Route.IsStructurallyValid())
	{
		AppendStartupDiagnosticsLine(
			TEXT("OpenFirstRunRoute"),
			FString::Printf(TEXT("Rejected invalid first-run route '%s'."), *RouteId),
			true
		);
		PersistStartupDiagnostics(TEXT("invalid-route-rejected"));
		return false;
	}

	AppendStartupDiagnosticsLine(
		TEXT("OpenFirstRunRoute"),
		FString::Printf(
			TEXT("Opening route '%s' (%s)."),
			*Route.RouteId,
			Route.bOpensDashboard ? TEXT("dashboard") : TEXT("dedicated-map")
		)
	);

	if (Route.bOpensDashboard)
	{
		RemoveFirstRunLaunchMenu();
		if (EnsureCoachDashboard() != nullptr)
		{
			PersistStartupDiagnostics(TEXT("dashboard-opened"));
			return true;
		}

		TryRecoverFromFirstRunFailure(
			FString::Printf(TEXT("Failed to open dashboard route '%s'."), *Route.RouteId)
		);
		return false;
	}

	if (Route.bLaunchesDedicatedMap)
	{
		OpenMapRoute(Route.MapAssetPath, Route.GameModeClassPath);
		PersistStartupDiagnostics(TEXT("map-route-opened"));
		return true;
	}

	AppendStartupDiagnosticsLine(
		TEXT("OpenFirstRunRoute"),
		FString::Printf(TEXT("Route '%s' did not match a supported launch action."), *Route.RouteId),
		true
	);
	PersistStartupDiagnostics(TEXT("route-without-launch-action"));
	return false;
}

void AHyperTwistFirstRunLaunchPlayerController::HandleFirstRunRouteRequested(
	const FString& RouteId
)
{
	if (!OpenFirstRunRoute(RouteId))
	{
		AppendStartupDiagnosticsLine(
			TEXT("HandleFirstRunRouteRequested"),
			FString::Printf(TEXT("The requested route '%s' did not open successfully."), *RouteId),
			true
		);
	}
}

void AHyperTwistFirstRunLaunchPlayerController::ReturnFromAdvancedDashboard()
{
	if (ActiveDashboardActor == nullptr || !ActiveDashboardActor->HasActiveDashboard())
	{
		return;
	}

	ActiveDashboardActor->RemoveDashboard();
	if (UHyperTwistFirstRunLaunchWidget* LaunchWidget = ShowFirstRunLaunchMenu())
	{
		LaunchWidget->ShowPage(EHyperTwistMainMenuPage::Advanced);
	}
	ApplyFirstRunInputMode();
	AppendStartupDiagnosticsLine(
		TEXT("ReturnFromAdvancedDashboard"),
		TEXT("Closed the engineering dashboard and restored the main menu."));
	PersistStartupDiagnostics(TEXT("dashboard-returned-to-main-menu"));
}

AHyperTwistCoachDashboardActor*
AHyperTwistFirstRunLaunchPlayerController::EnsureCoachDashboard()
{
	if (ActiveDashboardActor == nullptr)
	{
		ActiveDashboardActor = FindExistingDashboardActor();
	}

	if (ActiveDashboardActor == nullptr)
	{
		ActiveDashboardActor = SpawnDashboardActor();
	}

	if (ActiveDashboardActor != nullptr)
	{
		ApplyDashboardDefaults(ActiveDashboardActor);
		ActiveDashboardActor->CreateAndShowDashboard();
		AppendStartupDiagnosticsLine(
			TEXT("EnsureCoachDashboard"),
			TEXT("Dashboard actor is ready and the dashboard surface was requested.")
		);
		PersistStartupDiagnostics(TEXT("dashboard-surface-requested"));
	}
	else
	{
		AppendStartupDiagnosticsLine(
			TEXT("EnsureCoachDashboard"),
			TEXT("Dashboard actor could not be found or spawned."),
			true
		);
	}

	ApplyFirstRunInputMode();
	return ActiveDashboardActor;
}

void AHyperTwistFirstRunLaunchPlayerController::ApplyFirstRunInputMode()
{
	bShowMouseCursor = true;
	bEnableClickEvents = true;
	bEnableMouseOverEvents = true;

	FInputModeGameAndUI InputMode;
	InputMode.SetHideCursorDuringCapture(false);
	InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
	SetInputMode(InputMode);
	AppendStartupDiagnosticsLine(
		TEXT("ApplyFirstRunInputMode"),
		TEXT("Applied game-and-UI input mode with unlocked cursor capture.")
	);
}

void AHyperTwistFirstRunLaunchPlayerController::OpenMapRoute(
	const FString& MapAssetPath,
	const FString& GameModeClassPath
)
{
	if (MapAssetPath.IsEmpty())
	{
		AppendStartupDiagnosticsLine(
			TEXT("OpenMapRoute"),
			TEXT("Refused to open an empty map route."),
			true
		);
		return;
	}

	FString Options;
	if (!GameModeClassPath.IsEmpty())
	{
		Options = FString::Printf(TEXT("game=%s"), *GameModeClassPath);
	}

	AppendStartupDiagnosticsLine(
		TEXT("OpenMapRoute"),
		FString::Printf(
			TEXT("Opening map '%s' with options '%s'."),
			*MapAssetPath,
			Options.IsEmpty() ? TEXT("<none>") : *Options
		)
	);
	UGameplayStatics::OpenLevel(this, FName(*MapAssetPath), true, Options);
}

AHyperTwistCoachDashboardActor*
AHyperTwistFirstRunLaunchPlayerController::FindExistingDashboardActor()
{
	if (GetWorld() == nullptr)
	{
		return nullptr;
	}

	for (TActorIterator<AHyperTwistCoachDashboardActor> ActorIt(GetWorld()); ActorIt; ++ActorIt)
	{
		AppendStartupDiagnosticsLine(
			TEXT("FindExistingDashboardActor"),
			TEXT("Reused an existing dashboard actor from the world.")
		);
		return *ActorIt;
	}

	return nullptr;
}

AHyperTwistCoachDashboardActor*
AHyperTwistFirstRunLaunchPlayerController::SpawnDashboardActor()
{
	if (GetWorld() == nullptr)
	{
		return nullptr;
	}

	TSubclassOf<AHyperTwistCoachDashboardActor> ResolvedActorClass = DashboardActorClass;
	if (*ResolvedActorClass == nullptr)
	{
		ResolvedActorClass = AHyperTwistCoachDashboardActor::StaticClass();
	}
	if (*ResolvedActorClass == nullptr)
	{
		return nullptr;
	}

	FTransform SpawnTransform;
	AHyperTwistCoachDashboardActor* DashboardActor =
		GetWorld()->SpawnActorDeferred<AHyperTwistCoachDashboardActor>(
			ResolvedActorClass,
			SpawnTransform,
			nullptr,
			nullptr,
			ESpawnActorCollisionHandlingMethod::AlwaysSpawn
		);
	if (DashboardActor == nullptr)
	{
		AppendStartupDiagnosticsLine(
			TEXT("SpawnDashboardActor"),
			TEXT("SpawnActorDeferred returned null for the dashboard actor."),
			true
		);
		return nullptr;
	}

	ApplyDashboardDefaults(DashboardActor);
	DashboardActor->FinishSpawning(SpawnTransform);
	AppendStartupDiagnosticsLine(
		TEXT("SpawnDashboardActor"),
		TEXT("Spawned a new dashboard actor for the first-run shell.")
	);
	return DashboardActor;
}

void AHyperTwistFirstRunLaunchPlayerController::ApplyDashboardDefaults(
	AHyperTwistCoachDashboardActor* DashboardActor
) const
{
	if (DashboardActor == nullptr)
	{
		return;
	}

	DashboardActor->DashboardDefaultUserId = DashboardDefaultUserId;
	DashboardActor->DashboardDefaultSessionId = DashboardDefaultSessionId;
	DashboardActor->DashboardDefaultMode = DashboardDefaultMode;
	DashboardActor->DashboardDefaultCoachMaxCases = DashboardDefaultCoachMaxCases;
}

void AHyperTwistFirstRunLaunchPlayerController::InitializeStartupDiagnostics()
{
	if (!StartupDiagnosticsLines.IsEmpty() && !StartupSessionDiagnosticsLogPath.IsEmpty())
	{
		return;
	}

	const FString LogDirectory = FPaths::ProjectLogDir();
	RuntimeLogPath = FPaths::Combine(
		LogDirectory,
		UHyperTwistFirstRunLaunchLibrary::GetRuntimeLogFileName()
	);
	StartupDiagnosticsLogPath = FPaths::Combine(
		LogDirectory,
		UHyperTwistFirstRunLaunchLibrary::GetStartupDiagnosticsLogFileName()
	);
	const FString SessionToken = FDateTime::UtcNow().ToString(TEXT("%Y%m%dT%H%M%SZ"));
	StartupSessionDiagnosticsLogPath = FPaths::Combine(
		LogDirectory,
		FString::Printf(TEXT("HyperTwistFirstRunLaunch-%s.log"), *SessionToken)
	);

	StartupDiagnosticsLines.Reset();
	StartupDiagnosticsLines.Add(FString::Printf(TEXT("session=%s"), *SessionToken));
	StartupDiagnosticsLines.Add(FString::Printf(TEXT("world=%s"), *DescribeCurrentWorldName()));
	StartupDiagnosticsLines.Add(FString::Printf(TEXT("runtime_log=%s"), *RuntimeLogPath));
	StartupDiagnosticsLines.Add(FString::Printf(TEXT("latest_diagnostics_log=%s"), *StartupDiagnosticsLogPath));
	PersistStartupDiagnostics(TEXT("startup-initialized"));
}

void AHyperTwistFirstRunLaunchPlayerController::AppendStartupDiagnosticsLine(
	const FString& Stage,
	const FString& Message,
	const bool bTreatAsError
)
{
	InitializeStartupDiagnostics();

	const FString Line = FString::Printf(
		TEXT("[%s] [%s] %s"),
		*FDateTime::UtcNow().ToString(TEXT("%Y-%m-%dT%H:%M:%SZ")),
		*Stage,
		*Message
	);
	StartupDiagnosticsLines.Add(Line);

	if (bTreatAsError)
	{
		LastStartupFailureReason = Message;
		UE_LOG(LogHyperTwistFirstRunLaunch, Error, TEXT("%s"), *Line);
	}
	else
	{
		UE_LOG(LogHyperTwistFirstRunLaunch, Display, TEXT("%s"), *Line);
	}

	PersistStartupDiagnostics(bTreatAsError ? TEXT("startup-error") : TEXT("startup-progress"));
}

void AHyperTwistFirstRunLaunchPlayerController::PersistStartupDiagnostics(
	const FString& ResultStatus
) const
{
	if (StartupDiagnosticsLogPath.IsEmpty() || StartupSessionDiagnosticsLogPath.IsEmpty())
	{
		return;
	}

	TArray<FString> LinesToWrite = StartupDiagnosticsLines;
	LinesToWrite.Add(FString::Printf(TEXT("result=%s"), *ResultStatus));
	if (!LastStartupFailureReason.IsEmpty())
	{
		LinesToWrite.Add(FString::Printf(TEXT("last_failure=%s"), *LastStartupFailureReason));
	}

	FFileHelper::SaveStringArrayToFile(LinesToWrite, *StartupDiagnosticsLogPath);
	FFileHelper::SaveStringArrayToFile(LinesToWrite, *StartupSessionDiagnosticsLogPath);
}

bool AHyperTwistFirstRunLaunchPlayerController::TryRecoverFromFirstRunFailure(
	const FString& FailureReason
)
{
	LastStartupFailureReason = FailureReason;
	AppendStartupDiagnosticsLine(TEXT("StartupRecovery"), FailureReason, true);

	if (bStartupRecoveryAttempted)
	{
		PersistStartupDiagnostics(TEXT("startup-recovery-already-attempted"));
		return false;
	}

	bStartupRecoveryAttempted = true;
	const FString FallbackRouteId = UHyperTwistFirstRunLaunchLibrary::GetStartupFallbackRouteId();
	const FString RecoveryMessage = FString::Printf(
		TEXT("HyperTwist could not show the first-run launch surface. ")
		TEXT("Inspect Saved/Logs/%s and Saved/Logs/%s. ")
		TEXT("Attempting automatic fallback route '%s'."),
		*UHyperTwistFirstRunLaunchLibrary::GetRuntimeLogFileName(),
		*UHyperTwistFirstRunLaunchLibrary::GetStartupDiagnosticsLogFileName(),
		*FallbackRouteId
	);

	if (GEngine != nullptr)
	{
		GEngine->AddOnScreenDebugMessage(
			-1,
			15.0f,
			FColor::Yellow,
			RecoveryMessage
		);
	}

	AppendStartupDiagnosticsLine(
		TEXT("StartupRecovery"),
		FString::Printf(TEXT("Attempting fallback route '%s'."), *FallbackRouteId)
	);

	const bool bRecovered = OpenFirstRunRoute(FallbackRouteId);
	PersistStartupDiagnostics(bRecovered ? TEXT("startup-recovered") : TEXT("startup-recovery-failed"));
	return bRecovered;
}

void AHyperTwistFirstRunLaunchPlayerController::ScheduleFirstRunSurfaceValidation()
{
	FirstRunSurfaceValidationAttempt = 0;
	GetWorldTimerManager().ClearTimer(FirstRunSurfaceValidationTimerHandle);
	GetWorldTimerManager().SetTimer(
		FirstRunSurfaceValidationTimerHandle,
		this,
		&AHyperTwistFirstRunLaunchPlayerController::ValidateFirstRunSurfaceLayout,
		FMath::Max(FirstRunSurfaceValidationIntervalSeconds, 0.01f),
		false
	);
	PersistStartupDiagnostics(TEXT("launch-widget-added-pending-layout"));
}

void AHyperTwistFirstRunLaunchPlayerController::ValidateFirstRunSurfaceLayout()
{
	if (ActiveFirstRunLaunchWidget == nullptr)
	{
		return;
	}

	++FirstRunSurfaceValidationAttempt;
	const FVector2D LocalSize = ActiveFirstRunLaunchWidget->GetCachedGeometry().GetLocalSize();
	const bool bHasRenderableLayout = ActiveFirstRunLaunchWidget->IsInViewport()
		&& ActiveFirstRunLaunchWidget->IsVisible()
		&& ActiveFirstRunLaunchWidget->IsFirstRunLaunchSurfaceReady()
		&& LocalSize.X > 1.0f
		&& LocalSize.Y > 1.0f;
	if (bHasRenderableLayout)
	{
		AppendStartupDiagnosticsLine(
			TEXT("ValidateFirstRunSurfaceLayout"),
			FString::Printf(
				TEXT("Confirmed a renderable first-run layout at %.0fx%.0f after %d attempt(s)."),
				LocalSize.X,
				LocalSize.Y,
				FirstRunSurfaceValidationAttempt
			)
		);
		PersistStartupDiagnostics(TEXT("launch-menu-renderable"));
		HyperTwistRuntimeDiagnostics::RequestReadyFrameCapture(TEXT("first-run-launch-ready"));
		return;
	}

	if (FirstRunSurfaceValidationAttempt < FMath::Max(MaxFirstRunSurfaceValidationAttempts, 1))
	{
		GetWorldTimerManager().SetTimer(
			FirstRunSurfaceValidationTimerHandle,
			this,
			&AHyperTwistFirstRunLaunchPlayerController::ValidateFirstRunSurfaceLayout,
			FMath::Max(FirstRunSurfaceValidationIntervalSeconds, 0.01f),
			false
		);
		return;
	}

	TryRecoverFromFirstRunFailure(
		FString::Printf(
			TEXT("The first-run launch widget never produced a visible non-zero layout after %d attempts (last size %.0fx%.0f)."),
			FirstRunSurfaceValidationAttempt,
			LocalSize.X,
			LocalSize.Y
		)
	);
}

FString AHyperTwistFirstRunLaunchPlayerController::DescribeCurrentWorldName() const
{
	const UWorld* World = GetWorld();
	if (World == nullptr)
	{
		return TEXT("null-world");
	}

	return World->GetMapName();
}
