#pragma once

#include "OutputProductionController.h"
#include "RenderCadenceController.h"
#include "RenderOutputQueue.h"
#include "SystemOutputFramePool.h"
#include "VideoBackendLifecycle.h"
#include "VideoIOTypes.h"
#include "VideoPlayoutPolicy.h"

#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <memory>
#include <mutex>
#include <string>
#include <thread>

class HealthTelemetry;
class OpenGLVideoIOBridge;
class RenderEngine;
class RuntimeEventDispatcher;
class VideoIODevice;

class VideoBackend
{
public:
	VideoBackend(RenderEngine& renderEngine, HealthTelemetry& healthTelemetry, RuntimeEventDispatcher& runtimeEventDispatcher);
	~VideoBackend();

	void ReleaseResources();
	VideoBackendLifecycleState LifecycleState() const;
	bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error);
	bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error);
	bool ConfigureInput(const VideoFormat& inputVideoMode, std::string& error);
	bool ConfigureOutput(const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error);
	bool Start();
	bool Stop();

	const VideoIOState& State() const;
	VideoIOState& MutableState();
	bool BeginOutputFrame(VideoIOOutputFrame& frame);
	void EndOutputFrame(VideoIOOutputFrame& frame);
	bool ScheduleOutputFrame(const VideoIOOutputFrame& frame);
	VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth);
	void RecordBackendPlayoutHealth(VideoIOCompletionResult result, const VideoPlayoutRecoveryDecision& recoveryDecision);

	bool HasInputDevice() const;
	bool HasInputSource() const;
	unsigned InputFrameWidth() const;
	unsigned InputFrameHeight() const;
	unsigned OutputFrameWidth() const;
	unsigned OutputFrameHeight() const;
	unsigned CaptureTextureWidth() const;
	unsigned OutputPackTextureWidth() const;
	VideoIOPixelFormat InputPixelFormat() const;
	const std::string& InputDisplayModeName() const;
	const std::string& OutputModelName() const;
	bool SupportsInternalKeying() const;
	bool SupportsExternalKeying() const;
	bool KeyerInterfaceAvailable() const;
	bool ExternalKeyingActive() const;
	const std::string& StatusMessage() const;
	bool ShouldPrioritizeOutputOverPreview() const;
	void SetStatusMessage(const std::string& message);
	void PublishStatus(bool externalKeyingConfigured, const std::string& statusMessage = std::string());
	void ReportNoInputDeviceSignalStatus();

