// Clean-room implementation for HyperTwist algorithm-language subsystem
// Implemented from governance docs only (Phase 0R-D, Phase 1R, Phase 5R-A)
// Date: 2026-05-15

#include "HyperTwistAlgorithm/HyperTwistAlgorithmSerializer.h"

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

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

	void AppendSignedAmountSuffix(const int32 Amount, FString& OutText)
	{
		const int32 AbsoluteAmount = FMath::Abs(Amount);
		if (AbsoluteAmount != 1 || Amount == 0)
		{
			OutText.Append(FString::FromInt(AbsoluteAmount));
		}

		if (Amount < 0)
		{
			OutText.Append(TEXT("'"));
		}
	}

	void SerializeNodeList(const TArray<FHyperTwistAlgorithmNode>& Nodes, FString& OutText);

	TSharedPtr<FJsonObject> BuildNodeObject(const FHyperTwistAlgorithmNode& Node);

	TArray<TSharedPtr<FJsonValue>> BuildNodeArray(const TArray<FHyperTwistAlgorithmNode>& Nodes)
	{
		TArray<TSharedPtr<FJsonValue>> Values;
		Values.Reserve(Nodes.Num());
		for (const FHyperTwistAlgorithmNode& Node : Nodes)
		{
			Values.Add(MakeShared<FJsonValueObject>(BuildNodeObject(Node)));
		}

		return Values;
	}

	void SerializeBlockMove(const FHyperTwistAlgorithmBlockMove& Move, FString& OutText)
	{
		const EHyperTwistAlgorithmMoveType CanonicalMoveType = Move.GetCanonicalMoveType();
		if (CanonicalMoveType == EHyperTwistAlgorithmMoveType::InnerSlice)
		{
			OutText.Append(FString::FromInt(Move.GetCanonicalInnerLayer()));
		}
		else if (CanonicalMoveType == EHyperTwistAlgorithmMoveType::RangedSlice)
		{
			OutText.Append(FString::FromInt(Move.GetCanonicalInnerLayer()));
			OutText.Append(TEXT("-"));
			OutText.Append(FString::FromInt(Move.GetCanonicalOuterLayer()));
		}

		OutText.Append(Move.Family);
		if (Move.UsesWideSuffix())
		{
			OutText.Append(TEXT("w"));
		}

		AppendSignedAmountSuffix(Move.Amount, OutText);
	}

	void SerializeComment(const FHyperTwistAlgorithmComment& Comment, FString& OutText)
	{
		if (Comment.CommentType == EHyperTwistAlgorithmCommentType::Block)
		{
			if (Comment.CommentText.IsEmpty())
			{
				OutText.Append(TEXT("/**/"));
				return;
			}

			OutText.Append(TEXT("/* "));
			OutText.Append(Comment.CommentText);
			OutText.Append(TEXT(" */"));
			return;
		}

		OutText.Append(TEXT("//"));
		if (!Comment.CommentText.IsEmpty())
		{
			OutText.Append(TEXT(" "));
			OutText.Append(Comment.CommentText);
		}
	}

	void SerializeNode(const FHyperTwistAlgorithmNode& Node, FString& OutText)
	{
		switch (Node.NodeType)
		{
		case EHyperTwistAlgorithmNodeType::BlockMove:
			SerializeBlockMove(Node.BlockMove, OutText);
			break;

		case EHyperTwistAlgorithmNodeType::Group:
			OutText.Append(TEXT("("));
			SerializeNodeList(Node.Group.Inner, OutText);
			OutText.Append(TEXT(")"));
			if (Node.Group.Amount != 1)
			{
				AppendSignedAmountSuffix(Node.Group.Amount, OutText);
			}
			break;

		case EHyperTwistAlgorithmNodeType::Commutator:
			OutText.Append(TEXT("["));
			SerializeNodeList(Node.Commutator.A, OutText);
			OutText.Append(TEXT(", "));
			SerializeNodeList(Node.Commutator.B, OutText);
			OutText.Append(TEXT("]"));
			if (Node.Commutator.Amount != 1)
			{
				AppendSignedAmountSuffix(Node.Commutator.Amount, OutText);
			}
			break;

		case EHyperTwistAlgorithmNodeType::Conjugate:
			OutText.Append(TEXT("["));
			SerializeNodeList(Node.Conjugate.A, OutText);
			OutText.Append(TEXT(": "));
			SerializeNodeList(Node.Conjugate.B, OutText);
			OutText.Append(TEXT("]"));
			if (Node.Conjugate.Amount != 1)
			{
				AppendSignedAmountSuffix(Node.Conjugate.Amount, OutText);
			}
			break;

		case EHyperTwistAlgorithmNodeType::Pause:
			OutText.Append(TEXT("."));
			break;

		case EHyperTwistAlgorithmNodeType::Newline:
			OutText.Append(TEXT("\n"));
			break;

		case EHyperTwistAlgorithmNodeType::Comment:
			SerializeComment(Node.Comment, OutText);
			break;

		case EHyperTwistAlgorithmNodeType::Sequence:
		default:
			break;
		}
	}

	bool NeedsSeparator(
		const FHyperTwistAlgorithmNode& PreviousNode,
		const FHyperTwistAlgorithmNode& CurrentNode)
	{
		if (PreviousNode.NodeType == EHyperTwistAlgorithmNodeType::Newline
			|| CurrentNode.NodeType == EHyperTwistAlgorithmNodeType::Newline)
		{
			return false;
		}

		if (PreviousNode.NodeType == EHyperTwistAlgorithmNodeType::Pause
			&& CurrentNode.NodeType == EHyperTwistAlgorithmNodeType::Pause)
		{
			return false;
		}

		return true;
	}

	void SerializeNodeList(const TArray<FHyperTwistAlgorithmNode>& Nodes, FString& OutText)
	{
		for (int32 Index = 0; Index < Nodes.Num(); ++Index)
		{
			if (Index > 0 && NeedsSeparator(Nodes[Index - 1], Nodes[Index]))
			{
				OutText.Append(TEXT(" "));
			}

			SerializeNode(Nodes[Index], OutText);
		}
	}

	TSharedPtr<FJsonObject> BuildNodeObject(const FHyperTwistAlgorithmNode& Node)
	{
		TSharedPtr<FJsonObject> NodeObject = MakeShared<FJsonObject>();

		switch (Node.NodeType)
		{
		case EHyperTwistAlgorithmNodeType::BlockMove:
			{
				const EHyperTwistAlgorithmMoveType CanonicalMoveType = Node.BlockMove.GetCanonicalMoveType();
				NodeObject->SetStringField(TEXT("kind"), TEXT("move"));
				NodeObject->SetStringField(TEXT("family"), Node.BlockMove.Family);
				NodeObject->SetNumberField(TEXT("amount"), Node.BlockMove.Amount);
				NodeObject->SetBoolField(TEXT("wide"), Node.BlockMove.UsesWideSuffix());
				if (CanonicalMoveType == EHyperTwistAlgorithmMoveType::InnerSlice)
				{
					NodeObject->SetStringField(TEXT("layer_mode"), TEXT("index"));
					NodeObject->SetNumberField(TEXT("layer_index"), Node.BlockMove.GetCanonicalInnerLayer());
				}
				else if (CanonicalMoveType == EHyperTwistAlgorithmMoveType::RangedSlice)
				{
					NodeObject->SetStringField(TEXT("layer_mode"), TEXT("range"));
					NodeObject->SetNumberField(TEXT("layer_index"), Node.BlockMove.GetCanonicalInnerLayer());
					NodeObject->SetNumberField(TEXT("layer_end"), Node.BlockMove.GetCanonicalOuterLayer());
				}
				else
				{
					NodeObject->SetStringField(TEXT("layer_mode"), TEXT("plain"));
				}
			}
			break;

		case EHyperTwistAlgorithmNodeType::Group:
			NodeObject->SetStringField(TEXT("kind"), TEXT("group"));
			NodeObject->SetNumberField(TEXT("amount"), Node.Group.Amount);
			NodeObject->SetArrayField(TEXT("items"), BuildNodeArray(Node.Group.Inner));
			break;

		case EHyperTwistAlgorithmNodeType::Commutator:
			NodeObject->SetStringField(TEXT("kind"), TEXT("commutator"));
			NodeObject->SetNumberField(TEXT("amount"), Node.Commutator.Amount);
			NodeObject->SetArrayField(TEXT("left"), BuildNodeArray(Node.Commutator.A));
			NodeObject->SetArrayField(TEXT("right"), BuildNodeArray(Node.Commutator.B));
			break;

		case EHyperTwistAlgorithmNodeType::Conjugate:
			NodeObject->SetStringField(TEXT("kind"), TEXT("conjugate"));
			NodeObject->SetNumberField(TEXT("amount"), Node.Conjugate.Amount);
			NodeObject->SetArrayField(TEXT("left"), BuildNodeArray(Node.Conjugate.A));
			NodeObject->SetArrayField(TEXT("right"), BuildNodeArray(Node.Conjugate.B));
			break;

		case EHyperTwistAlgorithmNodeType::Pause:
			NodeObject->SetStringField(TEXT("kind"), TEXT("pause"));
			break;

		case EHyperTwistAlgorithmNodeType::Newline:
			NodeObject->SetStringField(TEXT("kind"), TEXT("newline"));
			break;

		case EHyperTwistAlgorithmNodeType::Comment:
			NodeObject->SetStringField(TEXT("kind"), TEXT("comment"));
			NodeObject->SetStringField(
				TEXT("comment_kind"),
				Node.Comment.CommentType == EHyperTwistAlgorithmCommentType::Block ? TEXT("block") : TEXT("line"));
			NodeObject->SetStringField(TEXT("text"), Node.Comment.CommentText);
			break;

		case EHyperTwistAlgorithmNodeType::Sequence:
		default:
			NodeObject->SetStringField(TEXT("kind"), TEXT("unsupported"));
			break;
		}

		return NodeObject;
	}
}

FString UHyperTwistAlgorithmSerializer::SerializeAlgorithm(const FHyperTwistAlgorithmSequence& Sequence)
{
	FString SerializedText;
	HyperTwistAlgorithmSerializerInternal::SerializeNodeList(Sequence.Nodes, SerializedText);
	return SerializedText;
}

bool UHyperTwistAlgorithmSerializer::TrySerializeToJson(
	const FHyperTwistAlgorithmSequence& Sequence,
	FString& OutJson
)
{
	OutJson.Empty();
	TSharedPtr<FJsonObject> RootObject = MakeShared<FJsonObject>();
	RootObject->SetStringField(TEXT("schema"), HyperTwistAlgorithmSerializerInternal::StructuredJsonSchema);
	RootObject->SetArrayField(
		TEXT("root"),
		HyperTwistAlgorithmSerializerInternal::BuildNodeArray(Sequence.Nodes));

	const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutJson);
	return FJsonSerializer::Serialize(RootObject.ToSharedRef(), Writer);
}
