// 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
// Bound 3 - validation profiles and typed validation failures

#include "HyperTwistAlgorithm/HyperTwistAlgorithmValidation.h"

namespace HyperTwistAlgorithmValidationInternal
{
	struct FResolvedValidationProfile
	{
		FString CanonicalName = TEXT("permissive");
		bool bRejectContainers = false;
		bool bClassicCubeLegality = false;
		bool bSupported = true;
	};

	FString NormalizeProfileKey(const FString& InProfile)
	{
		FString Normalized = InProfile.TrimStartAndEnd();
		Normalized.ToLowerInline();
		return Normalized;
	}

	FResolvedValidationProfile ResolveValidationProfile(const FString& InProfile)
	{
		const FString ProfileKey = NormalizeProfileKey(InProfile);
		FResolvedValidationProfile Result;

		if (ProfileKey.IsEmpty()
			|| ProfileKey == TEXT("permissive")
			|| ProfileKey == TEXT("unknown-family-ok"))
		{
			return Result;
		}

		if (ProfileKey == TEXT("flat-only"))
		{
			Result.CanonicalName = TEXT("flat-only");
			Result.bRejectContainers = true;
			return Result;
		}

		if (ProfileKey == TEXT("classic-wca")
			|| ProfileKey == TEXT("classic-cube"))
		{
			Result.CanonicalName = TEXT("classic-wca");
			Result.bClassicCubeLegality = true;
			return Result;
		}

		if (ProfileKey == TEXT("classic-wca-flat")
			|| ProfileKey == TEXT("classic-wca-flat-only")
			|| ProfileKey == TEXT("flat-only+classic-wca")
			|| ProfileKey == TEXT("classic-cube-flat"))
		{
			Result.CanonicalName = TEXT("classic-wca-flat");
			Result.bRejectContainers = true;
			Result.bClassicCubeLegality = true;
			return Result;
		}

		Result.CanonicalName = ProfileKey;
		Result.bSupported = false;
		return Result;
	}

	void AddIssue(
		FHyperTwistAlgorithmValidationResult& OutResult,
		const EHyperTwistAlgorithmValidationIssueCode IssueCode,
		const FString& NodePath,
		const FString& Message)
	{
		OutResult.bIsValid = false;

		FHyperTwistAlgorithmValidationIssue Issue;
		Issue.IssueCode = IssueCode;
		Issue.NodePath = NodePath;
		Issue.Message = Message;
		OutResult.Issues.Add(MoveTemp(Issue));
	}

	bool IsSingleCharacterInSet(const FString& Family, const TCHAR* AllowedCharacters)
	{
		return Family.Len() == 1 && FCString::Strchr(AllowedCharacters, Family[0]) != nullptr;
	}

	bool IsClassicFaceFamily(const FString& Family)
	{
		return IsSingleCharacterInSet(Family, TEXT("ULFRBD"));
	}

	bool IsClassicWideLowercaseFamily(const FString& Family)
	{
		return IsSingleCharacterInSet(Family, TEXT("ulfrbd"));
	}

	bool IsClassicRotationFamily(const FString& Family)
	{
		return IsSingleCharacterInSet(Family, TEXT("xyzXYZ"));
	}

	bool IsClassicSliceFamily(const FString& Family)
	{
		return IsSingleCharacterInSet(Family, TEXT("MESmes"));
	}

	bool IsKnownClassicFamily(const FHyperTwistAlgorithmBlockMove& Move)
	{
		return IsClassicFaceFamily(Move.Family)
			|| IsClassicWideLowercaseFamily(Move.Family)
			|| IsClassicRotationFamily(Move.Family)
			|| IsClassicSliceFamily(Move.Family);
	}

	FString NodeTypeToContainerLabel(const EHyperTwistAlgorithmNodeType NodeType)
	{
		switch (NodeType)
		{
		case EHyperTwistAlgorithmNodeType::Group:
			return TEXT("group");
		case EHyperTwistAlgorithmNodeType::Commutator:
			return TEXT("commutator");
		case EHyperTwistAlgorithmNodeType::Conjugate:
			return TEXT("conjugate");
		default:
			return TEXT("container");
		}
	}

