#pragma once

#include "RuntimeJson.h"
#include "ShaderTypes.h"

#include <atomic>
#include <cstddef>
#include <mutex>
#include <string>

class RuntimeStore;
class RuntimeEventDispatcher;

enum class RuntimeCoordinatorCommittedStateMode
{
	Unchanged,
	UseCommittedStates,
	UseLiveSnapshots
};

enum class RuntimeCoordinatorRenderResetScope
{
	None,
	TemporalHistoryOnly,
	TemporalHistoryAndFeedback
};

enum class RuntimeCoordinatorTransientOscInvalidation
{
	None,
	Layer,
	All
};

enum class RuntimeCoordinatorOscCommitPersistence
{
	SessionOnly,
	Persistent
};

struct RuntimeCoordinatorResult
{
	bool accepted = false;
	bool runtimeStateBroadcastRequired = false;
	bool shaderBuildRequested = false;
	bool persistenceRequested = false;
	bool compileStatusChanged = false;
	bool compileStatusSucceeded = false;
	bool clearReloadRequest = false;
	RuntimeCoordinatorCommittedStateMode committedStateMode = RuntimeCoordinatorCommittedStateMode::Unchanged;
	RuntimeCoordinatorRenderResetScope renderResetScope = RuntimeCoordinatorRenderResetScope::None;
	RuntimeCoordinatorTransientOscInvalidation transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::None;
	std::string transientOscLayerKey;
	std::string compileStatusMessage;
	std::string errorMessage;
};

class RuntimeCoordinator
{
public:
	RuntimeCoordinator(RuntimeStore& runtimeStore, RuntimeEventDispatcher& runtimeEventDispatcher);

	RuntimeCoordinatorResult AddLayer(const std::string& shaderId);
	RuntimeCoordinatorResult RemoveLayer(const std::string& layerId);
	RuntimeCoordinatorResult MoveLayer(const std::string& layerId, int direction);
	RuntimeCoordinatorResult MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex);
	RuntimeCoordinatorResult SetLayerBypass(const std::string& layerId, bool bypassed);
	RuntimeCoordinatorResult SetLayerShader(const std::string& layerId, const std::string& shaderId);
	RuntimeCoordinatorResult UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue);
	RuntimeCoordinatorResult UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue);
	RuntimeCoordinatorResult CommitOscParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue);
	RuntimeCoordinatorResult ResetLayerParameters(const std::string& layerId);
	RuntimeCoordinatorResult SaveStackPreset(const std::string& presetName);
	RuntimeCoordinatorResult LoadStackPreset(const std::string& presetName);

	RuntimeCoordinatorResult RequestShaderReload(bool preserveFeedbackState = false);
	RuntimeCoordinatorResult PollRuntimeStoreChanges(bool& registryChanged);
	RuntimeCoordinatorResult HandleRuntimePollFailure(const std::string& error);
	RuntimeCoordinatorResult HandlePreparedShaderBuildFailure(const std::string& error);
	RuntimeCoordinatorResult HandlePreparedShaderBuildSuccess();
	RuntimeCoordinatorResult HandleRuntimeReloadRequest();
	void ApplyCommittedStateMode(RuntimeCoordinatorCommittedStateMode mode);
	bool UseCommittedLayerStates() const;
	bool PreserveFeedbackOnNextShaderBuild() const;

private:
	struct ResolvedParameterMutation
	{
		std::string layerId;
		std::string parameterId;
		ShaderParameterValue value;
		bool persistState = true;
	};

	bool BuildParameterMutationById(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue,
		bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
	bool BuildParameterMutationByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue,
		bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
	bool BuildParameterMutationFromSnapshot(const std::string& layerId, const ShaderParameterDefinition& definition,
		const ShaderParameterValue& currentValue, bool hasCurrentValue, const JsonValue& newValue,
		bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
	bool ValidateLayerExists(const std::string& layerId, std::string& error) const;
	bool ValidateShaderExists(const std::string& shaderId, std::string& error) const;
	bool ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
	bool ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
	bool ValidatePresetName(const std::string& presetName, std::string& error) const;
	RuntimeCoordinatorResult ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState, bool persistenceRequested);
	RuntimeCoordinatorResult BuildQueuedReloadResult(bool preserveFeedbackState);
	RuntimeCoordinatorResult BuildAcceptedNoReloadResult() const;
	void PublishFileChangeDetected(const std::string& reason, bool registryChanged, bool reloadRequested) const;
	void PublishManualReloadRequested(bool preserveFeedbackState, const std::string& reason) const;
	void PublishCoordinatorResult(const std::string& action, const RuntimeCoordinatorResult& result) const;
	void PublishCoordinatorFollowUpEvents(const std::string& action, const RuntimeCoordinatorResult& result) const;

	RuntimeStore& mRuntimeStore;
	RuntimeEventDispatcher& mRuntimeEventDispatcher;
	mutable std::mutex mMutex;
	bool mPreserveFeedbackOnNextShaderBuild = false;
	std::atomic<bool> mUseCommittedLayerStates{ false };
};
