// Clean-room implementation for HyperTwist algorithm-language subsystem
// Implemented from governance docs only (Phase 0R-D, Phase 1R, Phase 5R-A)
// Source: Behavioral contracts from cubing/alg.js Model A handoff (GPL-3.0-or-later lane)
//         No source code inspection. Clean-room workflow only.
// Date: 2026-05-15

#include "HyperTwistAlgorithm/HyperTwistAlgorithmParser.h"

#include "Dom/JsonObject.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"

namespace HyperTwistAlgorithmParserInternal
{
	const TCHAR* StructuredJsonSchema = TEXT("ht-alg/v1");

	bool IsPrimeChar(const TCHAR Character)
	{
		return Character == TEXT('\'')
			|| Character == TEXT('`')
			|| Character == 0x2019;
	}

	bool IsHorizontalWhitespace(const TCHAR Character)
	{
		return Character == TEXT(' ')
			|| Character == TEXT('\t')
			|| Character == TEXT('\v')
			|| Character == TEXT('\f');
	}

	bool IsIdentifierStart(const TCHAR Character)
	{
		return FChar::IsAlpha(Character) || Character == TEXT('_');
	}

	bool IsIdentifierContinuation(const TCHAR Character)
	{
		return FChar::IsAlpha(Character) || Character == TEXT('_');
	}

	struct FParserState
	{
		explicit FParserState(const FString& InText)
			: Text(InText)
		{
		}

		const FString& Text;
		int32 Position = 0;
		FString ErrorMessage;

		bool IsAtEnd() const
		{
			return Position >= Text.Len();
		}

		TCHAR CurrentChar() const
		{
			return Position < Text.Len() ? Text[Position] : TEXT('\0');
		}

		TCHAR PeekChar(const int32 Offset = 1) const
		{
			const int32 Index = Position + Offset;
			return Index < Text.Len() ? Text[Index] : TEXT('\0');
		}

		bool IsNewlineStart() const
		{
			return CurrentChar() == TEXT('\n') || CurrentChar() == TEXT('\r');
		}

		void Advance(const int32 Count = 1)
		{
			Position = FMath::Min(Position + Count, Text.Len());
		}

		void SkipHorizontalWhitespace()
		{
			while (!IsAtEnd() && IsHorizontalWhitespace(CurrentChar()))
			{
				Advance();
			}
		}

		void ConsumeNewline()
		{
			if (CurrentChar() == TEXT('\r') && PeekChar() == TEXT('\n'))
			{
				Advance(2);
				return;
			}

			if (IsNewlineStart())
			{
				Advance();
			}
		}

		bool Consume(const TCHAR Expected)
		{
			if (CurrentChar() != Expected)
			{
				return false;
			}

			Advance();
			return true;
		}

		void SetError(const FString& InErrorMessage)
		{
			if (ErrorMessage.IsEmpty())
			{
				ErrorMessage = InErrorMessage;
			}
		}

		bool TryReadUnsignedInteger(int32& OutValue)
		{
			OutValue = 0;
			bool bReadDigit = false;

			while (!IsAtEnd() && FChar::IsDigit(CurrentChar()))
			{
				bReadDigit = true;
				OutValue = (OutValue * 10) + (CurrentChar() - TEXT('0'));
				Advance();
			}

			return bReadDigit;
		}
	};

	int32 ReadSignedAmountSuffix(FParserState& State)
	{
		int32 Value = 0;
		const bool bHasDigits = State.TryReadUnsignedInteger(Value);
		const bool bNegative = IsPrimeChar(State.CurrentChar());
		if (bNegative)
		{
			State.Advance();
		}

		if (!bHasDigits && !bNegative)
		{
			return 1;
		}

		if (!bHasDigits)
		{
			Value = 1;
		}

		return bNegative ? -Value : Value;
	}

	FHyperTwistAlgorithmNode MakeUnitNode(const EHyperTwistAlgorithmNodeType NodeType)
	{
		FHyperTwistAlgorithmNode Node;
		Node.NodeType = NodeType;
		return Node;
	}

