#if WITH_DEV_AUTOMATION_TESTS

#include "Dom/JsonObject.h"
#include "Misc/AutomationTest.h"
#include "Serialization/JsonSerializer.h"

#include "HyperTwistAlgorithm/HyperTwistAlgorithmLibrary.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmKeyboard.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmParser.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmShare.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmSerializer.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmValidation.h"
#include "HyperTwistCore/HyperTwistCoreLibrary.h"

namespace HyperTwistAlgorithmAutomationTestInternal
{
	bool TryParseSequence(
		FAutomationTestBase& Test,
		const FString& Input,
		FHyperTwistAlgorithmSequence& OutSequence)
	{
		const FHyperTwistAlgorithmParseResult ParseResult =
			UHyperTwistAlgorithmParser::ParseAlgorithm(Input);

		Test.TestTrue(FString::Printf(TEXT("Parse succeeded for '%s'"), *Input), ParseResult.bSuccess);
		if (!ParseResult.bSuccess)
		{
			Test.AddError(FString::Printf(
				TEXT("Parse failed for '%s' at %d: %s"),
				*Input,
				ParseResult.ErrorPosition,
				*ParseResult.ErrorMessage));
			OutSequence = FHyperTwistAlgorithmSequence();
			return false;
		}

		OutSequence = ParseResult.Sequence;
		return true;
	}

	bool TryRoundTripJson(
		FAutomationTestBase& Test,
		const FHyperTwistAlgorithmSequence& InputSequence,
		FHyperTwistAlgorithmSequence& OutSequence,
		const FString& ContextLabel)
	{
		FString JsonText;
		const bool bSerializeSucceeded =
			UHyperTwistAlgorithmSerializer::TrySerializeToJson(InputSequence, JsonText);
		Test.TestTrue(
			FString::Printf(TEXT("Structured JSON serialization succeeded for %s"), *ContextLabel),
			bSerializeSucceeded);
		if (!bSerializeSucceeded)
		{
			return false;
		}

		FString ParseError;
		const bool bParseSucceeded =
			UHyperTwistAlgorithmParser::TryParseFromJson(JsonText, OutSequence, ParseError);
		Test.TestTrue(
			FString::Printf(TEXT("Structured JSON parse succeeded for %s"), *ContextLabel),
			bParseSucceeded);
		if (!bParseSucceeded)
		{
			Test.AddError(FString::Printf(
				TEXT("Structured JSON parse failed for %s: %s"),
				*ContextLabel,
				*ParseError));
			return false;
		}

		return true;
	}

	FString SerializeSequence(const FHyperTwistAlgorithmSequence& Sequence)
	{
		return UHyperTwistAlgorithmSerializer::SerializeAlgorithm(Sequence);
	}

	bool ValidationResultsMatch(
		const FHyperTwistAlgorithmValidationResult& A,
		const FHyperTwistAlgorithmValidationResult& B)
	{
		if (A.bIsValid != B.bIsValid
			|| A.ProfileName != B.ProfileName
			|| A.Issues.Num() != B.Issues.Num())
		{
			return false;
		}

		for (int32 Index = 0; Index < A.Issues.Num(); ++Index)
		{
			if (A.Issues[Index].IssueCode != B.Issues[Index].IssueCode
				|| A.Issues[Index].NodePath != B.Issues[Index].NodePath
				|| A.Issues[Index].Message != B.Issues[Index].Message)
			{
				return false;
			}
		}

		return true;
	}

	bool ResultContainsIssue(
		const FHyperTwistAlgorithmValidationResult& Result,
		const EHyperTwistAlgorithmValidationIssueCode IssueCode)
	{
		for (const FHyperTwistAlgorithmValidationIssue& Issue : Result.Issues)
		{
			if (Issue.IssueCode == IssueCode)
			{
				return true;
			}
		}

		return false;
	}

	bool ShareDocumentsMatch(
		const FHyperTwistAlgorithmShareDocument& A,
		const FHyperTwistAlgorithmShareDocument& B)
	{
		const FString ExpectedMainCanonicalText = A.MainCanonicalText.IsEmpty()
			? UHyperTwistAlgorithmSerializer::SerializeAlgorithm(A.MainSequence)
			: A.MainCanonicalText;
		const FString ExpectedSetupCanonicalText = A.SetupCanonicalText.IsEmpty()
			? UHyperTwistAlgorithmSerializer::SerializeAlgorithm(A.SetupSequence)
			: A.SetupCanonicalText;

		return ExpectedMainCanonicalText == B.MainCanonicalText
			&& A.bHasSetupSequence == B.bHasSetupSequence
			&& (!A.bHasSetupSequence || ExpectedSetupCanonicalText == B.SetupCanonicalText)
			&& A.NotationProfile == B.NotationProfile
			&& A.Title == B.Title
			&& A.PuzzleScope == B.PuzzleScope
			&& A.StageScope == B.StageScope
			&& A.ViewMode == B.ViewMode
			&& A.InterpretationMode == B.InterpretationMode
			&& UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(A.MainSequence, B.MainSequence)
			&& (!A.bHasSetupSequence
				|| UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(A.SetupSequence, B.SetupSequence));
	}

	FHyperTwistAlgorithmSequence MakeSingleMoveSequence(const FHyperTwistAlgorithmBlockMove& Move)
	{
		FHyperTwistAlgorithmNode Node;
		Node.NodeType = EHyperTwistAlgorithmNodeType::BlockMove;
		Node.BlockMove = Move;

		FHyperTwistAlgorithmSequence Sequence;
		Sequence.Nodes.Add(Node);
		return Sequence;
	}

