#pragma once

#include "CoreMinimal.h"
#include "HyperTwistCoreTypes.generated.h"

UENUM(BlueprintType)
enum class EHyperTwistPuzzleFamily : uint8
{
	ClassicCube UMETA(DisplayName = "Classic Cube"),
	Hypercube UMETA(DisplayName = "Hypercube"),
	Tiling UMETA(DisplayName = "Tiling"),
	Other UMETA(DisplayName = "Other")
};

UENUM(BlueprintType)
enum class EHyperTwistStateEncodingKind : uint8
{
	PieceOrbit UMETA(DisplayName = "Piece Orbit"),
	Facelet UMETA(DisplayName = "Facelet"),
	CellComplex UMETA(DisplayName = "Cell Complex"),
	FamilySpecific UMETA(DisplayName = "Family Specific")
};

UENUM(BlueprintType)
enum class EHyperTwistStateSource : uint8
{
	Runtime UMETA(DisplayName = "Runtime"),
	Recognition UMETA(DisplayName = "Recognition"),
	SmartDevice UMETA(DisplayName = "Smart Device"),
	Import UMETA(DisplayName = "Import"),
	ManualEntry UMETA(DisplayName = "Manual Entry")
};

UENUM(BlueprintType)
enum class EHyperTwistTransformKind : uint8
{
	SingleMove UMETA(DisplayName = "Single Move"),
	Algorithm UMETA(DisplayName = "Algorithm"),
	Rotation UMETA(DisplayName = "Rotation"),
	Slice UMETA(DisplayName = "Slice"),
	Composite UMETA(DisplayName = "Composite")
};

UENUM(BlueprintType)
enum class EHyperTwistMelindaScramblePacketSource : uint8
{
	GeneratedState UMETA(DisplayName = "Generated State"),
	TransformSequence UMETA(DisplayName = "Transform Sequence")
};

UENUM(BlueprintType)
enum class EHyperTwistMelindaAxis : uint8
{
	X UMETA(DisplayName = "X"),
	Y UMETA(DisplayName = "Y"),
	Z UMETA(DisplayName = "Z"),
	W UMETA(DisplayName = "W")
};

UENUM(BlueprintType)
enum class EHyperTwistMelindaStickerColor : uint8
{
	Unknown UMETA(DisplayName = "Unknown"),
	XNegative UMETA(DisplayName = "X-"),
	XPositive UMETA(DisplayName = "X+"),
	YNegative UMETA(DisplayName = "Y-"),
	YPositive UMETA(DisplayName = "Y+"),
	ZNegative UMETA(DisplayName = "Z-"),
	ZPositive UMETA(DisplayName = "Z+"),
	WNegative UMETA(DisplayName = "W-"),
	WPositive UMETA(DisplayName = "W+")
};

UENUM(BlueprintType)
enum class EHyperTwistMelindaProjectionPanel : uint8
{
	Front UMETA(DisplayName = "Front"),
	Back UMETA(DisplayName = "Back")
};

USTRUCT(BlueprintType)
struct FHyperTwistPuzzleDefinitionRef
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString PuzzleId;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	EHyperTwistPuzzleFamily PuzzleFamily = EHyperTwistPuzzleFamily::ClassicCube;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	int32 Dimension = 3;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString DefinitionVersion = TEXT("2026.04");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString NotationProfile = TEXT("classic-wca");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<int32> SizeVector;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString Variant;

	bool IsStructurallyValid() const
	{
		return !PuzzleId.IsEmpty() && Dimension > 0 && !DefinitionVersion.IsEmpty() && !NotationProfile.IsEmpty();
	}
};

USTRUCT(BlueprintType)
struct FHyperTwistOrientationFrame
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString Reference = TEXT("canonical-default");
};

USTRUCT(BlueprintType)
struct FHyperTwistSerializedPayload
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString EncodingProfile;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString PayloadJson;

	bool IsStructurallyValid() const
	{
		return !EncodingProfile.IsEmpty();
	}
};

