#pragma once

#include "RuntimeEventType.h"

#include <cstddef>
#include <cstdint>
#include "PersistenceRequest.h"

#include <string>

enum class RuntimeEventSeverity
{
	Debug,
	Info,
	Warning,
	Error
};

enum class RuntimeEventRenderResetScope
{
	None,
	TemporalHistoryOnly,
	TemporalHistoryAndFeedback
};

enum class RuntimeEventShaderBuildPhase
{
	Requested,
	Prepared,
	Applied,
	Failed
};

struct OscValueReceivedEvent
{
	std::string routeKey;
	std::string layerKey;
	std::string parameterKey;
	std::string valueJson;
	uint64_t generation = 0;
};

struct OscValueCoalescedEvent
{
	std::string routeKey;
	std::size_t coalescedCount = 0;
	uint64_t latestGeneration = 0;
};

struct OscCommitRequestedEvent
{
	std::string routeKey;
	std::string layerKey;
	std::string parameterKey;
	std::string valueJson;
	uint64_t generation = 0;
};

struct HttpControlMutationRequestedEvent
{
	std::string method;
	std::string path;
	std::string bodyJson;
};

struct WebSocketClientConnectedEvent
{
	std::string clientId;
	std::size_t connectedClientCount = 0;
};

struct RuntimeStateBroadcastRequestedEvent
{
	std::string reason;
	bool coalescable = true;
};

struct FileChangeDetectedEvent
{
	std::string path;
	bool shaderPackageCandidate = false;
	bool runtimeConfigCandidate = false;
	bool presetCandidate = false;
};

struct ManualReloadRequestedEvent
{
	bool preserveFeedbackState = false;
	std::string reason;
};

struct RuntimeMutationEvent
{
	std::string action;
	bool accepted = false;
	bool runtimeStateChanged = false;
	bool runtimeStateBroadcastRequired = false;
	bool shaderBuildRequested = false;
	bool persistenceRequested = false;
	bool clearTransientOscState = false;
	RuntimeEventRenderResetScope renderResetScope = RuntimeEventRenderResetScope::None;
	std::string errorMessage;
};

struct RuntimeStateChangedEvent
{
	std::string reason;
	bool renderVisible = false;
	bool persistenceRequested = false;
};

struct RuntimePersistenceRequestedEvent
{
	PersistenceRequest request;
};

struct RuntimeReloadRequestedEvent
{
	bool preserveFeedbackState = false;
	std::string reason;
};

struct ShaderPackagesChangedEvent
{
	bool registryChanged = false;
	std::size_t packageCount = 0;
	std::string reason;
};

struct RenderSnapshotPublishRequestedEvent
{
	unsigned inputWidth = 0;
	unsigned inputHeight = 0;
	unsigned outputWidth = 0;
	unsigned outputHeight = 0;
	std::string reason;
};

struct RuntimeStatePresentationChangedEvent
{
	std::string reason;
};

struct ShaderBuildEvent
{
	RuntimeEventShaderBuildPhase phase = RuntimeEventShaderBuildPhase::Requested;
	uint64_t generation = 0;
	unsigned inputWidth = 0;
	unsigned inputHeight = 0;
	bool preserveFeedbackState = false;
	bool succeeded = false;
	std::string message;
};

struct CompileStatusChangedEvent
{
	bool succeeded = false;
	std::string message;
};

struct RenderSnapshotPublishedEvent
{
	uint64_t snapshotVersion = 0;
	uint64_t structureVersion = 0;
	uint64_t parameterVersion = 0;
	uint64_t packageVersion = 0;
	unsigned outputWidth = 0;
	unsigned outputHeight = 0;
	std::size_t layerCount = 0;
};

struct RenderResetEvent
{
	RuntimeEventRenderResetScope scope = RuntimeEventRenderResetScope::None;
	bool applied = false;
	std::string reason;
};

struct OscOverlayEvent
{
	std::string routeKey;
	std::string layerKey;
	std::string parameterKey;
	uint64_t generation = 0;
	bool settled = false;
};

struct FrameRenderedEvent
{
	uint64_t frameIndex = 0;
	double renderMilliseconds = 0.0;
};

struct PreviewFrameAvailableEvent
{
	uint64_t frameIndex = 0;
	unsigned width = 0;
	unsigned height = 0;
};

struct InputSignalChangedEvent
{
	bool hasSignal = false;
	unsigned width = 0;
	unsigned height = 0;
	std::string modeName;
};

struct InputFrameArrivedEvent
{
	uint64_t frameIndex = 0;
	unsigned width = 0;
	unsigned height = 0;
	long rowBytes = 0;
	std::string pixelFormat;
	bool hasNoInputSource = false;
};

struct OutputFrameScheduledEvent
{
	uint64_t frameIndex = 0;
	int64_t streamTime = 0;
	int64_t duration = 0;
	int64_t timeScale = 0;
};

struct OutputFrameCompletedEvent
{
	uint64_t frameIndex = 0;
	std::string result;
};

struct BackendStateChangedEvent
{
	std::string backendName;
	std::string state;
	std::string message;
};

struct SubsystemWarningEvent
{
	std::string subsystem;
	std::string warningKey;
	RuntimeEventSeverity severity = RuntimeEventSeverity::Warning;
	std::string message;
	bool cleared = false;
};