	bool TryReadComment(
		FParserState& State,
		FHyperTwistAlgorithmNode& OutNode)
	{
		if (State.CurrentChar() != TEXT('/')
			|| (State.PeekChar() != TEXT('/') && State.PeekChar() != TEXT('*')))
		{
			return false;
		}

		OutNode = MakeUnitNode(EHyperTwistAlgorithmNodeType::Comment);

		if (State.PeekChar() == TEXT('/'))
		{
			State.Advance(2);
			FString CommentText;
			while (!State.IsAtEnd() && !State.IsNewlineStart())
			{
				CommentText.AppendChar(State.CurrentChar());
				State.Advance();
			}

			OutNode.Comment.CommentType = EHyperTwistAlgorithmCommentType::Line;
			OutNode.Comment.CommentText = CommentText.TrimStartAndEnd();
			return true;
		}

		State.Advance(2);
		FString CommentText;
		while (!State.IsAtEnd())
		{
			if (State.CurrentChar() == TEXT('*') && State.PeekChar() == TEXT('/'))
			{
				State.Advance(2);
				OutNode.Comment.CommentType = EHyperTwistAlgorithmCommentType::Block;
				OutNode.Comment.CommentText = CommentText.TrimStartAndEnd();
				return true;
			}

			CommentText.AppendChar(State.CurrentChar());
			State.Advance();
		}

		State.SetError(TEXT("Unterminated block comment"));
		return false;
	}

	bool TryParseNodeSequence(
		FParserState& State,
		TArray<FHyperTwistAlgorithmNode>& OutNodes,
		const TArray<TCHAR>& Terminators);

	bool TryParseBlockMove(
		FParserState& State,
		FHyperTwistAlgorithmNode& OutNode)
	{
		const int32 StartPosition = State.Position;
		int32 PrefixStart = 0;
		int32 PrefixEnd = 0;
		const bool bHasPrefix = State.TryReadUnsignedInteger(PrefixStart);
		bool bHasRange = false;

		if (bHasPrefix && State.CurrentChar() == TEXT('-'))
		{
			State.Advance();
			if (!State.TryReadUnsignedInteger(PrefixEnd))
			{
				State.SetError(FString::Printf(
					TEXT("Expected range end after '-' at position %d"),
					State.Position));
				return false;
			}

			bHasRange = true;
		}

		if (!IsIdentifierStart(State.CurrentChar()))
		{
			State.SetError(FString::Printf(
				TEXT("Expected move family at position %d"),
				StartPosition));
			return false;
		}

		FString RawFamily;
		while (!State.IsAtEnd() && IsIdentifierContinuation(State.CurrentChar()))
		{
			RawFamily.AppendChar(State.CurrentChar());
			State.Advance();
		}

		bool bIsWide = false;
		if (RawFamily.Len() > 1 && RawFamily.EndsWith(TEXT("w")))
		{
			bIsWide = true;
			RawFamily = RawFamily.LeftChop(1);
		}

		const int32 Amount = ReadSignedAmountSuffix(State);

		OutNode = MakeUnitNode(EHyperTwistAlgorithmNodeType::BlockMove);
		OutNode.BlockMove.Family = RawFamily;
		OutNode.BlockMove.Amount = Amount;
		OutNode.BlockMove.bIsWide = bIsWide;
		OutNode.BlockMove.InnerLayer = 0;
		OutNode.BlockMove.OuterLayer = 0;

		if (bHasRange)
		{
			OutNode.BlockMove.MoveType = EHyperTwistAlgorithmMoveType::RangedSlice;
			OutNode.BlockMove.InnerLayer = PrefixStart;
			OutNode.BlockMove.OuterLayer = PrefixEnd;
		}
		else if (bHasPrefix)
		{
			OutNode.BlockMove.MoveType = EHyperTwistAlgorithmMoveType::InnerSlice;
			OutNode.BlockMove.InnerLayer = PrefixStart;
		}
		else
		{
			OutNode.BlockMove.MoveType = EHyperTwistAlgorithmMoveType::Plain;
		}

		return true;
	}

	bool TryParseGroup(
		FParserState& State,
		FHyperTwistAlgorithmNode& OutNode)
	{
		const int32 StartPosition = State.Position;
		if (!State.Consume(TEXT('(')))
		{
			State.SetError(FString::Printf(
				TEXT("Expected '(' at position %d"),
				StartPosition));
			return false;
		}

		TArray<FHyperTwistAlgorithmNode> InnerNodes;
		if (!TryParseNodeSequence(State, InnerNodes, { TEXT(')') }))
		{
			return false;
		}

		if (!State.Consume(TEXT(')')))
		{
			State.SetError(FString::Printf(
				TEXT("Expected ')' to close group at position %d"),
				State.Position));
			return false;
		}

		OutNode = MakeUnitNode(EHyperTwistAlgorithmNodeType::Group);
		OutNode.Group.Inner = MoveTemp(InnerNodes);
		OutNode.Group.Amount = ReadSignedAmountSuffix(State);
		return true;
	}