	bool TryRewriteMainCanonicalText(
		FAutomationTestBase& Test,
		const FString& InputJson,
		const FString& ReplacementCanonicalText,
		FString& OutJson)
	{
		TSharedPtr<FJsonObject> RootObject;
		const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(InputJson);
		if (!FJsonSerializer::Deserialize(Reader, RootObject) || !RootObject.IsValid())
		{
			Test.AddError(TEXT("Could not parse share JSON while preparing tamper case."));
			return false;
		}

		const TSharedPtr<FJsonValue>* MainValue = RootObject->Values.Find(TEXT("main"));
		if (MainValue == nullptr || !MainValue->IsValid() || (*MainValue)->Type != EJson::Object)
		{
			Test.AddError(TEXT("Share JSON did not contain a readable main algorithm entry."));
			return false;
		}

		TSharedPtr<FJsonObject> MainObject = (*MainValue)->AsObject();
		if (!MainObject.IsValid())
		{
			Test.AddError(TEXT("Share JSON main algorithm entry was not a valid object."));
			return false;
		}

		MainObject->SetStringField(TEXT("canonical_text"), ReplacementCanonicalText);

		OutJson.Empty();
		const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutJson);
		if (!FJsonSerializer::Serialize(RootObject.ToSharedRef(), Writer))
		{
			Test.AddError(TEXT("Could not serialize the tampered share JSON."));
			return false;
		}

		return true;
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmCanonicalizationAutomationTest,
	"HyperTwist.Algorithm.Bound1.Canonicalization",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmRoundTripAutomationTest,
	"HyperTwist.Algorithm.Bound1.RoundTrip",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmStructuredJsonAutomationTest,
	"HyperTwist.Algorithm.Bound1.StructuredJson",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmTraversalInvertAutomationTest,
	"HyperTwist.Algorithm.Bound2.Invert",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmTraversalExpandAutomationTest,
	"HyperTwist.Algorithm.Bound2.Expand",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmTraversalCoalesceAutomationTest,
	"HyperTwist.Algorithm.Bound2.Coalesce",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmTraversalStructuredJsonAutomationTest,
	"HyperTwist.Algorithm.Bound2.StructuredJsonRebaseline",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmValidationFlatOnlyAutomationTest,
	"HyperTwist.Algorithm.Bound3.FlatOnly",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmValidationClassicWcaAutomationTest,
	"HyperTwist.Algorithm.Bound3.ClassicWca",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmValidationJsonParityAutomationTest,
	"HyperTwist.Algorithm.Bound3.JsonProfileParity",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmShareStructuredAutomationTest,
	"HyperTwist.Algorithm.Bound4.StructuredShare",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmSharePayloadAutomationTest,
	"HyperTwist.Algorithm.Bound4.PayloadAndUrl",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmShareValidationAutomationTest,
	"HyperTwist.Algorithm.Bound4.ValidationAware",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmKeyboardDefaultProfileAutomationTest,
	"HyperTwist.Algorithm.Bound4.KeyboardDefaultProfile",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmKeyboardSuppressionAutomationTest,
	"HyperTwist.Algorithm.Bound4.KeyboardSuppression",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistAlgorithmKeyboardCustomProfileAutomationTest,
	"HyperTwist.Algorithm.Bound4.KeyboardCustomProfile",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

bool FHyperTwistAlgorithmCanonicalizationAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	struct FCanonicalCase
	{
		const TCHAR* Input;
		const TCHAR* Expected;
	};

	const TArray<FCanonicalCase> Cases = {
		{ TEXT("R1"), TEXT("R") },
		{ TEXT(". . ."), TEXT("...") },
		{ TEXT("R\r\nU"), TEXT("R\nU") },
		{ TEXT("R// note\r\nU"), TEXT("R // note\nU") },
		{ TEXT("R/* block */U"), TEXT("R /* block */ U") },
		{ TEXT("3Rw"), TEXT("3Rw") },
		{ TEXT("2-3Uw"), TEXT("2-3Uw") },
		{ TEXT("[R, U]2"), TEXT("[R, U]2") },
		{ TEXT("[R: U]'"), TEXT("[R: U]'") },
		{ TEXT("(R U)'"), TEXT("(R U)'") }
	};

	for (const FCanonicalCase& Case : Cases)
	{
		FString Canonical;
		FString ErrorMessage;
		int32 ErrorPosition = -1;
		const bool bSucceeded = UHyperTwistCoreLibrary::TryCanonicalizeAlgorithmNotation(
			Case.Input,
			Canonical,
			ErrorMessage,
			ErrorPosition
		);

		TestTrue(FString::Printf(TEXT("Canonicalization succeeded for '%s'"), Case.Input), bSucceeded);
		if (!bSucceeded)
		{
			AddError(FString::Printf(
				TEXT("Canonicalization failed for '%s' at %d: %s"),
				Case.Input,
				ErrorPosition,
				*ErrorMessage));
			continue;
		}

		TestEqual(
			FString::Printf(TEXT("Canonical output for '%s'"), Case.Input),
			Canonical,
			FString(Case.Expected));
	}

	return true;
}

bool FHyperTwistAlgorithmRoundTripAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	const TArray<FString> Cases = {
		TEXT("R U R' U'"),
		TEXT("(R U R' U')3"),
		TEXT("[R, U]2 [R: U]'"),
		TEXT("...\n// note\n/* block */\n2-3Uw R1"),
		TEXT("3Rw . [R: U2] (R U)'")
	};

	for (const FString& Input : Cases)
	{
		FString Reserialized;
		bool bStructurallyEqual = false;
		const bool bRoundTripSucceeded =
			UHyperTwistAlgorithmLibrary::ValidateRoundTrip(Input, Reserialized, bStructurallyEqual);

		TestTrue(FString::Printf(TEXT("Round-trip succeeded for '%s'"), *Input), bRoundTripSucceeded);
		TestTrue(FString::Printf(TEXT("Round-trip structural equality for '%s'"), *Input), bStructurallyEqual);

		if (!bRoundTripSucceeded || !bStructurallyEqual)
		{
			continue;
		}

		const FHyperTwistAlgorithmParseResult OriginalParse =
			UHyperTwistAlgorithmParser::ParseAlgorithm(Input);
		const FHyperTwistAlgorithmParseResult ReserializedParse =
			UHyperTwistAlgorithmParser::ParseAlgorithm(Reserialized);
		TestTrue(TEXT("Original parse succeeded"), OriginalParse.bSuccess);
		TestTrue(TEXT("Reserialized parse succeeded"), ReserializedParse.bSuccess);
		if (OriginalParse.bSuccess && ReserializedParse.bSuccess)
		{
			const FString OriginalCanonical =
				UHyperTwistAlgorithmSerializer::SerializeAlgorithm(OriginalParse.Sequence);
			const FString ReserializedCanonical =
				UHyperTwistAlgorithmSerializer::SerializeAlgorithm(ReserializedParse.Sequence);
			TestEqual(TEXT("Canonical form remains stable after round-trip"), ReserializedCanonical, OriginalCanonical);
		}
	}

	return true;
}