USTRUCT(BlueprintType)
struct FHyperTwistPuzzleState
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString SchemaVersion = TEXT("ht-puzzle-state/v1");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistPuzzleDefinitionRef Definition;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	EHyperTwistStateEncodingKind StateEncodingKind = EHyperTwistStateEncodingKind::PieceOrbit;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistSerializedPayload StateEncoding;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistOrientationFrame OrientationFrame;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	bool bIsSolved = false;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	EHyperTwistStateSource Source = EHyperTwistStateSource::Runtime;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString CapturedAtUtc;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	float SourceConfidence = -1.0f;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString SourceSessionId;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString Notes;

	bool IsStructurallyValid() const
	{
		return !SchemaVersion.IsEmpty() && Definition.IsStructurallyValid() && StateEncoding.IsStructurallyValid();
	}
};

USTRUCT(BlueprintType)
struct FHyperTwistTransformation
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistPuzzleDefinitionRef Definition;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	EHyperTwistTransformKind TransformKind = EHyperTwistTransformKind::SingleMove;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString Notation;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistSerializedPayload TransformEncoding;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	bool bInvertible = true;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString OriginalNotation;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FString> NormalizationWarnings;

	bool IsStructurallyValid() const
	{
		return Definition.IsStructurallyValid() && !Notation.IsEmpty() && TransformEncoding.IsStructurallyValid();
	}
};

USTRUCT(BlueprintType)
struct FHyperTwistStateSnapshot
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString SnapshotId;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistPuzzleState State;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString DerivedHash;

	bool IsStructurallyValid() const
	{
		return !SnapshotId.IsEmpty() && State.IsStructurallyValid() && !DerivedHash.IsEmpty();
	}
};

USTRUCT(BlueprintType)
struct FHyperTwistPieceSetState
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<int32> Permutation;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<int32> Orientation;
};

USTRUCT(BlueprintType)
struct FHyperTwistClassicPieceOrbitState
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString EncodingProfile = TEXT("classic-piece-orbit-v1");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistPieceSetState Corners;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistPieceSetState Edges;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistPieceSetState Centers;
};

USTRUCT(BlueprintType)
struct FHyperTwistClassicFaceletSnapshotState
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString EncodingProfile = TEXT("classic-facelet-snapshot-v1");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FString> U;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FString> R;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FString> F;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FString> D;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FString> L;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FString> B;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	bool bPreviewState = true;
};

USTRUCT(BlueprintType)
struct FHyperTwistHypercubePieceMapState
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString EncodingProfile = TEXT("hypercube-piece-map-v1");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FString> Corners;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FString> Edges;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FString> Faces;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FString> Cells;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString FrameProfile = TEXT("hypercube-canonical-v1");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString TopologyVersion = TEXT("2x2x2x2/v1");
};

USTRUCT(BlueprintType)
struct FHyperTwistMelinda2x2x2x2StateEncoding
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString EncodingProfile = TEXT("melinda-2x2x2x2-state-v1");

	// For each position in [0, 15], stores the piece currently occupying it.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<int32> PositionToPiece;

	// For each piece in [0, 15], stores an S4 orientation index in [0, 23].
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<int32> PieceOrientation;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString FrameProfile = TEXT("hypercube-canonical-v1");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString TopologyVersion = TEXT("2x2x2x2/v1");

	bool IsStructurallyValid() const
	{
		if (PositionToPiece.Num() != 16 || PieceOrientation.Num() != 16)
		{
			return false;
		}

		TArray<bool> SeenPieces;
		SeenPieces.Init(false, 16);

		for (const int32 PieceId : PositionToPiece)
		{
			if (PieceId < 0 || PieceId >= 16 || SeenPieces[PieceId])
			{
				return false;
			}

			SeenPieces[PieceId] = true;
		}

		for (const int32 OrientationIndex : PieceOrientation)
		{
			if (OrientationIndex < 0 || OrientationIndex >= 24)
			{
				return false;
			}
		}

		return !FrameProfile.IsEmpty() && !TopologyVersion.IsEmpty();
	}
};

USTRUCT(BlueprintType)
struct FHyperTwistClassicTransformEncoding
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString EncodingProfile = TEXT("classic-transform-v1");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FString> MoveSequence;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString CachedFamily = TEXT("classic-cube");
};

USTRUCT(BlueprintType)
struct FHyperTwistHypercubeTransformEncoding
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString EncodingProfile = TEXT("hypercube-transform-v1");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString AxisRef;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString LayerSelector;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	int32 TurnAmount = 1;
};