	bool TryParseBracketContainer(
		FParserState& State,
		FHyperTwistAlgorithmNode& OutNode)
	{
		const int32 StartPosition = State.Position;
		if (!State.Consume(TEXT('[')))
		{
			State.SetError(FString::Printf(
				TEXT("Expected '[' at position %d"),
				StartPosition));
			return false;
		}

		TArray<FHyperTwistAlgorithmNode> LeftNodes;
		if (!TryParseNodeSequence(State, LeftNodes, { TEXT(','), TEXT(':') }))
		{
			return false;
		}

		const bool bIsConjugate = State.Consume(TEXT(':'));
		if (!bIsConjugate && !State.Consume(TEXT(',')))
		{
			State.SetError(FString::Printf(
				TEXT("Expected ',' or ':' inside bracket container at position %d"),
				State.Position));
			return false;
		}

		TArray<FHyperTwistAlgorithmNode> RightNodes;
		if (!TryParseNodeSequence(State, RightNodes, { TEXT(']') }))
		{
			return false;
		}

		if (!State.Consume(TEXT(']')))
		{
			State.SetError(FString::Printf(
				TEXT("Expected ']' to close bracket container at position %d"),
				State.Position));
			return false;
		}

		if (bIsConjugate)
		{
			OutNode = MakeUnitNode(EHyperTwistAlgorithmNodeType::Conjugate);
			OutNode.Conjugate.A = MoveTemp(LeftNodes);
			OutNode.Conjugate.B = MoveTemp(RightNodes);
			OutNode.Conjugate.Amount = ReadSignedAmountSuffix(State);
			return true;
		}

		OutNode = MakeUnitNode(EHyperTwistAlgorithmNodeType::Commutator);
		OutNode.Commutator.A = MoveTemp(LeftNodes);
		OutNode.Commutator.B = MoveTemp(RightNodes);
		OutNode.Commutator.Amount = ReadSignedAmountSuffix(State);
		return true;
	}

	bool TryParseNodeSequence(
		FParserState& State,
		TArray<FHyperTwistAlgorithmNode>& OutNodes,
		const TArray<TCHAR>& Terminators)
	{
		while (!State.IsAtEnd())
		{
			State.SkipHorizontalWhitespace();
			if (State.IsAtEnd())
			{
				break;
			}

			if (Terminators.Contains(State.CurrentChar()))
			{
				break;
			}

			if (State.IsNewlineStart())
			{
				OutNodes.Add(MakeUnitNode(EHyperTwistAlgorithmNodeType::Newline));
				State.ConsumeNewline();
				continue;
			}

			if (State.CurrentChar() == TEXT(')') || State.CurrentChar() == TEXT(']'))
			{
				State.SetError(FString::Printf(
					TEXT("Unexpected closing delimiter '%c' at position %d"),
					State.CurrentChar(),
					State.Position));
				return false;
			}

			if (State.CurrentChar() == TEXT(',') || State.CurrentChar() == TEXT(':'))
			{
				State.SetError(FString::Printf(
					TEXT("Unexpected separator '%c' at position %d"),
					State.CurrentChar(),
					State.Position));
				return false;
			}

			if (State.CurrentChar() == TEXT('.') )
			{
				OutNodes.Add(MakeUnitNode(EHyperTwistAlgorithmNodeType::Pause));
				State.Advance();
				continue;
			}

			if (State.CurrentChar() == TEXT('/') && (State.PeekChar() == TEXT('/') || State.PeekChar() == TEXT('*')))
			{
				FHyperTwistAlgorithmNode CommentNode;
				if (!TryReadComment(State, CommentNode))
				{
					return false;
				}

				OutNodes.Add(CommentNode);
				continue;
			}

			if (State.CurrentChar() == TEXT('('))
			{
				FHyperTwistAlgorithmNode GroupNode;
				if (!TryParseGroup(State, GroupNode))
				{
					return false;
				}

				OutNodes.Add(GroupNode);
				continue;
			}

			if (State.CurrentChar() == TEXT('['))
			{
				FHyperTwistAlgorithmNode ContainerNode;
				if (!TryParseBracketContainer(State, ContainerNode))
				{
					return false;
				}

				OutNodes.Add(ContainerNode);
				continue;
			}

			FHyperTwistAlgorithmNode MoveNode;
			if (!TryParseBlockMove(State, MoveNode))
			{
				return false;
			}

			OutNodes.Add(MoveNode);
		}

		return true;
	}

