// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
#include "HyperTwistSimulation/HyperTwistPuzzleViewerComponent.h"
#include "JsonObjectConverter.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistTwistyBound4VisualizationTestInternal
{
	template <typename TStruct>
	FString SerializeStructToJson(const TStruct& Value)
	{
		FString Json;
		FJsonObjectConverter::UStructToJsonObjectString(TStruct::StaticStruct(), &Value, Json, 0, 0);
		return Json;
	}

	FHyperTwistReplayEvent MakeReplayEvent(
		const FString& EventId,
		const int32 Sequence,
		const int32 TimeMs,
		const EHyperTwistReplayEventType EventType,
		const FString& PayloadJson
	)
	{
		FHyperTwistReplayEvent Event;
		Event.EventId = EventId;
		Event.Sequence = Sequence;
		Event.TimeMs = TimeMs;
		Event.EventType = EventType;
		Event.PayloadJson = PayloadJson;
		return Event;
	}

	FHyperTwistReplayPacket MakeReplayPacket()
	{
		const FHyperTwistPuzzleState SampleState = UHyperTwistContractLibrary::MakeSampleClassicPuzzleState();

		FHyperTwistStateSnapshot Snapshot;
		Snapshot.SnapshotId = TEXT("snapshot-bound4");
		Snapshot.State = SampleState;
		Snapshot.DerivedHash = TEXT("snapshot-bound4-hash");

		FHyperTwistReplayMovePayload FirstMovePayload;
		FirstMovePayload.Notation = TEXT("R");
		FirstMovePayload.TransformationRef = TEXT("classic/r");
		FirstMovePayload.Source = TEXT("unit-test");

		FHyperTwistReplayMovePayload SecondMovePayload;
		SecondMovePayload.Notation = TEXT("U");
		SecondMovePayload.TransformationRef = TEXT("classic/u");
		SecondMovePayload.Source = TEXT("unit-test");

		FHyperTwistReplayMovePayload ThirdMovePayload;
		ThirdMovePayload.Notation = TEXT("F'");
		ThirdMovePayload.TransformationRef = TEXT("classic/f-prime");
		ThirdMovePayload.Source = TEXT("unit-test");

		FHyperTwistReplayPacket ReplayPacket;
		ReplayPacket.ReplayId = TEXT("replay-bound4-001");
		ReplayPacket.SessionId = TEXT("session-bound4-001");
		ReplayPacket.PuzzleDefinition = SampleState.Definition;
		ReplayPacket.CaptureMode = EHyperTwistCaptureMode::Imported;
		ReplayPacket.Events = {
			MakeReplayEvent(
				TEXT("evt-001"),
				1,
				0,
				EHyperTwistReplayEventType::StateSnapshot,
				SerializeStructToJson(Snapshot)
			),
			MakeReplayEvent(
				TEXT("evt-002"),
				2,
				0,
				EHyperTwistReplayEventType::TimerStart,
				TEXT("")
			),
			MakeReplayEvent(
				TEXT("evt-003"),
				3,
				200,
				EHyperTwistReplayEventType::Move,
				SerializeStructToJson(FirstMovePayload)
			),
			MakeReplayEvent(
				TEXT("evt-004"),
				4,
				400,
				EHyperTwistReplayEventType::Move,
				SerializeStructToJson(SecondMovePayload)
			),
			MakeReplayEvent(
				TEXT("evt-005"),
				5,
				700,
				EHyperTwistReplayEventType::Move,
				SerializeStructToJson(ThirdMovePayload)
			),
			MakeReplayEvent(
				TEXT("evt-006"),
				6,
				1000,
				EHyperTwistReplayEventType::SolveEnd,
				TEXT("")
			)
		};

		return ReplayPacket;
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistTwistyBound4VisualizationShellTest,
	"HyperTwist.CleanRoom.TwistyJs.Bound4.VisualizationShell",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistTwistyBound4VisualizationShellTest::RunTest(const FString& Parameters)
{
	UHyperTwistPuzzleViewerComponent* Viewer = NewObject<UHyperTwistPuzzleViewerComponent>();
	TestNotNull(TEXT("Viewer component instance must be created."), Viewer);
	TestTrue(
		TEXT("Replay packet load must succeed for the Bound 4 visualization-shell test."),
		Viewer->LoadReplayPacket(HyperTwistTwistyBound4VisualizationTestInternal::MakeReplayPacket())
	);

	FHyperTwistViewerVisualizationShellRequest Request;
	Request.HostBootstrapRequest.HostInstanceId = TEXT("visual-host-01");
	Request.HostBootstrapRequest.BindingMode = EHyperTwistViewerEmbeddedHostBindingMode::ScissoredView;
	Request.HostBootstrapRequest.bBindTimelineSurface = true;
	Request.HostBootstrapRequest.bBindScrubberSurface = true;
	Request.HostBootstrapRequest.bBindStatusSurface = true;
	Request.HostBootstrapRequest.bAllowPointerInput = true;
	Request.VisualizationPreference = EHyperTwistViewerVisualizationPreference::Prefer3D;
	Request.InitialPresentationMode = EHyperTwistViewerPresentationMode::Expanded;
	Request.bAllow2DFallback = true;
	Request.bAllowExpandedView = true;
	Request.bAllowFullscreen = true;

	FHyperTwistViewerVisualizationShellState VisualizationState;
	TestTrue(
		TEXT("The viewer must prepare a bounded visualization shell."),
		Viewer->TryBuildVisualizationShell(Request, VisualizationState)
	);
	TestTrue(TEXT("The visualization shell state must be structurally valid."), VisualizationState.IsStructurallyValid());
	TestEqual(TEXT("The visualization shell should preserve the bootstrap id."), VisualizationState.BootstrapId, Viewer->GetHostBootstrapState().BootstrapId);
	TestEqual(TEXT("The visualization shell should preserve the scene context id."), VisualizationState.SceneContextId, Viewer->GetSceneContext().SceneContextId);
	TestEqual(TEXT("The visualization shell should preserve the classic-cubing viewer adapter contract."), VisualizationState.ViewerAdapterContractId, FString(TEXT("classic-cubing-viewer-adapter-stack")));
	TestEqual(TEXT("The visualization shell should reference the browser viewer embed contract."), VisualizationState.BrowserEmbedContractId, FString(TEXT("browser-viewer-embed")));
	TestEqual(TEXT("The visualization shell should reference the browser spatial scene contract."), VisualizationState.SpatialSceneContractId, FString(TEXT("browser-spatial-scene-stack")));
	TestEqual(TEXT("The visualization shell should reference the browser spatial UI contract."), VisualizationState.SpatialUiContractId, FString(TEXT("browser-spatial-ui-stack")));
	TestEqual(TEXT("The visualization shell should reference the browser effect boundary."), VisualizationState.SpatialEffectBoundaryId, FString(TEXT("browser-spatial-effect-boundary")));
	TestEqual(TEXT("The visualization shell should reference the browser state-motion contract."), VisualizationState.SpatialStateMotionContractId, FString(TEXT("browser-spatial-state-motion-stack")));
	TestEqual(TEXT("The preferred visualization mode should stay 3D."), VisualizationState.PreferredMode, EHyperTwistViewerVisualizationMode::ThreeD);
	TestEqual(TEXT("The active visualization mode should stay 3D when the support stack is available."), VisualizationState.ActiveMode, EHyperTwistViewerVisualizationMode::ThreeD);
	TestFalse(TEXT("The 3D shell should not require a fallback in the scissored-view path."), VisualizationState.bUsingFallback);
	TestTrue(TEXT("The 3D shell should mark spatial rendering active."), VisualizationState.bUsesSpatialRendering);
	TestEqual(TEXT("The requested expanded presentation mode should be preserved."), VisualizationState.PresentationMode, EHyperTwistViewerPresentationMode::Expanded);
	TestTrue(TEXT("The visualization shell should support fullscreen when requested."), VisualizationState.bSupportsFullscreen);
	TestTrue(TEXT("The visualization shell should support expanded mode when requested."), VisualizationState.bSupportsExpandedView);
	TestTrue(TEXT("The visualization shell should surface embeddable browser-viewer behavior tags."), VisualizationState.ViewerBehaviorTags.Contains(TEXT("embeddable-viewer")));
	TestTrue(TEXT("The visualization shell should surface browser-viewer annotation tags."), VisualizationState.ViewerAnnotationSurfaceTags.Contains(TEXT("focus-annotation")));
	TestTrue(TEXT("The visualization shell should surface world-anchored UI tags."), VisualizationState.SpatialUiStateSurfaceTags.Contains(TEXT("HTML/spatial overlay bridge")));
	TestTrue(TEXT("The visualization shell should surface browser scene event tags."), VisualizationState.SpatialSceneEventSurfaceTags.Contains(TEXT("scissored view embedding")));
	TestTrue(TEXT("The visualization shell should surface effect-family tags."), VisualizationState.SpatialEffectFamilies.Contains(TEXT("tone mapping and highlight effects")));
	TestTrue(TEXT("The visualization shell should surface motion-kind tags."), VisualizationState.MotionKinds.Contains(TEXT("reduced-motion support")));
	TestEqual(TEXT("The stored visualization shell state should match the returned visualization id."), Viewer->GetVisualizationShellState().VisualizationId, VisualizationState.VisualizationId);

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistTwistyBound4FallbackPolicyTest,
	"HyperTwist.CleanRoom.TwistyJs.Bound4.FallbackPolicy",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistTwistyBound4FallbackPolicyTest::RunTest(const FString& Parameters)
{
	UHyperTwistPuzzleViewerComponent* Viewer = NewObject<UHyperTwistPuzzleViewerComponent>();
	TestNotNull(TEXT("Viewer component instance must be created."), Viewer);
	TestTrue(
		TEXT("Replay packet load must succeed for the Bound 4 fallback-policy test."),
		Viewer->LoadReplayPacket(HyperTwistTwistyBound4VisualizationTestInternal::MakeReplayPacket())
	);

	FHyperTwistViewerVisualizationShellRequest Request;
	Request.HostBootstrapRequest.HostInstanceId = TEXT("visual-host-02");
	Request.HostBootstrapRequest.BindingMode = EHyperTwistViewerEmbeddedHostBindingMode::InlineCanvas;
	Request.HostBootstrapRequest.bBindTimelineSurface = true;
	Request.HostBootstrapRequest.bBindScrubberSurface = true;
	Request.HostBootstrapRequest.bBindStatusSurface = true;
	Request.HostBootstrapRequest.bAllowPointerInput = true;
	Request.VisualizationPreference = EHyperTwistViewerVisualizationPreference::Prefer3D;
	Request.InitialPresentationMode = EHyperTwistViewerPresentationMode::Inline;
	Request.bAllow2DFallback = true;
	Request.bAllowExpandedView = true;
	Request.bAllowFullscreen = true;

	FHyperTwistViewerVisualizationShellState VisualizationState;
	TestTrue(
		TEXT("The viewer must still build a bounded visualization shell when 3D falls back to 2D."),
		Viewer->TryBuildVisualizationShell(Request, VisualizationState)
	);
	TestTrue(TEXT("The fallback visualization shell state must be structurally valid."), VisualizationState.IsStructurallyValid());
	TestEqual(TEXT("The preferred visualization mode should remain 3D."), VisualizationState.PreferredMode, EHyperTwistViewerVisualizationMode::ThreeD);
	TestEqual(TEXT("Inline-canvas host binding should fall back to the 2D shell."), VisualizationState.ActiveMode, EHyperTwistViewerVisualizationMode::TwoD);
	TestTrue(TEXT("The 2D shell should report that it is using the shared fallback policy."), VisualizationState.bUsingFallback);
	TestEqual(
		TEXT("The fallback reason should identify the host-binding constraint."),
		VisualizationState.FallbackReason,
		EHyperTwistViewerVisualizationFallbackReason::HostBindingDoesNotSupport3D
	);
	TestFalse(TEXT("The fallback 2D shell should not mark spatial rendering active."), VisualizationState.bUsesSpatialRendering);
	TestEqual(TEXT("The initial inline presentation mode should be preserved."), VisualizationState.PresentationMode, EHyperTwistViewerPresentationMode::Inline);

	FHyperTwistViewerVisualizationShellState FullscreenState;
	TestTrue(
		TEXT("Fullscreen should remain a visualization-local concern even on the 2D fallback shell."),
		Viewer->TrySetVisualizationPresentationMode(EHyperTwistViewerPresentationMode::Fullscreen, FullscreenState)
	);
	TestEqual(TEXT("The presentation mode should move to fullscreen."), FullscreenState.PresentationMode, EHyperTwistViewerPresentationMode::Fullscreen);
	TestEqual(TEXT("Fullscreen should preserve the active 2D fallback mode."), FullscreenState.ActiveMode, EHyperTwistViewerVisualizationMode::TwoD);

	FHyperTwistViewerVisualizationShellState ExpandedState;
	TestTrue(
		TEXT("Expanded view should also remain a visualization-local concern."),
		Viewer->TrySetVisualizationPresentationMode(EHyperTwistViewerPresentationMode::Expanded, ExpandedState)
	);
	TestEqual(TEXT("The presentation mode should move to expanded."), ExpandedState.PresentationMode, EHyperTwistViewerPresentationMode::Expanded);
	TestEqual(TEXT("Expanded view should preserve the active 2D fallback mode."), ExpandedState.ActiveMode, EHyperTwistViewerVisualizationMode::TwoD);
	TestEqual(TEXT("The stored visualization shell state should now reflect the expanded mode."), Viewer->GetVisualizationShellState().PresentationMode, EHyperTwistViewerPresentationMode::Expanded);

	return true;
}

#endif // WITH_AUTOMATION_TESTS