USTRUCT(BlueprintType)
struct FHyperTwistMelinda2x2x2x2TransformEncoding
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString EncodingProfile = TEXT("melinda-2x2x2x2-transform-v1");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString MoveId = TEXT("identity");

	// For each new position in [0, 15], stores the old position it pulls from.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<int32> PositionPullMap;

	// For each new position in [0, 15], stores an S4 orientation delta index.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<int32> OrientationDeltaPerNewPosition;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString FrameProfile = TEXT("hypercube-canonical-v1");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString TopologyVersion = TEXT("2x2x2x2/v1");

	bool IsStructurallyValid() const
	{
		if (MoveId.IsEmpty()
			|| PositionPullMap.Num() != 16
			|| OrientationDeltaPerNewPosition.Num() != 16
			|| FrameProfile.IsEmpty()
			|| TopologyVersion.IsEmpty())
		{
			return false;
		}

		TArray<bool> SeenPositions;
		SeenPositions.Init(false, 16);

		for (const int32 SourcePosition : PositionPullMap)
		{
			if (SourcePosition < 0 || SourcePosition >= 16 || SeenPositions[SourcePosition])
			{
				return false;
			}

			SeenPositions[SourcePosition] = true;
		}

		for (const int32 OrientationIndex : OrientationDeltaPerNewPosition)
		{
			if (OrientationIndex < 0 || OrientationIndex >= 24)
			{
				return false;
			}
		}

		return true;
	}
};

USTRUCT(BlueprintType)
struct FHyperTwistMelinda2x2x2x2ScramblePacket
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString PacketProfile = TEXT("melinda-2x2x2x2-scramble-packet-v1");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistPuzzleDefinitionRef Definition;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	EHyperTwistMelindaScramblePacketSource PacketSource = EHyperTwistMelindaScramblePacketSource::GeneratedState;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistPuzzleState TargetState;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FHyperTwistTransformation> TransformSequence;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistTransformation NetTransform;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	int32 RandomSeed = 0;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	bool bCarriesTargetState = false;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	bool bCarriesExactTransformSequence = false;

	bool IsStructurallyValid() const
	{
		if (PacketProfile.IsEmpty() || !Definition.IsStructurallyValid())
		{
			return false;
		}

		if (!bCarriesTargetState && !bCarriesExactTransformSequence)
		{
			return false;
		}

		if (bCarriesTargetState)
		{
			if (!TargetState.IsStructurallyValid()
				|| TargetState.Definition.PuzzleId != Definition.PuzzleId
				|| TargetState.Definition.PuzzleFamily != Definition.PuzzleFamily
				|| TargetState.Definition.Dimension != Definition.Dimension)
			{
				return false;
			}
		}

		if (bCarriesExactTransformSequence)
		{
			if (!NetTransform.IsStructurallyValid()
				|| NetTransform.Definition.PuzzleId != Definition.PuzzleId
				|| NetTransform.Definition.PuzzleFamily != Definition.PuzzleFamily
				|| NetTransform.Definition.Dimension != Definition.Dimension)
			{
				return false;
			}

			for (const FHyperTwistTransformation& Transformation : TransformSequence)
			{
				if (!Transformation.IsStructurallyValid()
					|| Transformation.Definition.PuzzleId != Definition.PuzzleId
					|| Transformation.Definition.PuzzleFamily != Definition.PuzzleFamily
					|| Transformation.Definition.Dimension != Definition.Dimension)
				{
					return false;
				}
			}
		}
		else if (TransformSequence.Num() > 0)
		{
			return false;
		}

		return true;
	}
};

USTRUCT(BlueprintType)
struct FHyperTwistMelindaFlatProjectionOptions
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	bool bIncludePieceLabels = false;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	bool bIncludeStickerPeeks = false;
};

USTRUCT(BlueprintType)
struct FHyperTwistMelindaStickerProjection
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	EHyperTwistMelindaAxis Axis = EHyperTwistMelindaAxis::X;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	bool bPositiveSide = false;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	EHyperTwistMelindaStickerColor Color = EHyperTwistMelindaStickerColor::Unknown;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	int32 SourcePieceAxisIndex = INDEX_NONE;

	bool IsStructurallyValid() const
	{
		return SourcePieceAxisIndex >= 0
			&& SourcePieceAxisIndex < 4
			&& Color != EHyperTwistMelindaStickerColor::Unknown;
	}
};