	bool TryReadJsonObject(
		const TSharedPtr<FJsonValue>& Value,
		const FString& Context,
		TSharedPtr<FJsonObject>& OutObject,
		FString& OutError)
	{
		if (!Value.IsValid())
		{
			OutError = FString::Printf(TEXT("%s is missing"), *Context);
			return false;
		}

		if (Value->Type != EJson::Object)
		{
			OutError = FString::Printf(TEXT("%s must be a JSON object"), *Context);
			return false;
		}

		OutObject = Value->AsObject();
		if (!OutObject.IsValid())
		{
			OutError = FString::Printf(TEXT("%s could not be read as an object"), *Context);
			return false;
		}

		return true;
	}

	bool TryGetRequiredStringField(
		const TSharedPtr<FJsonObject>& Object,
		const TCHAR* FieldName,
		FString& OutValue,
		FString& OutError)
	{
		if (!Object.IsValid() || !Object->TryGetStringField(FieldName, OutValue))
		{
			OutError = FString::Printf(TEXT("Missing string field '%s'"), FieldName);
			return false;
		}

		return true;
	}

	bool TryGetRequiredIntegerField(
		const TSharedPtr<FJsonObject>& Object,
		const TCHAR* FieldName,
		int32& OutValue,
		FString& OutError)
	{
		double NumericValue = 0.0;
		if (!Object.IsValid() || !Object->TryGetNumberField(FieldName, NumericValue))
		{
			OutError = FString::Printf(TEXT("Missing numeric field '%s'"), FieldName);
			return false;
		}

		const double Rounded = FMath::RoundToDouble(NumericValue);
		if (!FMath::IsNearlyEqual(NumericValue, Rounded))
		{
			OutError = FString::Printf(TEXT("Field '%s' must be an integer"), FieldName);
			return false;
		}

		OutValue = static_cast<int32>(Rounded);
		return true;
	}

	bool TryGetRequiredNodeArrayField(
		const TSharedPtr<FJsonObject>& Object,
		const TCHAR* FieldName,
		const TArray<TSharedPtr<FJsonValue>>*& OutArray,
		FString& OutError)
	{
		if (!Object.IsValid() || !Object->TryGetArrayField(FieldName, OutArray) || OutArray == nullptr)
		{
			OutError = FString::Printf(TEXT("Missing array field '%s'"), FieldName);
			return false;
		}

		return true;
	}

	bool TryParseJsonNodeArray(
		const TArray<TSharedPtr<FJsonValue>>& NodeValues,
		TArray<FHyperTwistAlgorithmNode>& OutNodes,
		FString& OutError);