bool FHyperTwistAlgorithmStructuredJsonAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	const TArray<FString> Cases = {
		TEXT("R U R' U'"),
		TEXT("[R, U]2 [R: U]'"),
		TEXT("...\n// note\n/* block */\n2-3Uw R1")
	};

	for (const FString& Input : Cases)
	{
		const FHyperTwistAlgorithmParseResult ParseResult =
			UHyperTwistAlgorithmParser::ParseAlgorithm(Input);
		TestTrue(FString::Printf(TEXT("Text parse succeeded for '%s'"), *Input), ParseResult.bSuccess);
		if (!ParseResult.bSuccess)
		{
			continue;
		}

		FString JsonText;
		const bool bSerializeSucceeded =
			UHyperTwistAlgorithmSerializer::TrySerializeToJson(ParseResult.Sequence, JsonText);
		TestTrue(TEXT("Structured JSON serialization succeeded"), bSerializeSucceeded);
		if (!bSerializeSucceeded)
		{
			continue;
		}

		TestTrue(TEXT("Structured JSON carries owned schema marker"), JsonText.Contains(TEXT("\"schema\":\"ht-alg/v1\"")));

		FHyperTwistAlgorithmSequence JsonSequence;
		FString JsonError;
		const bool bParseFromJsonSucceeded =
			UHyperTwistAlgorithmParser::TryParseFromJson(JsonText, JsonSequence, JsonError);
		TestTrue(TEXT("Structured JSON parse succeeded"), bParseFromJsonSucceeded);
		if (!bParseFromJsonSucceeded)
		{
			AddError(FString::Printf(TEXT("Structured JSON parse failed: %s"), *JsonError));
			continue;
		}

		TestTrue(
			TEXT("Structured JSON round-trip preserves structure"),
			UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(ParseResult.Sequence, JsonSequence));

		const FString JsonCanonical = UHyperTwistAlgorithmSerializer::SerializeAlgorithm(JsonSequence);
		const FString TextCanonical = UHyperTwistAlgorithmSerializer::SerializeAlgorithm(ParseResult.Sequence);
		TestEqual(TEXT("Structured JSON preserves canonical text"), JsonCanonical, TextCanonical);
	}

	return true;
}

bool FHyperTwistAlgorithmTraversalInvertAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	using namespace HyperTwistAlgorithmAutomationTestInternal;

	struct FInvertCase
	{
		const TCHAR* Input;
		const TCHAR* Expected;
	};

	const TArray<FInvertCase> Cases = {
		{ TEXT("R U R' U'"), TEXT("U R U' R'") },
		{ TEXT("[R, U]2"), TEXT("[U, R]2") },
		{ TEXT("[R: U]"), TEXT("[R: U']") },
		{ TEXT("R . U"), TEXT("U' . R'") },
		{ TEXT("R\nU"), TEXT("U'\nR'") }
	};

	for (const FInvertCase& Case : Cases)
	{
		FHyperTwistAlgorithmSequence InputSequence;
		if (!TryParseSequence(*this, FString(Case.Input), InputSequence))
		{
			continue;
		}

		const FHyperTwistAlgorithmSequence InvertedSequence =
			UHyperTwistAlgorithmTraversal::InvertSequence(InputSequence);
		const FString ActualText = SerializeSequence(InvertedSequence);
		TestEqual(
			FString::Printf(TEXT("Inverted canonical text for '%s'"), Case.Input),
			ActualText,
			FString(Case.Expected));

		FHyperTwistAlgorithmSequence ExpectedSequence;
		if (TryParseSequence(*this, FString(Case.Expected), ExpectedSequence))
		{
			TestTrue(
				FString::Printf(TEXT("Inverted structure matches expected for '%s'"), Case.Input),
				UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(InvertedSequence, ExpectedSequence));
		}
	}

	return true;
}

bool FHyperTwistAlgorithmTraversalExpandAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	using namespace HyperTwistAlgorithmAutomationTestInternal;

	struct FExpandCase
	{
		const TCHAR* Input;
		const TCHAR* Expected;
	};

	const TArray<FExpandCase> Cases = {
		{ TEXT("(R U)'"), TEXT("U' R'") },
		{ TEXT("[R, U]2"), TEXT("R U R' U' R U R' U'") },
		{ TEXT("[R: U]"), TEXT("R U R'") },
		{ TEXT("R . [U, F]"), TEXT("R . U F U' F'") }
	};

	for (const FExpandCase& Case : Cases)
	{
		FHyperTwistAlgorithmSequence InputSequence;
		if (!TryParseSequence(*this, FString(Case.Input), InputSequence))
		{
			continue;
		}

		const FHyperTwistAlgorithmSequence ExpandedSequence =
			UHyperTwistAlgorithmTraversal::ExpandToFlatSequence(InputSequence);
		const FString ActualText = SerializeSequence(ExpandedSequence);
		TestEqual(
			FString::Printf(TEXT("Expanded canonical text for '%s'"), Case.Input),
			ActualText,
			FString(Case.Expected));

		FHyperTwistAlgorithmSequence ExpectedSequence;
		if (TryParseSequence(*this, FString(Case.Expected), ExpectedSequence))
		{
			TestTrue(
				FString::Printf(TEXT("Expanded structure matches expected for '%s'"), Case.Input),
				UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(ExpandedSequence, ExpectedSequence));
		}
	}

	return true;
}