USTRUCT(BlueprintType)
struct FHyperTwistMelindaFlatTileProjection
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	EHyperTwistMelindaProjectionPanel Panel = EHyperTwistMelindaProjectionPanel::Front;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	int32 PositionIndex = INDEX_NONE;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	int32 PieceId = INDEX_NONE;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	int32 GridRow = 0;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	int32 GridColumn = 0;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString SlotLabel;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString PieceLabel;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	bool bPieceInSolvedPosition = false;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistMelindaStickerProjection PanelSticker;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FHyperTwistMelindaStickerProjection> PeekStickers;

	bool IsStructurallyValid() const
	{
		return PositionIndex >= 0
			&& PositionIndex < 16
			&& PieceId >= 0
			&& PieceId < 16
			&& GridRow >= 0
			&& GridRow < 2
			&& GridColumn >= 0
			&& GridColumn < 4
			&& !SlotLabel.IsEmpty()
			&& bPieceInSolvedPosition == (PieceId == PositionIndex)
			&& PanelSticker.IsStructurallyValid()
			&& PanelSticker.Axis == EHyperTwistMelindaAxis::W
			&& PanelSticker.bPositiveSide == (Panel == EHyperTwistMelindaProjectionPanel::Back);
	}
};

USTRUCT(BlueprintType)
struct FHyperTwistMelinda2x2x2x2FlatProjection
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FString ProjectionProfile = TEXT("melinda-2x2x2x2-flat-projection-v1");

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistPuzzleDefinitionRef Definition;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	FHyperTwistMelindaFlatProjectionOptions Options;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FHyperTwistMelindaFlatTileProjection> FrontTiles;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
	TArray<FHyperTwistMelindaFlatTileProjection> BackTiles;

	bool IsStructurallyValid() const
	{
		if (ProjectionProfile.IsEmpty()
			|| !Definition.IsStructurallyValid()
			|| FrontTiles.Num() != 8
			|| BackTiles.Num() != 8)
		{
			return false;
		}

		TArray<bool> SeenPositions;
		SeenPositions.Init(false, 16);

		auto ValidatePeekStickers = [](const TArray<FHyperTwistMelindaStickerProjection>& PeekStickers) -> bool
		{
			TArray<bool> SeenAxes;
			SeenAxes.Init(false, 4);

			for (const FHyperTwistMelindaStickerProjection& PeekSticker : PeekStickers)
			{
				if (!PeekSticker.IsStructurallyValid())
				{
					return false;
				}

				const int32 AxisIndex = static_cast<int32>(PeekSticker.Axis);
				if (AxisIndex < 0
					|| AxisIndex >= 4
					|| AxisIndex == static_cast<int32>(EHyperTwistMelindaAxis::W)
					|| SeenAxes[AxisIndex])
				{
					return false;
				}

				SeenAxes[AxisIndex] = true;
			}

			return PeekStickers.Num() == 3;
		};

		auto ValidateTiles =
			[this, &SeenPositions, &ValidatePeekStickers](
				const TArray<FHyperTwistMelindaFlatTileProjection>& Tiles,
				const EHyperTwistMelindaProjectionPanel ExpectedPanel
			) -> bool
		{
			for (const FHyperTwistMelindaFlatTileProjection& Tile : Tiles)
			{
				if (!Tile.IsStructurallyValid()
					|| Tile.Panel != ExpectedPanel
					|| SeenPositions[Tile.PositionIndex])
				{
					return false;
				}

				if (Options.bIncludePieceLabels != !Tile.PieceLabel.IsEmpty())
				{
					return false;
				}

				if (Options.bIncludeStickerPeeks)
				{
					if (!ValidatePeekStickers(Tile.PeekStickers))
					{
						return false;
					}
				}
				else if (Tile.PeekStickers.Num() != 0)
				{
					return false;
				}

				SeenPositions[Tile.PositionIndex] = true;
			}

			return true;
		};

		return ValidateTiles(FrontTiles, EHyperTwistMelindaProjectionPanel::Front)
			&& ValidateTiles(BackTiles, EHyperTwistMelindaProjectionPanel::Back);
	}
};