private:
	void HandleInputFrame(const VideoIOFrame& frame);
	void HandleOutputFrameCompletion(const VideoIOCompletion& completion);
	void StartOutputCompletionWorker();
	void StopOutputCompletionWorker();
	void OutputCompletionWorkerMain();
	void StartOutputProducerWorker();
	void StopOutputProducerWorker();
	void OutputProducerWorkerMain();
	void NotifyOutputProducer();
	bool WarmupOutputPreroll();
	std::chrono::milliseconds OutputProducerWakeInterval() const;
	void ProcessOutputFrameCompletion(const VideoIOCompletion& completion);
	std::size_t ProduceReadyOutputFrames(const VideoIOCompletion& completion, std::size_t maxFrames);
	OutputProductionPressure BuildOutputProductionPressure(const RenderOutputQueueMetrics& metrics) const;
	bool RenderReadyOutputFrame(const VideoIOState& state, const VideoIOCompletion& completion);
	std::size_t ScheduleReadyOutputFramesToTarget();
	bool ScheduleReadyOutputFrame();
	bool ScheduleBlackUnderrunFrame();
	void RecordFramePacing(VideoIOCompletionResult completionResult);
	void RecordReadyQueueDepthSample(const RenderOutputQueueMetrics& metrics);
	void RecordDeckLinkBufferTelemetry();
	void RecordSystemMemoryPlayoutStats();
	void RecordOutputRenderDuration(double renderMilliseconds, double acquireMilliseconds, double renderRequestMilliseconds, double endAccessMilliseconds);
	bool ApplyLifecycleTransition(VideoBackendLifecycleState state, const std::string& message);
	bool ApplyLifecycleFailure(const std::string& message);
	void PublishBackendStateChanged(const std::string& state, const std::string& message);
	void PublishInputSignalChanged(const VideoIOFrame& frame, const VideoIOState& state);
	void PublishInputFrameArrived(const VideoIOFrame& frame);
	void PublishOutputFrameScheduled(const VideoIOOutputFrame& frame);
	void PublishOutputFrameCompleted(const VideoIOCompletion& completion);
	void PublishTimingSample(const std::string& subsystem, const std::string& metric, double value, const std::string& unit);
	static std::string CompletionResultName(VideoIOCompletionResult result);
	static std::string PixelFormatName(VideoIOPixelFormat pixelFormat);
	static bool IsEnvironmentFlagEnabled(const char* name);

	HealthTelemetry& mHealthTelemetry;
	RuntimeEventDispatcher& mRuntimeEventDispatcher;
	VideoBackendLifecycle mLifecycle;
	VideoPlayoutPolicy mPlayoutPolicy;
	OutputProductionController mOutputProductionController;
	RenderCadenceController mRenderCadenceController;
	RenderOutputQueue mReadyOutputQueue;
	SystemOutputFramePool mSystemOutputFramePool;
	std::unique_ptr<VideoIODevice> mVideoIODevice;
	std::unique_ptr<OpenGLVideoIOBridge> mBridge;
	std::mutex mOutputCompletionMutex;
	std::condition_variable mOutputCompletionCondition;
	std::deque<VideoIOCompletion> mPendingOutputCompletions;
	std::thread mOutputCompletionWorker;
	std::mutex mOutputProducerMutex;
	std::condition_variable mOutputProducerCondition;
	std::thread mOutputProducerWorker;
	VideoIOCompletion mLastOutputProductionCompletion;
	std::chrono::steady_clock::time_point mLastOutputProductionTime;
	std::mutex mOutputProductionMutex;
	std::mutex mOutputSchedulingMutex;
	mutable std::mutex mOutputMetricsMutex;
	bool mOutputCompletionWorkerRunning = false;
	bool mOutputCompletionWorkerStopping = false;
	bool mOutputProducerWorkerRunning = false;
	bool mOutputProducerWorkerStopping = false;
	bool mInputCaptureDisabled = false;
	uint64_t mNextReadyOutputFrameIndex = 0;
	uint64_t mInputFrameIndex = 0;
	uint64_t mOutputFrameScheduleIndex = 0;
	uint64_t mOutputFrameCompletionIndex = 0;
	bool mHasLastInputSignal = false;
	bool mLastInputSignal = false;
	unsigned mLastInputSignalWidth = 0;
	unsigned mLastInputSignalHeight = 0;
	std::string mLastInputSignalModeName;
	std::chrono::steady_clock::time_point mLastPlayoutCompletionTime;
	double mCompletionIntervalMilliseconds = 0.0;
	double mSmoothedCompletionIntervalMilliseconds = 0.0;
	double mMaxCompletionIntervalMilliseconds = 0.0;
	bool mHasReadyQueueDepthBaseline = false;
	std::size_t mMinReadyQueueDepth = 0;
	std::size_t mMaxReadyQueueDepth = 0;
	uint64_t mReadyQueueZeroDepthCount = 0;
	double mOutputRenderMilliseconds = 0.0;
	double mSmoothedOutputRenderMilliseconds = 0.0;
	double mMaxOutputRenderMilliseconds = 0.0;
	double mOutputFrameAcquireMilliseconds = 0.0;
	double mOutputFrameRenderRequestMilliseconds = 0.0;
	double mOutputFrameEndAccessMilliseconds = 0.0;
	uint64_t mLastLateStreak = 0;
	uint64_t mLastDropStreak = 0;
	uint64_t mLateFrameCount = 0;
	uint64_t mDroppedFrameCount = 0;
	uint64_t mFlushedFrameCount = 0;
};