	bool TryParseJsonNodeObject(
		const TSharedPtr<FJsonObject>& NodeObject,
		FHyperTwistAlgorithmNode& OutNode,
		FString& OutError)
	{
		FString NodeKind;
		if (!TryGetRequiredStringField(NodeObject, TEXT("kind"), NodeKind, OutError))
		{
			return false;
		}

		if (NodeKind == TEXT("move"))
		{
			OutNode = MakeUnitNode(EHyperTwistAlgorithmNodeType::BlockMove);
			if (!TryGetRequiredStringField(NodeObject, TEXT("family"), OutNode.BlockMove.Family, OutError))
			{
				return false;
			}
			if (!TryGetRequiredIntegerField(NodeObject, TEXT("amount"), OutNode.BlockMove.Amount, OutError))
			{
				return false;
			}

			FString LayerMode;
			if (!TryGetRequiredStringField(NodeObject, TEXT("layer_mode"), LayerMode, OutError))
			{
				return false;
			}

			OutNode.BlockMove.bIsWide = false;
			NodeObject->TryGetBoolField(TEXT("wide"), OutNode.BlockMove.bIsWide);
			OutNode.BlockMove.InnerLayer = 0;
			OutNode.BlockMove.OuterLayer = 0;

			if (LayerMode == TEXT("plain"))
			{
				OutNode.BlockMove.MoveType = EHyperTwistAlgorithmMoveType::Plain;
				return true;
			}

			if (LayerMode == TEXT("index"))
			{
				OutNode.BlockMove.MoveType = EHyperTwistAlgorithmMoveType::InnerSlice;
				return TryGetRequiredIntegerField(
					NodeObject,
					TEXT("layer_index"),
					OutNode.BlockMove.InnerLayer,
					OutError);
			}

			if (LayerMode == TEXT("range"))
			{
				OutNode.BlockMove.MoveType = EHyperTwistAlgorithmMoveType::RangedSlice;
				return TryGetRequiredIntegerField(
						NodeObject,
						TEXT("layer_index"),
						OutNode.BlockMove.InnerLayer,
						OutError)
					&& TryGetRequiredIntegerField(
						NodeObject,
						TEXT("layer_end"),
						OutNode.BlockMove.OuterLayer,
						OutError);
			}

			OutError = FString::Printf(TEXT("Unsupported move layer_mode '%s'"), *LayerMode);
			return false;
		}

		if (NodeKind == TEXT("group"))
		{
			OutNode = MakeUnitNode(EHyperTwistAlgorithmNodeType::Group);
			if (!TryGetRequiredIntegerField(NodeObject, TEXT("amount"), OutNode.Group.Amount, OutError))
			{
				return false;
			}

			const TArray<TSharedPtr<FJsonValue>>* InnerValues = nullptr;
			if (!TryGetRequiredNodeArrayField(NodeObject, TEXT("items"), InnerValues, OutError))
			{
				return false;
			}

			return TryParseJsonNodeArray(*InnerValues, OutNode.Group.Inner, OutError);
		}

		if (NodeKind == TEXT("commutator"))
		{
			OutNode = MakeUnitNode(EHyperTwistAlgorithmNodeType::Commutator);
			if (!TryGetRequiredIntegerField(NodeObject, TEXT("amount"), OutNode.Commutator.Amount, OutError))
			{
				return false;
			}

			const TArray<TSharedPtr<FJsonValue>>* LeftValues = nullptr;
			const TArray<TSharedPtr<FJsonValue>>* RightValues = nullptr;
			if (!TryGetRequiredNodeArrayField(NodeObject, TEXT("left"), LeftValues, OutError)
				|| !TryGetRequiredNodeArrayField(NodeObject, TEXT("right"), RightValues, OutError))
			{
				return false;
			}

			return TryParseJsonNodeArray(*LeftValues, OutNode.Commutator.A, OutError)
				&& TryParseJsonNodeArray(*RightValues, OutNode.Commutator.B, OutError);
		}

		if (NodeKind == TEXT("conjugate"))
		{
			OutNode = MakeUnitNode(EHyperTwistAlgorithmNodeType::Conjugate);
			if (!TryGetRequiredIntegerField(NodeObject, TEXT("amount"), OutNode.Conjugate.Amount, OutError))
			{
				return false;
			}

			const TArray<TSharedPtr<FJsonValue>>* LeftValues = nullptr;
			const TArray<TSharedPtr<FJsonValue>>* RightValues = nullptr;
			if (!TryGetRequiredNodeArrayField(NodeObject, TEXT("left"), LeftValues, OutError)
				|| !TryGetRequiredNodeArrayField(NodeObject, TEXT("right"), RightValues, OutError))
			{
				return false;
			}

			return TryParseJsonNodeArray(*LeftValues, OutNode.Conjugate.A, OutError)
				&& TryParseJsonNodeArray(*RightValues, OutNode.Conjugate.B, OutError);
		}

		if (NodeKind == TEXT("pause"))
		{
			OutNode = MakeUnitNode(EHyperTwistAlgorithmNodeType::Pause);
			return true;
		}

		if (NodeKind == TEXT("newline"))
		{
			OutNode = MakeUnitNode(EHyperTwistAlgorithmNodeType::Newline);
			return true;
		}

		if (NodeKind == TEXT("comment"))
		{
			OutNode = MakeUnitNode(EHyperTwistAlgorithmNodeType::Comment);

			FString CommentKind;
			if (!TryGetRequiredStringField(NodeObject, TEXT("comment_kind"), CommentKind, OutError)
				|| !TryGetRequiredStringField(NodeObject, TEXT("text"), OutNode.Comment.CommentText, OutError))
			{
				return false;
			}

			if (CommentKind == TEXT("line"))
			{
				OutNode.Comment.CommentType = EHyperTwistAlgorithmCommentType::Line;
				return true;
			}
			if (CommentKind == TEXT("block"))
			{
				OutNode.Comment.CommentType = EHyperTwistAlgorithmCommentType::Block;
				return true;
			}

			OutError = FString::Printf(TEXT("Unsupported comment_kind '%s'"), *CommentKind);
			return false;
		}

		OutError = FString::Printf(TEXT("Unsupported node kind '%s'"), *NodeKind);
		return false;
	}