struct SubsystemRecoveredEvent
{
	std::string subsystem;
	std::string recoveryKey;
	std::string message;
};

struct TimingSampleRecordedEvent
{
	std::string subsystem;
	std::string metric;
	double value = 0.0;
	std::string unit;
};

struct QueueDepthChangedEvent
{
	std::string queueName;
	std::size_t depth = 0;
	std::size_t capacity = 0;
	std::size_t droppedCount = 0;
	std::size_t coalescedCount = 0;
};

constexpr RuntimeEventType RuntimeEventPayloadType(const OscValueReceivedEvent&)
{
	return RuntimeEventType::OscValueReceived;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const OscValueCoalescedEvent&)
{
	return RuntimeEventType::OscValueCoalesced;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const OscCommitRequestedEvent&)
{
	return RuntimeEventType::OscCommitRequested;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const HttpControlMutationRequestedEvent&)
{
	return RuntimeEventType::HttpControlMutationRequested;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const WebSocketClientConnectedEvent&)
{
	return RuntimeEventType::WebSocketClientConnected;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStateBroadcastRequestedEvent&)
{
	return RuntimeEventType::RuntimeStateBroadcastRequested;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const FileChangeDetectedEvent&)
{
	return RuntimeEventType::FileChangeDetected;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const ManualReloadRequestedEvent&)
{
	return RuntimeEventType::ManualReloadRequested;
}

inline RuntimeEventType RuntimeEventPayloadType(const RuntimeMutationEvent& event)
{
	return event.accepted ? RuntimeEventType::RuntimeMutationAccepted : RuntimeEventType::RuntimeMutationRejected;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStateChangedEvent&)
{
	return RuntimeEventType::RuntimeStateChanged;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimePersistenceRequestedEvent&)
{
	return RuntimeEventType::RuntimePersistenceRequested;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeReloadRequestedEvent&)
{
	return RuntimeEventType::RuntimeReloadRequested;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const ShaderPackagesChangedEvent&)
{
	return RuntimeEventType::ShaderPackagesChanged;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const RenderSnapshotPublishRequestedEvent&)
{
	return RuntimeEventType::RenderSnapshotPublishRequested;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const RuntimeStatePresentationChangedEvent&)
{
	return RuntimeEventType::RuntimeStatePresentationChanged;
}

inline RuntimeEventType RuntimeEventPayloadType(const ShaderBuildEvent& event)
{
	switch (event.phase)
	{
	case RuntimeEventShaderBuildPhase::Requested:
		return RuntimeEventType::ShaderBuildRequested;
	case RuntimeEventShaderBuildPhase::Prepared:
		return RuntimeEventType::ShaderBuildPrepared;
	case RuntimeEventShaderBuildPhase::Applied:
		return RuntimeEventType::ShaderBuildApplied;
	case RuntimeEventShaderBuildPhase::Failed:
		return RuntimeEventType::ShaderBuildFailed;
	}

	return RuntimeEventType::ShaderBuildRequested;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const CompileStatusChangedEvent&)
{
	return RuntimeEventType::CompileStatusChanged;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const RenderSnapshotPublishedEvent&)
{
	return RuntimeEventType::RenderSnapshotPublished;
}

inline RuntimeEventType RuntimeEventPayloadType(const RenderResetEvent& event)
{
	return event.applied ? RuntimeEventType::RenderResetApplied : RuntimeEventType::RenderResetRequested;
}

inline RuntimeEventType RuntimeEventPayloadType(const OscOverlayEvent& event)
{
	return event.settled ? RuntimeEventType::OscOverlaySettled : RuntimeEventType::OscOverlayApplied;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const FrameRenderedEvent&)
{
	return RuntimeEventType::FrameRendered;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const PreviewFrameAvailableEvent&)
{
	return RuntimeEventType::PreviewFrameAvailable;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const InputSignalChangedEvent&)
{
	return RuntimeEventType::InputSignalChanged;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const InputFrameArrivedEvent&)
{
	return RuntimeEventType::InputFrameArrived;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const OutputFrameScheduledEvent&)
{
	return RuntimeEventType::OutputFrameScheduled;
}

inline RuntimeEventType RuntimeEventPayloadType(const OutputFrameCompletedEvent& event)
{
	if (event.result == "DisplayedLate")
		return RuntimeEventType::OutputLateFrameDetected;
	if (event.result == "Dropped")
		return RuntimeEventType::OutputDroppedFrameDetected;
	return RuntimeEventType::OutputFrameCompleted;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const BackendStateChangedEvent&)
{
	return RuntimeEventType::BackendStateChanged;
}

inline RuntimeEventType RuntimeEventPayloadType(const SubsystemWarningEvent& event)
{
	return event.cleared ? RuntimeEventType::SubsystemWarningCleared : RuntimeEventType::SubsystemWarningRaised;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const SubsystemRecoveredEvent&)
{
	return RuntimeEventType::SubsystemRecovered;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const TimingSampleRecordedEvent&)
{
	return RuntimeEventType::TimingSampleRecorded;
}

constexpr RuntimeEventType RuntimeEventPayloadType(const QueueDepthChangedEvent&)
{
	return RuntimeEventType::QueueDepthChanged;
}