bool FHyperTwistAlgorithmTraversalCoalesceAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	using namespace HyperTwistAlgorithmAutomationTestInternal;

	struct FCoalesceCase
	{
		const TCHAR* Input;
		const TCHAR* Expected;
	};

	const TArray<FCoalesceCase> Cases = {
		{ TEXT("R R"), TEXT("R2") },
		{ TEXT("R2 R2"), TEXT("R4") },
		{ TEXT("R R'"), TEXT("") },
		{ TEXT("R . R"), TEXT("R . R") },
		{ TEXT("R (R R') U"), TEXT("R U") }
	};

	for (const FCoalesceCase& Case : Cases)
	{
		FHyperTwistAlgorithmSequence InputSequence;
		if (!TryParseSequence(*this, FString(Case.Input), InputSequence))
		{
			continue;
		}

		const FHyperTwistAlgorithmSequence CoalescedSequence =
			UHyperTwistAlgorithmTraversal::ExpandAndCoalesceSequence(InputSequence);
		const FString ActualText = SerializeSequence(CoalescedSequence);
		TestEqual(
			FString::Printf(TEXT("Expanded/coalesced canonical text for '%s'"), Case.Input),
			ActualText,
			FString(Case.Expected));

		FHyperTwistAlgorithmSequence ExpectedSequence;
		if (TryParseSequence(*this, FString(Case.Expected), ExpectedSequence))
		{
			TestTrue(
				FString::Printf(TEXT("Expanded/coalesced structure matches expected for '%s'"), Case.Input),
				UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(CoalescedSequence, ExpectedSequence));
		}
	}

	FHyperTwistAlgorithmSequence IntegerSumSequence;
	if (TryParseSequence(*this, TEXT("R2 R2"), IntegerSumSequence))
	{
		const TArray<FHyperTwistAlgorithmBlockMove> Moves =
			UHyperTwistAlgorithmTraversal::ExpandAndSimplify(IntegerSumSequence);
		TestEqual(TEXT("Integer-sum coalescing retains one move"), Moves.Num(), 1);
		if (Moves.Num() == 1)
		{
			TestEqual(TEXT("Integer-sum coalescing preserves amount 4"), Moves[0].Amount, 4);
		}
	}

	return true;
}

bool FHyperTwistAlgorithmTraversalStructuredJsonAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	using namespace HyperTwistAlgorithmAutomationTestInternal;

	FHyperTwistAlgorithmSequence TextSequence;
	if (!TryParseSequence(*this, TEXT("R R . [U, F]"), TextSequence))
	{
		return false;
	}

	FHyperTwistAlgorithmSequence JsonSequence;
	if (!TryRoundTripJson(*this, TextSequence, JsonSequence, TEXT("input sequence")))
	{
		return false;
	}

	const FHyperTwistAlgorithmSequence ExpandedFromText =
		UHyperTwistAlgorithmTraversal::ExpandToFlatSequence(TextSequence);
	const FHyperTwistAlgorithmSequence ExpandedFromJson =
		UHyperTwistAlgorithmTraversal::ExpandToFlatSequence(JsonSequence);
	TestTrue(
		TEXT("Expanded text-ingested and JSON-ingested sequences match structurally"),
		UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(ExpandedFromText, ExpandedFromJson));
	TestEqual(
		TEXT("Expanded text-ingested and JSON-ingested sequences share canonical text"),
		SerializeSequence(ExpandedFromText),
		SerializeSequence(ExpandedFromJson));

	const FHyperTwistAlgorithmSequence CoalescedFromText =
		UHyperTwistAlgorithmTraversal::ExpandAndCoalesceSequence(TextSequence);
	const FHyperTwistAlgorithmSequence CoalescedFromJson =
		UHyperTwistAlgorithmTraversal::ExpandAndCoalesceSequence(JsonSequence);
	TestTrue(
		TEXT("Expanded/coalesced text-ingested and JSON-ingested sequences match structurally"),
		UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(CoalescedFromText, CoalescedFromJson));
	TestEqual(
		TEXT("Expanded/coalesced text-ingested and JSON-ingested sequences share canonical text"),
		SerializeSequence(CoalescedFromText),
		SerializeSequence(CoalescedFromJson));
	TestEqual(
		TEXT("Expanded/coalesced canonical text matches expected output"),
		SerializeSequence(CoalescedFromText),
		FString(TEXT("R2 . U F U' F'")));

	FHyperTwistAlgorithmSequence JsonRoundTrippedTransformedSequence;
	if (!TryRoundTripJson(
			*this,
			CoalescedFromText,
			JsonRoundTrippedTransformedSequence,
			TEXT("expanded/coalesced sequence")))
	{
		return false;
	}

	TestTrue(
		TEXT("Structured JSON preserves transformed sequence structure"),
		UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(
			CoalescedFromText,
			JsonRoundTrippedTransformedSequence));
	TestEqual(
		TEXT("Structured JSON preserves transformed sequence canonical text"),
		SerializeSequence(CoalescedFromText),
		SerializeSequence(JsonRoundTrippedTransformedSequence));

	return true;
}

