// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"
#include "Misc/ConfigCacheIni.h"

#include "Blueprint/WidgetTree.h"
#include "Components/Button.h"
#include "Containers/Set.h"
#include "HyperTwistSimulation/HyperTwistClassicCubeHUDWidget.h"
#include "HyperTwistTraining/HyperTwistCoachDashboardWidget.h"
#include "HyperTwistTraining/HyperTwistFirstRunLaunchGameMode.h"
#include "HyperTwistTraining/HyperTwistFirstRunLaunchLibrary.h"
#include "HyperTwistTraining/HyperTwistFirstRunLaunchPlayerController.h"
#include "HyperTwistTraining/HyperTwistFirstRunLaunchWidget.h"
#include "HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistFirstRunLaunchSurfaceTestInternal
{
	struct FExpectedFourDimensionalRoute
	{
		const TCHAR* RouteId;
		const TCHAR* PuzzleId;
		const TCHAR* MapAssetPath;
		const TCHAR* GameModeClassPath;
	};

	const FExpectedFourDimensionalRoute FourDimensionalRoutes[] = {
		{
			TEXT("magic-cube-4d-2x2x2x2"),
			TEXT("hypercube/2x2x2x2"),
			TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube4D_2x2x2x2Training"),
			TEXT("/Script/UnrealHyperTwist.HyperTwistMelindaProjectionGameMode")
		},
		{
			TEXT("magic-cube-4d-3x3x3x3"),
			TEXT("hypercube/3x3x3x3"),
			TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube4D_3x3x3x3Training"),
			TEXT("/Script/UnrealHyperTwist.HyperTwistVirtual3333ProjectionGameMode")
		},
		{
			TEXT("magic-cube-4d-4x4x4x4"),
			TEXT("hypercube/4x4x4x4"),
			TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube4D_4x4x4x4Training"),
			TEXT("/Script/UnrealHyperTwist.HyperTwistVirtual4444ProjectionGameMode")
		},
		{
			TEXT("magic-cube-4d-5x5x5x5"),
			TEXT("hypercube/5x5x5x5"),
			TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube4D_5x5x5x5Training"),
			TEXT("/Script/UnrealHyperTwist.HyperTwistVirtual5555ProjectionGameMode")
		},
		{
			TEXT("magic-cube-4d-6x6x6x6"),
			TEXT("hypercube/6x6x6x6"),
			TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube4D_6x6x6x6Training"),
			TEXT("/Script/UnrealHyperTwist.HyperTwistVirtual6666ProjectionGameMode")
		}
	};

	bool ContainsGuidanceSubstring(const TArray<FString>& Lines, const FString& ExpectedSubstring)
	{
		for (const FString& Line : Lines)
		{
			if (Line.Contains(ExpectedSubstring))
			{
				return true;
			}
		}

		return false;
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistFirstRunLaunchRouteContractTest,
	"HyperTwist.FirstRun.LaunchRouteContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistFirstRunLaunchRouteContractTest::RunTest(const FString& Parameters)
{
	using namespace HyperTwistFirstRunLaunchSurfaceTestInternal;
	const TArray<FHyperTwistFirstRunLaunchRoute> Routes =
		UHyperTwistFirstRunLaunchLibrary::BuildFirstRunLaunchRoutes();
	TestEqual(TEXT("The first-run surface must expose all launch choices."), Routes.Num(), 11);

	for (const FHyperTwistFirstRunLaunchRoute& Route : Routes)
	{
		TestTrue(
			FString::Printf(TEXT("Route '%s' must be structurally valid."), *Route.RouteId),
			Route.IsStructurallyValid()
		);
	}

	FHyperTwistFirstRunLaunchRoute Route;
	TestTrue(
		TEXT("The default route must enter the complete keyboard-and-mouse Classic runtime."),
		UHyperTwistFirstRunLaunchLibrary::TryFindFirstRunLaunchRoute(
			UHyperTwistFirstRunLaunchLibrary::GetDefaultRouteId(),
			Route
		)
			&& Route.bDefaultSafeChoice
			&& !Route.bOpensDashboard
			&& Route.bLaunchesDedicatedMap
			&& Route.RouteId == TEXT("classic-cube-training")
		);
	TestEqual(
		TEXT("The packaged startup fallback route must target classic free play."),
		UHyperTwistFirstRunLaunchLibrary::GetStartupFallbackRouteId(),
		FString(TEXT("classic-cube-training"))
	);

	TestTrue(
		TEXT("The classic free-play route must target the authored classic training map."),
		UHyperTwistFirstRunLaunchLibrary::TryFindFirstRunLaunchRoute(TEXT("classic-cube-training"), Route)
			&& Route.MapAssetPath == TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_ClassicTraining")
			&& Route.GameModeClassPath == TEXT("/Script/UnrealHyperTwist.HyperTwistClassicCubeGameMode")
	);

	TestTrue(
		TEXT("The follow-along route must target the authored follow-along map."),
		UHyperTwistFirstRunLaunchLibrary::TryFindFirstRunLaunchRoute(TEXT("follow-along-training"), Route)
			&& Route.MapAssetPath == TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_FollowAlongTraining")
			&& Route.GameModeClassPath == TEXT("/Script/UnrealHyperTwist.HyperTwistClassicCubeFollowAlongGameMode")
	);

	for (const FExpectedFourDimensionalRoute& Expected : FourDimensionalRoutes)
	{
		const FString RouteLabel = Expected.RouteId;
		TestTrue(
			*FString::Printf(
				TEXT("The %s route must target its dedicated map and exact runtime."),
				*RouteLabel),
			UHyperTwistFirstRunLaunchLibrary::TryFindFirstRunLaunchRoute(
				Expected.RouteId,
				Route)
				&& Route.MapAssetPath == Expected.MapAssetPath
				&& Route.GameModeClassPath == Expected.GameModeClassPath
				&& Route.bLaunchesDedicatedMap
				&& Route.RuntimeProofStatus == TEXT("ready-first-party-runtime"));
		TestEqual(
			*FString::Printf(
				TEXT("The %s launch URL must bind its dedicated game mode."),
				*RouteLabel),
			Route.LaunchUrl,
			FString::Printf(
				TEXT("%s?game=%s"),
				Expected.MapAssetPath,
				Expected.GameModeClassPath));
	}

	TestTrue(
		TEXT("The Magic120Cell route must target its dedicated family map and runtime game mode."),
		UHyperTwistFirstRunLaunchLibrary::TryFindFirstRunLaunchRoute(TEXT("magic-120-cell-training"), Route)
			&& Route.MapAssetPath == TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining")
			&& Route.GameModeClassPath == TEXT("/Script/UnrealHyperTwist.HyperTwistHigherDimensionalTrainingGameMode")
			&& Route.LaunchUrl == TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining?game=/Script/UnrealHyperTwist.HyperTwistHigherDimensionalTrainingGameMode")
			&& Route.RuntimeProofStatus == TEXT("ready-dedicated-family-map")
	);

	TestTrue(
		TEXT("The MagicCube5D route must target its dedicated family map and runtime game mode."),
		UHyperTwistFirstRunLaunchLibrary::TryFindFirstRunLaunchRoute(TEXT("magic-cube-5d-training"), Route)
			&& Route.MapAssetPath == TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining")
			&& Route.GameModeClassPath == TEXT("/Script/UnrealHyperTwist.HyperTwistHigherDimensionalTrainingGameMode")
			&& Route.LaunchUrl == TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining?game=/Script/UnrealHyperTwist.HyperTwistHigherDimensionalTrainingGameMode")
			&& Route.RuntimeProofStatus == TEXT("ready-dedicated-family-map")
	);

	TestTrue(
		TEXT("The XR route must be explicit about the live-device proof gate."),
		UHyperTwistFirstRunLaunchLibrary::TryFindFirstRunLaunchRoute(TEXT("xr-training-validation"), Route)
			&& Route.MapAssetPath == TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_FollowAlongTraining")
			&& Route.GameModeClassPath == TEXT("/Script/UnrealHyperTwist.HyperTwistXrTrainingGameMode")
			&& Route.bRequiresLiveHeadset
			&& Route.bRequiresLiveControllerObservation
	);

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistFirstRunPuzzleCatalogContractTest,
	"HyperTwist.FirstRun.PuzzleCatalogContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistFirstRunPuzzleCatalogContractTest::RunTest(const FString& Parameters)
{
	static_cast<void>(Parameters);
	using namespace HyperTwistFirstRunLaunchSurfaceTestInternal;

	const TArray<FHyperTwistPuzzleCatalogEntry> Catalog =
		UHyperTwistFirstRunLaunchLibrary::BuildPlayerPuzzleCatalog();
	TestEqual(
		TEXT("The player catalog must expose every playable family plus MagicTile preview."),
		Catalog.Num(),
		9);

	for (const FExpectedFourDimensionalRoute& Expected : FourDimensionalRoutes)
	{
		FHyperTwistPuzzleCatalogEntry Entry;
		TestTrue(
			*FString::Printf(
				TEXT("The player catalog must expose %s."),
				Expected.PuzzleId),
			UHyperTwistFirstRunLaunchLibrary::TryFindPuzzleCatalogEntry(
				Expected.PuzzleId,
				Entry));
		TestEqual(
			*FString::Printf(
				TEXT("%s must launch through its registered route."),
				Expected.PuzzleId),
			Entry.LaunchRouteId,
			FString(Expected.RouteId));
		TestTrue(
			*FString::Printf(
				TEXT("%s must be player-facing and playable."),
				Expected.PuzzleId),
			Entry.Availability == EHyperTwistPuzzleAvailability::Playable);
	}

	FHyperTwistPuzzleCatalogEntry MagicTileEntry;
	TestTrue(
		TEXT("MagicTile must remain explicitly represented without overstating native readiness."),
		UHyperTwistFirstRunLaunchLibrary::TryFindPuzzleCatalogEntry(
			TEXT("tiling/magictile"),
			MagicTileEntry)
			&& MagicTileEntry.Availability
				== EHyperTwistPuzzleAvailability::BrowserPreview
			&& MagicTileEntry.LaunchRouteId.IsEmpty());
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistIntegratedCapabilityCatalogContractTest,
	"HyperTwist.FirstRun.IntegratedCapabilityCatalogContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistIntegratedCapabilityCatalogContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);

	const TArray<FHyperTwistIntegratedCapability> Catalog =
		UHyperTwistFirstRunLaunchLibrary::BuildIntegratedCapabilityCatalog();
	TestEqual(
		TEXT("The integration catalog must retain the canonical original 29 live lanes."),
		Catalog.Num(),
		29);

	TSet<FString> CapabilityIds;
	TSet<FString> CanonicalRepositories;
	int32 PermissiveCount = 0;
	int32 CleanRoomCount = 0;
	int32 BoundarySensitiveCount = 0;
	int32 NativePlayableCount = 0;
	int32 NativePlayerServiceCount = 0;
	int32 BrowserSupportCount = 0;
	int32 OperatorSupportCount = 0;
	for (const FHyperTwistIntegratedCapability& Capability : Catalog)
	{
		TestTrue(
			*FString::Printf(
				TEXT("Capability '%s' must be structurally valid."),
				*Capability.CapabilityId),
			Capability.IsStructurallyValid());
		TestFalse(
			*FString::Printf(
				TEXT("Capability id '%s' must be unique."),
				*Capability.CapabilityId),
			CapabilityIds.Contains(Capability.CapabilityId));
		TestFalse(
			*FString::Printf(
				TEXT("Repository '%s' must appear exactly once."),
				*Capability.CanonicalRepository),
			CanonicalRepositories.Contains(Capability.CanonicalRepository));
		CapabilityIds.Add(Capability.CapabilityId);
		CanonicalRepositories.Add(Capability.CanonicalRepository);

		switch (Capability.LicenseLane)
		{
		case EHyperTwistIntegrationLicenseLane::Permissive:
			++PermissiveCount;
			break;
		case EHyperTwistIntegrationLicenseLane::RestrictiveCleanRoom:
			++CleanRoomCount;
			TestTrue(
				*FString::Printf(
					TEXT("Restrictive row '%s' must remain explicitly clean-roomed."),
					*Capability.CanonicalRepository),
				Capability.ImplementationPosture.Contains(TEXT("clean-room")));
			break;
		case EHyperTwistIntegrationLicenseLane::BoundarySensitive:
			++BoundarySensitiveCount;
			break;
		default:
			AddError(TEXT("The capability catalog contains an unknown license lane."));
			break;
		}

		switch (Capability.Surface)
		{
		case EHyperTwistIntegrationSurface::NativePlayable:
			++NativePlayableCount;
			break;
		case EHyperTwistIntegrationSurface::NativePlayerService:
			++NativePlayerServiceCount;
			break;
		case EHyperTwistIntegrationSurface::BrowserSupport:
			++BrowserSupportCount;
			break;
		case EHyperTwistIntegrationSurface::OperatorSupport:
			++OperatorSupportCount;
			break;
		default:
			AddError(TEXT("The capability catalog contains an unknown product surface."));
			break;
		}

		if (!Capability.PrimaryExperienceRoute.StartsWith(TEXT("menu:"))
			&& !Capability.PrimaryExperienceRoute.StartsWith(TEXT("web:")))
		{
			FHyperTwistFirstRunLaunchRoute Route;
			TestTrue(
				*FString::Printf(
					TEXT("Product route '%s' for '%s' must resolve."),
					*Capability.PrimaryExperienceRoute,
					*Capability.CanonicalRepository),
				UHyperTwistFirstRunLaunchLibrary::TryFindFirstRunLaunchRoute(
					Capability.PrimaryExperienceRoute,
					Route));
		}
	}

	TestEqual(TEXT("The original 29 must retain 18 permissive lanes."), PermissiveCount, 18);
	TestEqual(TEXT("The original 29 must retain five restrictive clean-room lanes."), CleanRoomCount, 5);
	TestEqual(TEXT("The original 29 must retain six boundary-sensitive lanes."), BoundarySensitiveCount, 6);
	TestEqual(TEXT("Five integration rows own playable puzzle engines."), NativePlayableCount, 5);
	TestEqual(TEXT("Fourteen integration rows own native player services."), NativePlayerServiceCount, 14);
	TestEqual(TEXT("Seven integration rows remain bounded browser support."), BrowserSupportCount, 7);
	TestEqual(TEXT("Three integration rows remain operator support."), OperatorSupportCount, 3);

	FHyperTwistIntegratedCapability Capability;
	TestTrue(
		TEXT("KubeTimr must explicitly resolve to the live timer owner and Classic route."),
		UHyperTwistFirstRunLaunchLibrary::TryFindIntegratedCapability(
			TEXT("Aarav2709/KubeTimr"),
			Capability)
			&& Capability.RuntimeOwnerId.Contains(TEXT("LiveTimer"))
			&& Capability.PrimaryExperienceRoute == TEXT("classic-cube-training"));
	TestTrue(
		TEXT("cube_trainer must explicitly resolve to persisted coaching in Learning Studio."),
		UHyperTwistFirstRunLaunchLibrary::TryFindIntegratedCapability(
			TEXT("Lykos/cube_trainer"),
			Capability)
			&& Capability.RuntimeOwnerId.Contains(TEXT("TrainingRepository"))
			&& Capability.PrimaryExperienceRoute == TEXT("menu:learn"));
	TestTrue(
		TEXT("Hyperspeedcube must resolve to the generalized native 4D runtime."),
		UHyperTwistFirstRunLaunchLibrary::TryFindIntegratedCapability(
			TEXT("HactarCE/Hyperspeedcube"),
			Capability)
			&& Capability.Surface == EHyperTwistIntegrationSurface::NativePlayable
			&& Capability.PrimaryExperienceRoute
				== TEXT("magic-cube-4d-3x3x3x3"));
	TestTrue(
		TEXT("MagicTile must remain browser support rather than a falsely complete native renderer."),
		UHyperTwistFirstRunLaunchLibrary::TryFindIntegratedCapability(
			TEXT("roice3/MagicTile"),
			Capability)
			&& Capability.Surface == EHyperTwistIntegrationSurface::BrowserSupport);
	TestTrue(
		TEXT("Remotion must preserve landed outputs while marking future widening restrictive."),
		UHyperTwistFirstRunLaunchLibrary::TryFindIntegratedCapability(
			TEXT("remotion-dev/remotion"),
			Capability)
			&& Capability.ImplementationPosture.Contains(TEXT("restrictive-future")));

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistLearningStudioDeckContractTest,
	"HyperTwist.FirstRun.LearningStudioDeckContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistLearningStudioDeckContractTest::RunTest(const FString& Parameters)
{
	static_cast<void>(Parameters);

	const TArray<FHyperTwistContentPack> Catalog =
		UHyperTwistTrainingCatalogLibrary::MakePhase3TrainingCatalog();
	const TArray<FString> LearningStudioDeckIds = {
		TEXT("oll-t"),
		TEXT("cross-4move"),
		TEXT("5style-4mover"),
		TEXT("roux-trainers-algorithm-case-deck-cmll-default"),
		TEXT("cube-trainer-algorithm-case-deck-commutator-uf-edge")
	};
	for (const FString& DeckId : LearningStudioDeckIds)
	{
		FHyperTwistTrainingDeck Deck;
		TestTrue(
			*FString::Printf(
				TEXT("Learning Studio deck '%s' must resolve from the packaged catalog."),
				*DeckId),
			UHyperTwistTrainingCatalogLibrary::TryFindDeckInCatalog(
				Catalog,
				DeckId,
				Deck)
				&& Deck.IsStructurallyValid()
				&& !Deck.Cases.IsEmpty());
	}

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistPackagedStartupRouteArgumentContractTest,
	"HyperTwist.FirstRun.PackagedStartupRouteArgumentContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistPackagedStartupRouteArgumentContractTest::RunTest(const FString& Parameters)
{
	TestEqual(
		TEXT("The packaged startup-route switch must have one stable public name."),
		UHyperTwistFirstRunLaunchLibrary::GetPackagedStartupRouteArgumentName(),
		FString(TEXT("HyperTwistStartupRoute"))
	);

	FHyperTwistFirstRunLaunchRoute Route;
	FString FailureReason;
	TestTrue(
		TEXT("An unrelated command line must not request a startup route."),
		UHyperTwistFirstRunLaunchLibrary::ParsePackagedStartupRouteArgument(
			TEXT("-windowed -notraceserver"),
			Route,
			FailureReason
		) == EHyperTwistPackagedStartupRouteParseResult::NotPresent
	);
	TestTrue(TEXT("An absent route must not fabricate a route."), Route.RouteId.IsEmpty());
	TestTrue(TEXT("An absent route must not fabricate an error."), FailureReason.IsEmpty());

	TestTrue(
		TEXT("A registered follow-along route must parse through first-run authority."),
		UHyperTwistFirstRunLaunchLibrary::ParsePackagedStartupRouteArgument(
			TEXT("-windowed -HyperTwistStartupRoute=follow-along-training"),
			Route,
			FailureReason
		) == EHyperTwistPackagedStartupRouteParseResult::Valid
	);
	TestEqual(
		TEXT("The parsed route must retain the registered identifier."),
		Route.RouteId,
		FString(TEXT("follow-along-training"))
	);
	TestEqual(
		TEXT("The parsed route must retain its dedicated map authority."),
		Route.MapAssetPath,
		FString(TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_FollowAlongTraining"))
	);
	TestTrue(TEXT("A valid route must not retain an error."), FailureReason.IsEmpty());

	TestTrue(
		TEXT("An unknown route identifier must be rejected."),
		UHyperTwistFirstRunLaunchLibrary::ParsePackagedStartupRouteArgument(
			TEXT("-HyperTwistStartupRoute=unknown-training"),
			Route,
			FailureReason
		) == EHyperTwistPackagedStartupRouteParseResult::Invalid
	);
	TestTrue(TEXT("An unknown route must not escape as resolved state."), Route.RouteId.IsEmpty());
	TestTrue(TEXT("An unknown route must explain its rejection."), !FailureReason.IsEmpty());

	TestTrue(
		TEXT("Duplicate startup-route switches must be rejected rather than resolved ambiguously."),
		UHyperTwistFirstRunLaunchLibrary::ParsePackagedStartupRouteArgument(
			TEXT("-HyperTwistStartupRoute=follow-along-training -HyperTwistStartupRoute=magic-120-cell-training"),
			Route,
			FailureReason
		) == EHyperTwistPackagedStartupRouteParseResult::Invalid
	);
	TestTrue(
		TEXT("Map-option injection must be rejected before route lookup."),
		UHyperTwistFirstRunLaunchLibrary::ParsePackagedStartupRouteArgument(
			TEXT("-HyperTwistStartupRoute=follow-along-training?game=Other"),
			Route,
			FailureReason
		) == EHyperTwistPackagedStartupRouteParseResult::Invalid
	);
	TestTrue(
		TEXT("A value-less startup-route switch must be rejected."),
		UHyperTwistFirstRunLaunchLibrary::ParsePackagedStartupRouteArgument(
			TEXT("-HyperTwistStartupRoute"),
			Route,
			FailureReason
		) == EHyperTwistPackagedStartupRouteParseResult::Invalid
	);

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistFirstRunLaunchGuidanceContractTest,
	"HyperTwist.FirstRun.LaunchGuidanceContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistFirstRunLaunchGuidanceContractTest::RunTest(const FString& Parameters)
{
	const TArray<FString> Lines = UHyperTwistFirstRunLaunchLibrary::BuildFirstRunGuidanceLines();
	TestTrue(
		TEXT("The first-run guidance must state keyboard/mouse readiness."),
		HyperTwistFirstRunLaunchSurfaceTestInternal::ContainsGuidanceSubstring(
			Lines,
			TEXT("Keyboard and mouse")
		)
	);
	TestTrue(
		TEXT("The first-run guidance must state the XR live proof gate."),
		HyperTwistFirstRunLaunchSurfaceTestInternal::ContainsGuidanceSubstring(
			Lines,
			TEXT("live headset session")
		)
	);
	TestTrue(
		TEXT("The first-run guidance must clarify that the Unreal download is authoritative over the web simulator."),
		HyperTwistFirstRunLaunchSurfaceTestInternal::ContainsGuidanceSubstring(
			Lines,
				TEXT("downloadable Unreal build is the authoritative")
			)
		);
	TestTrue(
		TEXT("The first-run guidance must tell operators where to find runtime and startup logs."),
		HyperTwistFirstRunLaunchSurfaceTestInternal::ContainsGuidanceSubstring(
			Lines,
			TEXT("Saved/Logs/HyperTwistRuntime-latest.log")
		)
			&& HyperTwistFirstRunLaunchSurfaceTestInternal::ContainsGuidanceSubstring(
				Lines,
				TEXT("Saved/Logs/HyperTwistFirstRunLaunch-latest.log")
			)
	);
	TestTrue(
		TEXT("The first-run guidance must state the automatic gameplay fallback instead of leaving boot on a blank map."),
		HyperTwistFirstRunLaunchSurfaceTestInternal::ContainsGuidanceSubstring(
			Lines,
			TEXT("automatically falls back into Classic Cube Free Play")
		)
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistFirstRunLaunchGameModeDefaultsTest,
	"HyperTwist.FirstRun.GameModeDefaults",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistFirstRunLaunchGameModeDefaultsTest::RunTest(const FString& Parameters)
{
	const AHyperTwistFirstRunLaunchGameMode* GameMode =
		GetDefault<AHyperTwistFirstRunLaunchGameMode>();
	TestNotNull(TEXT("The first-run game mode defaults must be constructible."), GameMode);
	if (GameMode == nullptr)
	{
		return false;
	}

	TestEqual(
		TEXT("The first-run game mode must boot through the first-run player controller."),
			GameMode->PlayerControllerClass.Get(),
			AHyperTwistFirstRunLaunchPlayerController::StaticClass()
		);
	TestEqual(
		TEXT("The first-run contract must publish the packaged startup map."),
		UHyperTwistFirstRunLaunchLibrary::GetPackagedStartupMapAssetPath(),
		FString(TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_ClassicTraining"))
	);
	TestEqual(
		TEXT("The first-run contract must publish the default runtime log file name."),
		UHyperTwistFirstRunLaunchLibrary::GetRuntimeLogFileName(),
		FString(TEXT("HyperTwistRuntime-latest.log"))
	);
	TestEqual(
		TEXT("The first-run contract must publish the startup diagnostics log file name."),
		UHyperTwistFirstRunLaunchLibrary::GetStartupDiagnosticsLogFileName(),
		FString(TEXT("HyperTwistFirstRunLaunch-latest.log"))
	);

	const AHyperTwistFirstRunLaunchPlayerController* PlayerController =
		GetDefault<AHyperTwistFirstRunLaunchPlayerController>();
	TestNotNull(TEXT("The first-run player controller defaults must be constructible."), PlayerController);
	TestTrue(
		TEXT("The first-run player controller must auto-show the launch menu by default."),
		PlayerController != nullptr && PlayerController->bShowFirstRunLaunchOnBeginPlay
	);
	TestTrue(
		TEXT("The first-run surface must render above ordinary gameplay HUD layers."),
		PlayerController != nullptr && PlayerController->FirstRunLaunchZOrder > 0
	);
	TestTrue(
		TEXT("The first-run surface must use bounded delayed layout validation."),
		PlayerController != nullptr
			&& PlayerController->MaxFirstRunSurfaceValidationAttempts > 1
			&& PlayerController->FirstRunSurfaceValidationIntervalSeconds > 0.0f
	);
	TestTrue(
		TEXT("The first-run player controller must default to the native first-run widget."),
		PlayerController != nullptr && PlayerController->FirstRunLaunchWidgetClass.Get() == nullptr
	);

	const UHyperTwistFirstRunLaunchWidget* WidgetDefaults =
		GetDefault<UHyperTwistFirstRunLaunchWidget>();
	TestNotNull(TEXT("The first-run launch widget defaults must be constructible."), WidgetDefaults);

	FString GameDefaultMap;
	FString ServerDefaultMap;
	FString LocalMapOptions;
	TestTrue(
		TEXT("The loaded project config must expose a game default map for packaged boot."),
		GConfig != nullptr
			&& GConfig->GetString(
				TEXT("/Script/EngineSettings.GameMapsSettings"),
				TEXT("GameDefaultMap"),
				GameDefaultMap,
				GEngineIni
			)
	);
	TestTrue(
		TEXT("The loaded project config must expose a server default map for packaged boot."),
		GConfig != nullptr
			&& GConfig->GetString(
				TEXT("/Script/EngineSettings.GameMapsSettings"),
				TEXT("ServerDefaultMap"),
				ServerDefaultMap,
				GEngineIni
			)
	);
	TestTrue(
		TEXT("The loaded project config must expose explicit startup map options for packaged boot."),
		GConfig != nullptr
			&& GConfig->GetString(
				TEXT("/Script/EngineSettings.GameMapsSettings"),
				TEXT("LocalMapOptions"),
				LocalMapOptions,
				GEngineIni
			)
	);
	TestEqual(
		TEXT("Packaged boot must no longer point at the blank Entry map."),
		GameDefaultMap,
		UHyperTwistFirstRunLaunchLibrary::GetPackagedStartupMapAssetPath()
	);
	TestEqual(
		TEXT("Server boot must match the same visible startup map rather than Entry."),
		ServerDefaultMap,
		UHyperTwistFirstRunLaunchLibrary::GetPackagedStartupMapAssetPath()
	);
	TestEqual(
		TEXT("Packaged boot must force the first-run game mode even when the startup map carries its own authored override."),
		LocalMapOptions,
		FString::Printf(
			TEXT("?game=%s"),
			*UHyperTwistFirstRunLaunchLibrary::GetFirstRunGameModeClassPath()
		)
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistNativeWidgetRootLifecycleTest,
	"HyperTwist.FirstRun.NativeWidgetRootLifecycle",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistNativeWidgetRootLifecycleTest::RunTest(const FString& Parameters)
{
	UHyperTwistFirstRunLaunchWidget* FirstRunWidget =
		NewObject<UHyperTwistFirstRunLaunchWidget>();
	TestNotNull(TEXT("The native first-run widget must be constructible."), FirstRunWidget);
	if (FirstRunWidget != nullptr)
	{
		FirstRunWidget->TakeWidget();
		TestTrue(
			TEXT("TakeWidget must adopt a fully built first-run tree rather than caching an empty spacer."),
			FirstRunWidget->IsFirstRunLaunchSurfaceReady()
		);

		const UButton* CoachDashboardButton = FirstRunWidget->WidgetTree != nullptr
			? Cast<UButton>(FirstRunWidget->WidgetTree->FindWidget(FName(TEXT("CoachDashboardRouteButton"))))
			: nullptr;
		const UButton* ClassicCubeButton = FirstRunWidget->WidgetTree != nullptr
			? Cast<UButton>(FirstRunWidget->WidgetTree->FindWidget(FName(TEXT("ClassicCubeRouteButton"))))
			: nullptr;
		TestNotNull(TEXT("The native launch tree must retain the coach-dashboard route button."), CoachDashboardButton);
		TestNotNull(TEXT("The native launch tree must retain the Classic cube route button."), ClassicCubeButton);
		TestTrue(
			TEXT("The coach-dashboard route button must retain its click delegate after Slate rebuild."),
			CoachDashboardButton != nullptr && CoachDashboardButton->OnClicked.IsBound()
		);
		TestTrue(
			TEXT("The Classic cube route button must retain its click delegate after Slate rebuild."),
			ClassicCubeButton != nullptr && ClassicCubeButton->OnClicked.IsBound()
		);

		const TArray<FName> LearningStudioButtonNames = {
			TEXT("LearningStudioOllButton"),
			TEXT("LearningStudioCrossButton"),
			TEXT("LearningStudioFiveStyleButton"),
			TEXT("LearningStudioRouxButton"),
			TEXT("LearningStudioBlindfoldButton"),
			TEXT("LearningStudioRevealButton"),
			TEXT("LearningStudioRememberedButton"),
			TEXT("LearningStudioReviewButton")
		};
		for (const FName ButtonName : LearningStudioButtonNames)
		{
			const UButton* LearningButton = FirstRunWidget->WidgetTree != nullptr
				? Cast<UButton>(FirstRunWidget->WidgetTree->FindWidget(ButtonName))
				: nullptr;
			TestNotNull(
				*FString::Printf(
					TEXT("The native Learning Studio must expose '%s'."),
					*ButtonName.ToString()),
				LearningButton);
			TestTrue(
				*FString::Printf(
					TEXT("Learning Studio button '%s' must retain its action binding."),
					*ButtonName.ToString()),
				LearningButton != nullptr && LearningButton->OnClicked.IsBound());
		}
		TestNotNull(
			TEXT("The native Learning Studio must expose its active exercise panel."),
			FirstRunWidget->WidgetTree != nullptr
				? FirstRunWidget->WidgetTree->FindWidget(
					FName(TEXT("LearningStudioExercisePanel")))
				: nullptr);
		const UButton* AboutButton = FirstRunWidget->WidgetTree != nullptr
			? Cast<UButton>(
				FirstRunWidget->WidgetTree->FindWidget(
					FName(TEXT("AboutNavButton"))))
			: nullptr;
		TestNotNull(
			TEXT("The native menu must expose About & Credits."),
			AboutButton);
		TestTrue(
			TEXT("The About & Credits navigation action must remain bound."),
			AboutButton != nullptr && AboutButton->OnClicked.IsBound());
		TestNotNull(
			TEXT("The native menu must build the complete integration-credit page."),
			FirstRunWidget->WidgetTree != nullptr
				? FirstRunWidget->WidgetTree->FindWidget(
					FName(TEXT("AboutPage")))
				: nullptr);
	}

	UHyperTwistClassicCubeHUDWidget* ClassicHudWidget =
		NewObject<UHyperTwistClassicCubeHUDWidget>();
	TestNotNull(TEXT("The native classic cube HUD must be constructible."), ClassicHudWidget);
	if (ClassicHudWidget != nullptr)
	{
		ClassicHudWidget->TakeWidget();
		TestTrue(
			TEXT("TakeWidget must adopt the native classic cube HUD tree."),
			ClassicHudWidget->IsClassicCubeHudSurfaceReady()
		);
	}

	UHyperTwistCoachDashboardWidget* DashboardWidget =
		NewObject<UHyperTwistCoachDashboardWidget>();
	TestNotNull(TEXT("The native coach dashboard must be constructible."), DashboardWidget);
	if (DashboardWidget != nullptr)
	{
		DashboardWidget->TakeWidget();
		TestTrue(
			TEXT("TakeWidget must adopt the scrollable native coach dashboard tree."),
			DashboardWidget->IsCoachDashboardSurfaceReady()
		);
		TestNotNull(
			TEXT("The native coach dashboard must expose its scroll container."),
			DashboardWidget->WidgetTree != nullptr
				? DashboardWidget->WidgetTree->FindWidget(FName(TEXT("CoachDashboardScroll")))
				: nullptr
		);
	}

	return true;
}

#endif
