#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "HyperTwistSimulation/HyperTwistClassicCubeTypes.h"
#include "HyperTwistClassicCubeActor.generated.h"

class UProceduralMeshComponent;

UCLASS()
class UNREALHYPERTWIST_API AHyperTwistClassicCubeActor : public AActor
{
	GENERATED_BODY()

public:
	AHyperTwistClassicCubeActor();

	/** A 3x3x3 cube has 26 visible cubies; the enclosed core is intentionally not rendered. */
	static constexpr int32 ExpectedRenderablePieceCount = 26;

	/** Size of each cubelet (one small cube) in unreal units. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Cube")
	float CubeletSize = 10.0f;

	/** Gap between cubelets in unreal units. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Cube")
	float Gap = 0.5f;

	/** If true, generate cube geometry in OnConstruction (visible in editor). */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Cube")
	bool bGenerateOnConstruction = true;

	/**
	 * Override materials for each outward face.
	 * Index order matches EHyperTwistClassicCubeFace: Up, Down, Front, Back, Left, Right.
	 * If null, the default engine material is used.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Cube")
	TArray<UMaterialInterface*> FaceMaterials;

	/** Material for internal (non-visible) faces. If null, the default engine material is used. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Cube")
	UMaterialInterface* InternalMaterial;

	/** Low emissive baseline that keeps every sticker readable in sparse training maps. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Cube|Presentation", meta = (ClampMin = "0.0"))
	float BaseGlowStrength = 0.65f;

	/** Emissive strength applied to the solver-hinted world-facing side. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Cube|Presentation", meta = (ClampMin = "0.0"))
	float HintGlowStrength = 7.5f;

	/** Duration of a quarter-turn animation in seconds. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Cube")
	float TurnDuration = 0.15f;

	/** Rotate one face of the cube. Queued if already animating. */
	UFUNCTION(BlueprintCallable, Category = "HyperTwist|Cube")
	void RotateFace(EHyperTwistClassicCubeFace Face, EHyperTwistRotationDirection Direction);

	/** True if a face rotation animation is in progress. */
	UFUNCTION(BlueprintPure, Category = "HyperTwist|Cube")
	bool IsAnimating() const;

	/** Reset the cube to solved state. */
	UFUNCTION(BlueprintCallable, Category = "HyperTwist|Cube")
	void ResetCube();

	/** True when every tracked piece is back in its canonical solved position and orientation. */
	UFUNCTION(BlueprintPure, Category = "HyperTwist|Cube")
	bool IsSolved() const;

	/**
	 * Process a click ray. If it hits a visible cube face, rotates that face.
	 * @param RayOrigin    World-space ray origin (e.g. camera location)
	 * @param RayDirection World-space ray direction (normalized)
	 * @param bCounterClockwise If true, rotate counter-clockwise; else clockwise
	 * @return True if a face was hit and rotation was queued
	 */
	UFUNCTION(BlueprintCallable, Category = "HyperTwist|Cube")
	bool ProcessClick(const FVector& RayOrigin, const FVector& RayDirection, bool bCounterClockwise = false);

	static bool TryResolveFaceFromImpactNormal(
		const FVector& ImpactNormal,
		EHyperTwistClassicCubeFace& OutFace
	);

	/** Generate a random scramble of the given length (WCA-style notation). */
	UFUNCTION(BlueprintCallable, Category = "HyperTwist|Cube")
	TArray<FString> GenerateScramble(int32 Length = 20);

	/** Apply a scramble from WCA-style move strings (e.g. ["R", "U'", "F2", ...]). */
	UFUNCTION(BlueprintCallable, Category = "HyperTwist|Cube")
	void ApplyScramble(const TArray<FString>& MoveStrings);

	/** Apply a scramble immediately without animation. Useful for replay reconstruction and headless validation. */
	UFUNCTION(BlueprintCallable, Category = "HyperTwist|Cube")
	void ApplyScrambleImmediately(const TArray<FString>& MoveStrings);

	/** Queue a move using standard notation (for example R, U', or F2). */
	UFUNCTION(BlueprintCallable, Category = "HyperTwist|Cube")
	bool TryQueueMoveNotation(const FString& MoveNotation, bool bGameplayMove = true);