bool FHyperTwistAlgorithmValidationFlatOnlyAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	using namespace HyperTwistAlgorithmAutomationTestInternal;

	FHyperTwistAlgorithmSequence FlatSequence;
	if (!TryParseSequence(*this, TEXT("R . // note\nU"), FlatSequence))
	{
		return false;
	}

	const FHyperTwistAlgorithmValidationResult FlatResult =
		UHyperTwistAlgorithmValidationLibrary::ValidateSequenceFlatOnly(FlatSequence);
	TestTrue(TEXT("Flat-only profile accepts atomic moves and annotations"), FlatResult.bIsValid);
	TestEqual(TEXT("Flat-only valid case has no issues"), FlatResult.Issues.Num(), 0);

	FHyperTwistAlgorithmSequence GroupSequence;
	if (!TryParseSequence(*this, TEXT("(R U)'"), GroupSequence))
	{
		return false;
	}

	const FHyperTwistAlgorithmValidationResult GroupResult =
		UHyperTwistAlgorithmValidationLibrary::ValidateSequenceFlatOnly(GroupSequence);
	TestFalse(TEXT("Flat-only profile rejects group containers"), GroupResult.bIsValid);
	TestTrue(
		TEXT("Flat-only profile returns typed container failure"),
		ResultContainsIssue(GroupResult, EHyperTwistAlgorithmValidationIssueCode::ContainerNotAllowed));

	FHyperTwistAlgorithmSequence CommutatorSequence;
	if (!TryParseSequence(*this, TEXT("[R, U]"), CommutatorSequence))
	{
		return false;
	}

	const FHyperTwistAlgorithmValidationResult CommutatorResult =
		UHyperTwistAlgorithmValidationLibrary::ValidateSequenceFlatOnly(CommutatorSequence);
	TestFalse(TEXT("Flat-only profile rejects commutators"), CommutatorResult.bIsValid);
	TestTrue(
		TEXT("Flat-only commutator rejection uses typed container failure"),
		ResultContainsIssue(CommutatorResult, EHyperTwistAlgorithmValidationIssueCode::ContainerNotAllowed));

	return true;
}

bool FHyperTwistAlgorithmValidationClassicWcaAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	using namespace HyperTwistAlgorithmAutomationTestInternal;

	const TArray<FString> ValidCases = {
		TEXT("R U R' U'"),
		TEXT("3Rw"),
		TEXT("2-3Uw"),
		TEXT("x y z"),
		TEXT("M E S"),
		TEXT("u")
	};

	for (const FString& Input : ValidCases)
	{
		FHyperTwistAlgorithmSequence Sequence;
		if (!TryParseSequence(*this, Input, Sequence))
		{
			return false;
		}

		const FHyperTwistAlgorithmValidationResult Result =
			UHyperTwistAlgorithmValidationLibrary::ValidateSequenceClassicWca(Sequence);
		TestTrue(FString::Printf(TEXT("classic-wca accepts '%s'"), *Input), Result.bIsValid);
		if (!Result.bIsValid)
		{
			AddError(FString::Printf(
				TEXT("classic-wca rejected '%s' with %d issues"),
				*Input,
				Result.Issues.Num()));
		}
	}

	struct FInvalidCase
	{
		const TCHAR* Input;
		EHyperTwistAlgorithmValidationIssueCode ExpectedCode;
	};

	const TArray<FInvalidCase> InvalidCases = {
		{ TEXT("P"), EHyperTwistAlgorithmValidationIssueCode::UnknownMoveFamily },
		{ TEXT("0R"), EHyperTwistAlgorithmValidationIssueCode::LayerIndexMustBePositive },
		{ TEXT("3-2Uw"), EHyperTwistAlgorithmValidationIssueCode::LayerRangeMustAscend },
		{ TEXT("2x"), EHyperTwistAlgorithmValidationIssueCode::LayerPrefixNotAllowedForFamily },
		{ TEXT("2M"), EHyperTwistAlgorithmValidationIssueCode::LayerPrefixNotAllowedForFamily }
	};

	for (const FInvalidCase& Case : InvalidCases)
	{
		FHyperTwistAlgorithmSequence Sequence;
		if (!TryParseSequence(*this, FString(Case.Input), Sequence))
		{
			return false;
		}

		const FHyperTwistAlgorithmValidationResult Result =
			UHyperTwistAlgorithmValidationLibrary::ValidateSequenceClassicWca(Sequence);
		TestFalse(FString::Printf(TEXT("classic-wca rejects '%s'"), Case.Input), Result.bIsValid);
		TestTrue(
			FString::Printf(TEXT("classic-wca reports typed issue for '%s'"), Case.Input),
			ResultContainsIssue(Result, Case.ExpectedCode));
	}

	return true;
}

bool FHyperTwistAlgorithmValidationJsonParityAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	using namespace HyperTwistAlgorithmAutomationTestInternal;

	FHyperTwistAlgorithmSequence InvalidTextSequence;
	if (!TryParseSequence(*this, TEXT("(R U)'"), InvalidTextSequence))
	{
		return false;
	}

	FHyperTwistAlgorithmSequence InvalidJsonSequence;
	if (!TryRoundTripJson(*this, InvalidTextSequence, InvalidJsonSequence, TEXT("flat-only invalid sequence")))
	{
		return false;
	}

	const FHyperTwistAlgorithmValidationResult InvalidTextResult =
		UHyperTwistAlgorithmValidationLibrary::ValidateSequenceFlatOnly(InvalidTextSequence);
	const FHyperTwistAlgorithmValidationResult InvalidJsonResult =
		UHyperTwistAlgorithmValidationLibrary::ValidateSequenceFlatOnly(InvalidJsonSequence);
	TestTrue(
		TEXT("Flat-only validation matches between text-ingested and JSON-ingested sequences"),
		ValidationResultsMatch(InvalidTextResult, InvalidJsonResult));

	FHyperTwistAlgorithmSequence ValidTextSequence;
	if (!TryParseSequence(*this, TEXT("2-3Uw R x"), ValidTextSequence))
	{
		return false;
	}

	FHyperTwistAlgorithmSequence ValidJsonSequence;
	if (!TryRoundTripJson(*this, ValidTextSequence, ValidJsonSequence, TEXT("classic-wca valid sequence")))
	{
		return false;
	}

	const FHyperTwistAlgorithmValidationResult ValidTextResult =
		UHyperTwistAlgorithmValidationLibrary::ValidateSequenceClassicWca(ValidTextSequence);
	const FHyperTwistAlgorithmValidationResult ValidJsonResult =
		UHyperTwistAlgorithmValidationLibrary::ValidateSequenceClassicWca(ValidJsonSequence);
	TestTrue(TEXT("classic-wca text-ingested sequence validates"), ValidTextResult.bIsValid);
	TestTrue(TEXT("classic-wca JSON-ingested sequence validates"), ValidJsonResult.bIsValid);
	TestTrue(
		TEXT("classic-wca validation matches between text-ingested and JSON-ingested sequences"),
		ValidationResultsMatch(ValidTextResult, ValidJsonResult));

	return true;
}