	bool TryParseJsonNodeArray(
		const TArray<TSharedPtr<FJsonValue>>& NodeValues,
		TArray<FHyperTwistAlgorithmNode>& OutNodes,
		FString& OutError)
	{
		OutNodes.Reset();
		OutNodes.Reserve(NodeValues.Num());

		for (int32 Index = 0; Index < NodeValues.Num(); ++Index)
		{
			TSharedPtr<FJsonObject> NodeObject;
			if (!TryReadJsonObject(
					NodeValues[Index],
					FString::Printf(TEXT("root[%d]"), Index),
					NodeObject,
					OutError))
			{
				return false;
			}

			FHyperTwistAlgorithmNode ParsedNode;
			if (!TryParseJsonNodeObject(NodeObject, ParsedNode, OutError))
			{
				OutError = FString::Printf(TEXT("root[%d]: %s"), Index, *OutError);
				return false;
			}

			OutNodes.Add(MoveTemp(ParsedNode));
		}

		return true;
	}
}

FHyperTwistAlgorithmParseResult UHyperTwistAlgorithmParser::ParseAlgorithm(const FString& AlgorithmText)
{
	using namespace HyperTwistAlgorithmParserInternal;

	FHyperTwistAlgorithmParseResult Result;
	FParserState State(AlgorithmText);
	TArray<FHyperTwistAlgorithmNode> ParsedNodes;

	if (TryParseNodeSequence(State, ParsedNodes, {}))
	{
		Result.bSuccess = true;
		Result.Sequence.Nodes = MoveTemp(ParsedNodes);
		return Result;
	}

	Result.bSuccess = false;
	Result.ErrorMessage = State.ErrorMessage;
	Result.ErrorPosition = State.Position;
	return Result;
}

FHyperTwistAlgorithmSequence UHyperTwistAlgorithmParser::ParseAlgorithmOrEmpty(const FString& AlgorithmText)
{
	const FHyperTwistAlgorithmParseResult Result = ParseAlgorithm(AlgorithmText);
	return Result.bSuccess ? Result.Sequence : FHyperTwistAlgorithmSequence();
}

bool UHyperTwistAlgorithmParser::TryParseFromJson(
	const FString& JsonText,
	FHyperTwistAlgorithmSequence& OutSequence,
	FString& OutError
)
{
	using namespace HyperTwistAlgorithmParserInternal;

	OutSequence = FHyperTwistAlgorithmSequence();
	OutError.Empty();

	TSharedPtr<FJsonObject> RootObject;
	const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonText);
	if (!FJsonSerializer::Deserialize(Reader, RootObject) || !RootObject.IsValid())
	{
		OutError = TEXT("Structured JSON payload could not be parsed.");
		return false;
	}

	FString SchemaName;
	if (!TryGetRequiredStringField(RootObject, TEXT("schema"), SchemaName, OutError))
	{
		return false;
	}

	if (SchemaName != StructuredJsonSchema)
	{
		OutError = FString::Printf(TEXT("Unsupported structured JSON schema '%s'"), *SchemaName);
		return false;
	}

	const TArray<TSharedPtr<FJsonValue>>* RootValues = nullptr;
	if (!TryGetRequiredNodeArrayField(RootObject, TEXT("root"), RootValues, OutError))
	{
		return false;
	}

	return TryParseJsonNodeArray(*RootValues, OutSequence.Nodes, OutError);
}