	/** Queue a normalized move descriptor. Double turns expand into two quarter turns internally. */
	UFUNCTION(BlueprintCallable, Category = "HyperTwist|Cube")
	bool TryQueueMoveDescriptor(const FHyperTwistClassicCubeMoveDescriptor& MoveDescriptor);

	/** Apply a normalized move descriptor immediately without animation. */
	UFUNCTION(BlueprintCallable, Category = "HyperTwist|Cube")
	bool ExecuteMoveDescriptorImmediately(const FHyperTwistClassicCubeMoveDescriptor& MoveDescriptor);

	/** Total completed quarter turns, including scripted scramble turns. */
	UFUNCTION(BlueprintPure, Category = "HyperTwist|Cube")
	int32 GetTotalCompletedMoveCount() const;

	/** Total completed gameplay quarter turns triggered outside scramble playback. */
	UFUNCTION(BlueprintPure, Category = "HyperTwist|Cube")
	int32 GetGameplayCompletedMoveCount() const;

	/** Number of queued rotations still waiting to start. */
	UFUNCTION(BlueprintPure, Category = "HyperTwist|Cube")
	int32 GetQueuedRotationCount() const;

	/** The current active scramble as a single WCA-style notation string. */
	UFUNCTION(BlueprintPure, Category = "HyperTwist|Cube")
	FString GetCurrentScrambleNotation() const;

	/** Completed move history in quarter-turn resolution. */
	UFUNCTION(BlueprintPure, Category = "HyperTwist|Cube")
	TArray<FHyperTwistClassicCubeMoveDescriptor> GetCompletedMoveHistory() const;

	/** Current tracked piece positions for validation and debug surfaces. */
	UFUNCTION(BlueprintPure, Category = "HyperTwist|Cube")
	TArray<FHyperTwistClassicCubePieceData> GetPieceDataSnapshot() const;

	/** Current settled cube state in solver facelet order U/R/F/D/L/B. */
	UFUNCTION(BlueprintPure, Category = "HyperTwist|Cube")
	FString GetSolverFaceletString() const;

	/** Highlight the currently suggested world-facing side. */
	UFUNCTION(BlueprintCallable, Category = "HyperTwist|Cube")
	void SetHintedFace(EHyperTwistClassicCubeFace Face);

	/** Clear any active face hint highlight. */
	UFUNCTION(BlueprintCallable, Category = "HyperTwist|Cube")
	void ClearHintedFace();

	/** True when the cube is not animating and has no queued turns waiting to start. */
	UFUNCTION(BlueprintPure, Category = "HyperTwist|Cube")
	bool IsSettled() const;

	/** Number of generated, registered cubelet mesh components with geometry. */
	UFUNCTION(BlueprintPure, Category = "HyperTwist|Cube|Diagnostics")
	int32 GetRenderablePieceCount() const;

	/** True when cubie occupancy, sticker orientation, and live mesh transforms form one coherent 3x3x3 cube. */
	UFUNCTION(BlueprintPure, Category = "HyperTwist|Cube|Diagnostics")
	bool HasValidRenderableState() const;

	/** Empty for a coherent renderable cube; otherwise identifies the first violated presentation invariant. */
	UFUNCTION(BlueprintPure, Category = "HyperTwist|Cube|Diagnostics")
	FString GetRenderableStateValidationError() const;

	/** Rebuild transient procedural geometry when a cooked map did not preserve a complete presentation. */
	UFUNCTION(BlueprintCallable, Category = "HyperTwist|Cube|Diagnostics")
	bool EnsureRenderablePresentation();

	virtual void OnConstruction(const FTransform& Transform) override;

protected:
	virtual void BeginPlay() override;
	virtual void Tick(float DeltaTime) override;

private:
	struct FTrackedPiece
	{
		UProceduralMeshComponent* Mesh = nullptr;
		FVector GridPos = FVector::ZeroVector;
		FQuat Orientation = FQuat::Identity;
		TArray<EHyperTwistClassicCubeFace> IdentityFaces;
		UMaterialInstanceDynamic* DynamicFaceMaterials[6] = {
			nullptr,
			nullptr,
			nullptr,
			nullptr,
			nullptr,
			nullptr
		};
	};