bool FHyperTwistAlgorithmShareStructuredAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	using namespace HyperTwistAlgorithmAutomationTestInternal;

	FHyperTwistAlgorithmSequence MainSequence;
	if (!TryParseSequence(*this, TEXT("R U R' U'"), MainSequence))
	{
		return false;
	}

	FHyperTwistAlgorithmSequence SetupSequence;
	if (!TryParseSequence(*this, TEXT("x y"), SetupSequence))
	{
		return false;
	}

	FHyperTwistAlgorithmShareDocument Document;
	Document.MainSequence = MainSequence;
	Document.bHasSetupSequence = true;
	Document.SetupSequence = SetupSequence;
	Document.NotationProfile = TEXT("classic-wca");
	Document.Title = TEXT("Sune demo");
	Document.PuzzleScope = TEXT("3x3x3");
	Document.StageScope = TEXT("oll");
	Document.ViewMode = TEXT("playback");
	Document.InterpretationMode = TEXT("direct");

	FString ShareJson;
	FString Error;
	const bool bSerializeSucceeded =
		UHyperTwistAlgorithmShareLibrary::TrySerializeShareDocumentToJson(Document, ShareJson, Error);
	TestTrue(TEXT("Structured share serialization succeeded"), bSerializeSucceeded);
	if (!bSerializeSucceeded)
	{
		AddError(FString::Printf(TEXT("Structured share serialization failed: %s"), *Error));
		return false;
	}

	TestTrue(
		TEXT("Structured share carries owned share schema marker"),
		ShareJson.Contains(TEXT("ht-alg-share/v1")));
	TestTrue(
		TEXT("Structured share carries owned algorithm schema marker"),
		ShareJson.Contains(TEXT("ht-alg/v1")));

	FHyperTwistAlgorithmShareDocument ParsedDocument;
	const bool bParseSucceeded =
		UHyperTwistAlgorithmShareLibrary::TryParseShareDocumentFromJson(ShareJson, ParsedDocument, Error);
	TestTrue(TEXT("Structured share parse succeeded"), bParseSucceeded);
	if (!bParseSucceeded)
	{
		AddError(FString::Printf(TEXT("Structured share parse failed: %s"), *Error));
		return false;
	}

	TestTrue(
		TEXT("Structured share preserves document shape"),
		ShareDocumentsMatch(Document, ParsedDocument));
	TestEqual(TEXT("Structured share populates main canonical text"), ParsedDocument.MainCanonicalText, FString(TEXT("R U R' U'")));
	TestEqual(TEXT("Structured share populates setup canonical text"), ParsedDocument.SetupCanonicalText, FString(TEXT("x y")));

	return true;
}

bool FHyperTwistAlgorithmSharePayloadAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	using namespace HyperTwistAlgorithmAutomationTestInternal;

	FHyperTwistAlgorithmSequence MainSequence;
	if (!TryParseSequence(*this, TEXT("[R, U]2"), MainSequence))
	{
		return false;
	}

	FHyperTwistAlgorithmShareDocument Document;
	Document.MainSequence = MainSequence;
	Document.NotationProfile = TEXT("classic-wca");
	Document.Title = TEXT("Commutator drill");
	Document.ViewMode = TEXT("editor");

	FString Payload;
	FString Error;
	const bool bPayloadSucceeded =
		UHyperTwistAlgorithmShareLibrary::TryEncodeShareDocumentPayload(Document, Payload, Error);
	TestTrue(TEXT("Share payload encoding succeeded"), bPayloadSucceeded);
	if (!bPayloadSucceeded)
	{
		AddError(FString::Printf(TEXT("Share payload encoding failed: %s"), *Error));
		return false;
	}

	TestFalse(TEXT("Share payload is not empty"), Payload.IsEmpty());
	TestFalse(TEXT("Share payload is URL-safe rather than raw JSON"), Payload.Contains(TEXT("{")));

	FHyperTwistAlgorithmShareDocument PayloadDocument;
	const bool bDecodeSucceeded =
		UHyperTwistAlgorithmShareLibrary::TryDecodeShareDocumentPayload(Payload, PayloadDocument, Error);
	TestTrue(TEXT("Share payload decode succeeded"), bDecodeSucceeded);
	if (!bDecodeSucceeded)
	{
		AddError(FString::Printf(TEXT("Share payload decode failed: %s"), *Error));
		return false;
	}

	TestTrue(
		TEXT("Share payload preserves document shape"),
		ShareDocumentsMatch(Document, PayloadDocument));

	FString ShareUrl;
	const bool bUrlSucceeded =
		UHyperTwistAlgorithmShareLibrary::TryBuildShareUrl(
			TEXT("https://example.com/trainer?mode=focus#session"),
			Document,
			ShareUrl,
			Error);
	TestTrue(TEXT("Share URL build succeeded"), bUrlSucceeded);
	if (!bUrlSucceeded)
	{
		AddError(FString::Printf(TEXT("Share URL build failed: %s"), *Error));
		return false;
	}

	TestTrue(TEXT("Share URL contains HyperTwist query key"), ShareUrl.Contains(TEXT("htalg=")));
	TestTrue(TEXT("Share URL preserves fragment suffix"), ShareUrl.EndsWith(TEXT("#session")));

	FHyperTwistAlgorithmShareDocument UrlDocument;
	const bool bUrlParseSucceeded =
		UHyperTwistAlgorithmShareLibrary::TryParseShareUrl(ShareUrl, UrlDocument, Error);
	TestTrue(TEXT("Share URL parse succeeded"), bUrlParseSucceeded);
	if (!bUrlParseSucceeded)
	{
		AddError(FString::Printf(TEXT("Share URL parse failed: %s"), *Error));
		return false;
	}

	TestTrue(
		TEXT("Share URL preserves document shape"),
		ShareDocumentsMatch(Document, UrlDocument));

	return true;
}

bool FHyperTwistAlgorithmShareValidationAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	using namespace HyperTwistAlgorithmAutomationTestInternal;

	FHyperTwistAlgorithmSequence InvalidSequence;
	if (!TryParseSequence(*this, TEXT("2x"), InvalidSequence))
	{
		return false;
	}

	FHyperTwistAlgorithmShareDocument InvalidDocument;
	InvalidDocument.MainSequence = InvalidSequence;
	InvalidDocument.NotationProfile = TEXT("classic-wca");

	FString ShareJson;
	FString Error;
	const bool bInvalidSerializeSucceeded =
		UHyperTwistAlgorithmShareLibrary::TrySerializeShareDocumentToJson(InvalidDocument, ShareJson, Error);
	TestFalse(TEXT("Validation-aware share rejects invalid profile content"), bInvalidSerializeSucceeded);
	TestTrue(TEXT("Validation-aware share returns a validation error"), Error.Contains(TEXT("failed validation")));

	FHyperTwistAlgorithmSequence ValidSequence;
	if (!TryParseSequence(*this, TEXT("R"), ValidSequence))
	{
		return false;
	}

	FHyperTwistAlgorithmShareDocument ValidDocument;
	ValidDocument.MainSequence = ValidSequence;
	ValidDocument.NotationProfile = TEXT("classic-wca");

	if (!UHyperTwistAlgorithmShareLibrary::TrySerializeShareDocumentToJson(ValidDocument, ShareJson, Error))
	{
		AddError(FString::Printf(TEXT("Valid share serialization failed: %s"), *Error));
		return false;
	}

	FString TamperedJson;
	if (!TryRewriteMainCanonicalText(*this, ShareJson, TEXT("R2"), TamperedJson))
	{
		return false;
	}
	FHyperTwistAlgorithmShareDocument ParsedDocument;
	const bool bTamperedParseSucceeded =
		UHyperTwistAlgorithmShareLibrary::TryParseShareDocumentFromJson(TamperedJson, ParsedDocument, Error);
	TestFalse(TEXT("Share parse rejects canonical/structured mismatch"), bTamperedParseSucceeded);
	TestTrue(
		TEXT("Mismatch error mentions canonical alignment"),
		Error.Contains(TEXT("canonical text")));

	return true;
}

bool FHyperTwistAlgorithmKeyboardDefaultProfileAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	using namespace HyperTwistAlgorithmAutomationTestInternal;

	const FString DefaultProfileName = UHyperTwistAlgorithmKeyboardLibrary::GetDefaultKeyboardProfileName();
	TestEqual(
		TEXT("Default keyboard profile name is stable"),
		DefaultProfileName,
		FString(TEXT("classic-wca-keyboard/v1")));

	FHyperTwistAlgorithmKeyboardEvent Event;
	Event.Key = EKeys::I;

	const FHyperTwistAlgorithmKeyboardMappingResult Result =
		UHyperTwistAlgorithmKeyboardLibrary::MapKeyboardEventForProfile(Event, DefaultProfileName);
	TestEqual(
		TEXT("Default profile emits a move for mapped key"),
		Result.Status,
		EHyperTwistAlgorithmKeyboardMappingStatus::ProducedMove);
	TestTrue(TEXT("Default profile returns move intent"), Result.bHasMoveIntent);
	TestEqual(TEXT("Default profile canonical text for I"), Result.CanonicalText, FString(TEXT("R")));

	const FHyperTwistAlgorithmSequence ExpectedSequence = MakeSingleMoveSequence(Result.Move);
	FHyperTwistAlgorithmSequence ParsedSequence;
	if (!TryParseSequence(*this, Result.CanonicalText, ParsedSequence))
	{
		return false;
	}

	TestTrue(
		TEXT("Keyboard move intent canonical text matches structural move"),
		UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(ExpectedSequence, ParsedSequence));

	const FHyperTwistAlgorithmValidationResult ValidationResult =
		UHyperTwistAlgorithmValidationLibrary::ValidateSequenceClassicWca(ExpectedSequence);
	TestTrue(TEXT("Default keyboard move intent validates under classic-wca"), ValidationResult.bIsValid);

	FHyperTwistAlgorithmKeyboardEvent PrimeEvent;
	PrimeEvent.Key = EKeys::K;
	const FHyperTwistAlgorithmKeyboardMappingResult PrimeResult =
		UHyperTwistAlgorithmKeyboardLibrary::MapKeyboardEventForProfile(PrimeEvent, DefaultProfileName);
	TestEqual(
		TEXT("Default profile canonical text for K"),
		PrimeResult.CanonicalText,
		FString(TEXT("R'")));

	return true;
}