	void ValidateBlockMove(
		const FHyperTwistAlgorithmBlockMove& Move,
		const FString& NodePath,
		const FResolvedValidationProfile& Profile,
		FHyperTwistAlgorithmValidationResult& OutResult)
	{
		if (!Profile.bClassicCubeLegality)
		{
			return;
		}

		if (!IsKnownClassicFamily(Move))
		{
			AddIssue(
				OutResult,
				EHyperTwistAlgorithmValidationIssueCode::UnknownMoveFamily,
				NodePath,
				FString::Printf(
					TEXT("Move family '%s' is not allowed by profile '%s'."),
					*Move.Family,
					*Profile.CanonicalName));
			return;
		}

		const bool bHasSingleLayerPrefix = Move.GetCanonicalMoveType() == EHyperTwistAlgorithmMoveType::InnerSlice;
		const bool bHasRangePrefix = Move.GetCanonicalMoveType() == EHyperTwistAlgorithmMoveType::RangedSlice;

		if (bHasSingleLayerPrefix && Move.GetCanonicalInnerLayer() <= 0)
		{
			AddIssue(
				OutResult,
				EHyperTwistAlgorithmValidationIssueCode::LayerIndexMustBePositive,
				NodePath,
				FString::Printf(
					TEXT("Move '%s' uses layer index %d, but layer indices must be positive."),
					*Move.Family,
					Move.GetCanonicalInnerLayer()));
		}

		if (bHasRangePrefix)
		{
			if (Move.GetCanonicalInnerLayer() <= 0 || Move.GetCanonicalOuterLayer() <= 0)
			{
				AddIssue(
					OutResult,
					EHyperTwistAlgorithmValidationIssueCode::LayerRangeMustBePositive,
					NodePath,
					FString::Printf(
						TEXT("Move '%s' uses range %d-%d, but both range endpoints must be positive."),
						*Move.Family,
						Move.GetCanonicalInnerLayer(),
						Move.GetCanonicalOuterLayer()));
			}

			if (Move.GetCanonicalInnerLayer() >= Move.GetCanonicalOuterLayer())
			{
				AddIssue(
					OutResult,
					EHyperTwistAlgorithmValidationIssueCode::LayerRangeMustAscend,
					NodePath,
					FString::Printf(
						TEXT("Move '%s' uses range %d-%d, but the range must ascend with start < end."),
						*Move.Family,
						Move.GetCanonicalInnerLayer(),
						Move.GetCanonicalOuterLayer()));
			}
		}

		if ((bHasSingleLayerPrefix || bHasRangePrefix)
			&& (IsClassicRotationFamily(Move.Family) || IsClassicSliceFamily(Move.Family)))
		{
			AddIssue(
				OutResult,
				EHyperTwistAlgorithmValidationIssueCode::LayerPrefixNotAllowedForFamily,
				NodePath,
				FString::Printf(
					TEXT("Move family '%s' does not allow layer indices or ranges in profile '%s'."),
					*Move.Family,
					*Profile.CanonicalName));
		}
	}

	void ValidateNodeList(
		const TArray<FHyperTwistAlgorithmNode>& Nodes,
		const FString& PathPrefix,
		const FResolvedValidationProfile& Profile,
		FHyperTwistAlgorithmValidationResult& OutResult);

