#pragma once

#include "RuntimeEventPayloads.h"

#include <chrono>
#include <cstdint>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>

using RuntimeEventPayload = std::variant<
	std::monostate,
	OscValueReceivedEvent,
	OscValueCoalescedEvent,
	OscCommitRequestedEvent,
	HttpControlMutationRequestedEvent,
	WebSocketClientConnectedEvent,
	RuntimeStateBroadcastRequestedEvent,
	FileChangeDetectedEvent,
	ManualReloadRequestedEvent,
	RuntimeMutationEvent,
	RuntimeStateChangedEvent,
	RuntimePersistenceRequestedEvent,
	RuntimeReloadRequestedEvent,
	ShaderPackagesChangedEvent,
	RenderSnapshotPublishRequestedEvent,
	RuntimeStatePresentationChangedEvent,
	ShaderBuildEvent,
	CompileStatusChangedEvent,
	RenderSnapshotPublishedEvent,
	RenderResetEvent,
	OscOverlayEvent,
	FrameRenderedEvent,
	PreviewFrameAvailableEvent,
	InputSignalChangedEvent,
	InputFrameArrivedEvent,
	OutputFrameScheduledEvent,
	OutputFrameCompletedEvent,
	BackendStateChangedEvent,
	SubsystemWarningEvent,
	SubsystemRecoveredEvent,
	TimingSampleRecordedEvent,
	QueueDepthChangedEvent>;

inline RuntimeEventType RuntimeEventPayloadType(const RuntimeEventPayload& payload)
{
	return std::visit([](const auto& value) -> RuntimeEventType {
		using PayloadType = std::decay_t<decltype(value)>;
		if constexpr (std::is_same_v<PayloadType, std::monostate>)
			return RuntimeEventType::Unknown;
		else
			return RuntimeEventPayloadType(value);
	}, payload);
}

struct RuntimeEvent
{
	RuntimeEventType type = RuntimeEventType::Unknown;
	uint64_t sequence = 0;
	std::chrono::steady_clock::time_point createdAt = std::chrono::steady_clock::now();
	std::string source;
	RuntimeEventPayload payload;

	bool HasPayload() const
	{
		return !std::holds_alternative<std::monostate>(payload);
	}

	bool PayloadMatchesType() const
	{
		return RuntimeEventPayloadType(payload) == type;
	}
};

template <typename Payload>
RuntimeEvent MakeRuntimeEvent(Payload payload, std::string source = {}, uint64_t sequence = 0,
	std::chrono::steady_clock::time_point createdAt = std::chrono::steady_clock::now())
{
	RuntimeEvent event;
	event.type = RuntimeEventPayloadType(payload);
	event.sequence = sequence;
	event.createdAt = createdAt;
	event.source = std::move(source);
	event.payload = std::move(payload);
	return event;
}