bool FHyperTwistAlgorithmKeyboardSuppressionAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	const FString DefaultProfileName = UHyperTwistAlgorithmKeyboardLibrary::GetDefaultKeyboardProfileName();

	FHyperTwistAlgorithmKeyboardEvent CtrlEvent;
	CtrlEvent.Key = EKeys::I;
	CtrlEvent.bCtrlPressed = true;
	const FHyperTwistAlgorithmKeyboardMappingResult CtrlResult =
		UHyperTwistAlgorithmKeyboardLibrary::MapKeyboardEventForProfile(CtrlEvent, DefaultProfileName);
	TestEqual(
		TEXT("Ctrl modifier is suppressed by default"),
		CtrlResult.Status,
		EHyperTwistAlgorithmKeyboardMappingStatus::SuppressedByModifier);

	FHyperTwistAlgorithmKeyboardEvent AltEvent;
	AltEvent.Key = EKeys::I;
	AltEvent.bAltPressed = true;
	const FHyperTwistAlgorithmKeyboardMappingResult AltResult =
		UHyperTwistAlgorithmKeyboardLibrary::MapKeyboardEventForProfile(AltEvent, DefaultProfileName);
	TestEqual(
		TEXT("Alt modifier is suppressed by default"),
		AltResult.Status,
		EHyperTwistAlgorithmKeyboardMappingStatus::SuppressedByModifier);

	FHyperTwistAlgorithmKeyboardEvent MetaEvent;
	MetaEvent.Key = EKeys::I;
	MetaEvent.bMetaPressed = true;
	const FHyperTwistAlgorithmKeyboardMappingResult MetaResult =
		UHyperTwistAlgorithmKeyboardLibrary::MapKeyboardEventForProfile(MetaEvent, DefaultProfileName);
	TestEqual(
		TEXT("Meta modifier is suppressed by default"),
		MetaResult.Status,
		EHyperTwistAlgorithmKeyboardMappingStatus::SuppressedByModifier);

	FHyperTwistAlgorithmKeyboardEvent UnboundEvent;
	UnboundEvent.Key = EKeys::SpaceBar;
	const FHyperTwistAlgorithmKeyboardMappingResult UnboundResult =
		UHyperTwistAlgorithmKeyboardLibrary::MapKeyboardEventForProfile(UnboundEvent, DefaultProfileName);
	TestEqual(
		TEXT("Unbound key yields no mapping"),
		UnboundResult.Status,
		EHyperTwistAlgorithmKeyboardMappingStatus::NoMapping);

	const FHyperTwistAlgorithmKeyboardMappingResult UnsupportedResult =
		UHyperTwistAlgorithmKeyboardLibrary::MapKeyboardEventForProfile(UnboundEvent, TEXT("missing-profile"));
	TestEqual(
		TEXT("Unknown keyboard profile is reported explicitly"),
		UnsupportedResult.Status,
		EHyperTwistAlgorithmKeyboardMappingStatus::UnsupportedProfile);

	return true;
}

bool FHyperTwistAlgorithmKeyboardCustomProfileAutomationTest::RunTest(const FString& Parameters)
{
	(void)Parameters;

	FHyperTwistAlgorithmKeyboardProfile Profile;
	Profile.ProfileName = TEXT("custom-test-profile");
	Profile.Policy.bSuppressRepeat = true;

	FHyperTwistAlgorithmKeyboardBinding ShiftBinding;
	ShiftBinding.Key = EKeys::One;
	ShiftBinding.bRequiresShift = true;
	ShiftBinding.Move.Family = TEXT("R");
	ShiftBinding.Move.Amount = 2;
	Profile.Bindings.Add(ShiftBinding);

	FHyperTwistAlgorithmKeyboardBinding WideBinding;
	WideBinding.Key = EKeys::Two;
	WideBinding.Move.Family = TEXT("R");
	WideBinding.Move.Amount = 1;
	WideBinding.Move.MoveType = EHyperTwistAlgorithmMoveType::InnerSlice;
	WideBinding.Move.InnerLayer = 2;
	WideBinding.Move.bIsWide = true;
	Profile.Bindings.Add(WideBinding);

	FHyperTwistAlgorithmKeyboardEvent NoShiftEvent;
	NoShiftEvent.Key = EKeys::One;
	const FHyperTwistAlgorithmKeyboardMappingResult NoShiftResult =
		UHyperTwistAlgorithmKeyboardLibrary::MapKeyboardEvent(NoShiftEvent, Profile);
	TestEqual(
		TEXT("Shift-required binding does not fire without shift"),
		NoShiftResult.Status,
		EHyperTwistAlgorithmKeyboardMappingStatus::NoMapping);

	FHyperTwistAlgorithmKeyboardEvent ShiftEvent;
	ShiftEvent.Key = EKeys::One;
	ShiftEvent.bShiftPressed = true;
	const FHyperTwistAlgorithmKeyboardMappingResult ShiftResult =
		UHyperTwistAlgorithmKeyboardLibrary::MapKeyboardEvent(ShiftEvent, Profile);
	TestEqual(
		TEXT("Shift-required binding fires with shift"),
		ShiftResult.Status,
		EHyperTwistAlgorithmKeyboardMappingStatus::ProducedMove);
	TestEqual(TEXT("Shift-required binding canonicalizes correctly"), ShiftResult.CanonicalText, FString(TEXT("R2")));

	FHyperTwistAlgorithmKeyboardEvent RepeatEvent = ShiftEvent;
	RepeatEvent.bIsRepeat = true;
	const FHyperTwistAlgorithmKeyboardMappingResult RepeatResult =
		UHyperTwistAlgorithmKeyboardLibrary::MapKeyboardEvent(RepeatEvent, Profile);
	TestEqual(
		TEXT("Repeat suppression is profile-tunable"),
		RepeatResult.Status,
		EHyperTwistAlgorithmKeyboardMappingStatus::SuppressedByRepeat);

	FHyperTwistAlgorithmKeyboardEvent WideEvent;
	WideEvent.Key = EKeys::Two;
	const FHyperTwistAlgorithmKeyboardMappingResult WideResult =
		UHyperTwistAlgorithmKeyboardLibrary::MapKeyboardEvent(WideEvent, Profile);
	TestEqual(
		TEXT("Custom profile can emit wider move intents"),
		WideResult.Status,
		EHyperTwistAlgorithmKeyboardMappingStatus::ProducedMove);
	TestEqual(TEXT("Custom profile wide move canonicalizes correctly"), WideResult.CanonicalText, FString(TEXT("2Rw")));

	return true;
}

#endif