	void ValidateNode(
		const FHyperTwistAlgorithmNode& Node,
		const FString& NodePath,
		const FResolvedValidationProfile& Profile,
		FHyperTwistAlgorithmValidationResult& OutResult)
	{
		switch (Node.NodeType)
		{
		case EHyperTwistAlgorithmNodeType::BlockMove:
			ValidateBlockMove(Node.BlockMove, NodePath, Profile, OutResult);
			break;

		case EHyperTwistAlgorithmNodeType::Group:
			if (Profile.bRejectContainers)
			{
				AddIssue(
					OutResult,
					EHyperTwistAlgorithmValidationIssueCode::ContainerNotAllowed,
					NodePath,
					FString::Printf(
						TEXT("Profile '%s' does not allow group containers."),
						*Profile.CanonicalName));
			}
			ValidateNodeList(Node.Group.Inner, NodePath + TEXT(".items"), Profile, OutResult);
			break;

		case EHyperTwistAlgorithmNodeType::Commutator:
			if (Profile.bRejectContainers)
			{
				AddIssue(
					OutResult,
					EHyperTwistAlgorithmValidationIssueCode::ContainerNotAllowed,
					NodePath,
					FString::Printf(
						TEXT("Profile '%s' does not allow commutator containers."),
						*Profile.CanonicalName));
			}
			ValidateNodeList(Node.Commutator.A, NodePath + TEXT(".left"), Profile, OutResult);
			ValidateNodeList(Node.Commutator.B, NodePath + TEXT(".right"), Profile, OutResult);
			break;

		case EHyperTwistAlgorithmNodeType::Conjugate:
			if (Profile.bRejectContainers)
			{
				AddIssue(
					OutResult,
					EHyperTwistAlgorithmValidationIssueCode::ContainerNotAllowed,
					NodePath,
					FString::Printf(
						TEXT("Profile '%s' does not allow conjugate containers."),
						*Profile.CanonicalName));
			}
			ValidateNodeList(Node.Conjugate.A, NodePath + TEXT(".left"), Profile, OutResult);
			ValidateNodeList(Node.Conjugate.B, NodePath + TEXT(".right"), Profile, OutResult);
			break;

		case EHyperTwistAlgorithmNodeType::Pause:
		case EHyperTwistAlgorithmNodeType::Newline:
		case EHyperTwistAlgorithmNodeType::Comment:
		case EHyperTwistAlgorithmNodeType::Sequence:
		default:
			break;
		}
	}

	void ValidateNodeList(
		const TArray<FHyperTwistAlgorithmNode>& Nodes,
		const FString& PathPrefix,
		const FResolvedValidationProfile& Profile,
		FHyperTwistAlgorithmValidationResult& OutResult)
	{
		for (int32 Index = 0; Index < Nodes.Num(); ++Index)
		{
			const FString NodePath = FString::Printf(TEXT("%s[%d]"), *PathPrefix, Index);
			ValidateNode(Nodes[Index], NodePath, Profile, OutResult);
		}
	}
}

FHyperTwistAlgorithmValidationResult UHyperTwistAlgorithmValidationLibrary::ValidateSequenceForProfile(
	const FHyperTwistAlgorithmSequence& Sequence,
	const FString& NotationProfile)
{
	using namespace HyperTwistAlgorithmValidationInternal;

	FHyperTwistAlgorithmValidationResult Result;
	const FResolvedValidationProfile Profile = ResolveValidationProfile(NotationProfile);
	Result.ProfileName = Profile.CanonicalName;

	if (!Profile.bSupported)
	{
		AddIssue(
			Result,
			EHyperTwistAlgorithmValidationIssueCode::UnsupportedProfile,
			TEXT("profile"),
			FString::Printf(TEXT("Validation profile '%s' is not supported."), *NotationProfile));
		return Result;
	}

	ValidateNodeList(Sequence.Nodes, TEXT("root"), Profile, Result);
	return Result;
}

FHyperTwistAlgorithmValidationResult UHyperTwistAlgorithmValidationLibrary::ValidateSequenceFlatOnly(
	const FHyperTwistAlgorithmSequence& Sequence)
{
	return ValidateSequenceForProfile(Sequence, TEXT("flat-only"));
}

FHyperTwistAlgorithmValidationResult UHyperTwistAlgorithmValidationLibrary::ValidateSequenceClassicWca(
	const FHyperTwistAlgorithmSequence& Sequence)
{
	return ValidateSequenceForProfile(Sequence, TEXT("classic-wca"));
}