	struct FActiveRotation
	{
		USceneComponent* Pivot = nullptr;
		TArray<int32> PieceIndices;
		FQuat PivotStartRot;
		FQuat PivotTargetRot;
		double StartTime = 0.0;
		float Duration = 0.15f;
		EHyperTwistClassicCubeFace Face = EHyperTwistClassicCubeFace::Up;
		EHyperTwistRotationDirection Direction = EHyperTwistRotationDirection::Clockwise;
		bool bGameplayMove = false;
	};

	struct FQueuedRotationRequest
	{
		EHyperTwistClassicCubeFace Face = EHyperTwistClassicCubeFace::Up;
		EHyperTwistRotationDirection Direction = EHyperTwistRotationDirection::Clockwise;
		bool bGameplayMove = false;
	};

	void GenerateCube();
	void ClearPieces();
	void CreatePiece(const FVector& GridPos, const TArray<EHyperTwistClassicCubeFace>& IdentityFaces);
	void CreateCubeletFace(UProceduralMeshComponent* Mesh, int32 SectionIndex,
		const FVector& Center, float HalfSize, EHyperTwistClassicCubeFace Face,
		UMaterialInterface* Material);

	void QueueRotation(
		EHyperTwistClassicCubeFace Face,
		EHyperTwistRotationDirection Direction,
		bool bGameplayMove
	);
	void SynchronizePieceMeshTransform(FTrackedPiece& Piece);
	void ApplyQuarterTurnImmediate(
		EHyperTwistClassicCubeFace Face,
		EHyperTwistRotationDirection Direction,
		bool bGameplayMove
	);
	void StartFaceRotation(EHyperTwistClassicCubeFace Face, EHyperTwistRotationDirection Direction);
	void FinalizeRotation();
	void ProcessRotationQueue();

	static FVector RotateGridPosition(const FVector& Pos, EHyperTwistClassicCubeFace Face, EHyperTwistRotationDirection Direction);
	static FQuat GetFaceRotationQuat(EHyperTwistClassicCubeFace Face, EHyperTwistRotationDirection Direction);
	static TArray<int32> GetFacePieceIndices(const TArray<FTrackedPiece>& Pieces, EHyperTwistClassicCubeFace Face);
	static FVector GetFaceCenter(EHyperTwistClassicCubeFace Face);
	static int32 GetFaceAxisIndex(EHyperTwistClassicCubeFace Face);
	static FVector GetExpectedGridPositionFromIdentity(const TArray<EHyperTwistClassicCubeFace>& IdentityFaces);
	static bool IsSolvedOrientation(const FQuat& Orientation);
	static FVector GetFaceNormal(EHyperTwistClassicCubeFace Face);
	static TCHAR GetSolverFaceLetter(EHyperTwistClassicCubeFace Face);
	static FString BuildQuarterTurnNotation(
		EHyperTwistClassicCubeFace Face,
		EHyperTwistRotationDirection Direction
	);
	static bool TryResolveStickerWorldFace(
		const FTrackedPiece& Piece,
		EHyperTwistClassicCubeFace IdentityFace,
		EHyperTwistClassicCubeFace& OutWorldFace
	);
	static int32 GetFaceletIndexForSticker(
		EHyperTwistClassicCubeFace WorldFace,
		const FVector& GridPosition
	);

	void UpdateHintMaterialState();
	float ResolveHintGlowStrength(
		const FTrackedPiece& Piece,
		EHyperTwistClassicCubeFace IdentityFace
	) const;

	int32 GridToIndex(const FVector& GridPos) const;
	FVector IndexToGrid(int32 Index) const;

	TArray<FTrackedPiece> TrackedPieces;
	TArray<FQueuedRotationRequest> RotationQueue;
	FActiveRotation ActiveRotation;
	TArray<FString> CurrentScrambleMoves;
	TArray<FHyperTwistClassicCubeMoveDescriptor> CompletedMoveHistory;
	int32 TotalCompletedMoveCount = 0;
	int32 GameplayCompletedMoveCount = 0;
	bool bIsAnimating = false;
	bool bHasHintedFace = false;
	EHyperTwistClassicCubeFace HintedFace = EHyperTwistClassicCubeFace::Up;
};
