// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0

#pragma once

#include "types.h"

#include <functional>
#include <optional>
#include <utility>

class Error;
struct WindowInfo;

namespace Threading {
class ThreadHandle;
}

enum class RenderAPI : u8;
enum class GPUVSyncMode : u8;

enum class GPURenderer : u8;
enum class VideoThreadCommandType : u8;

class GPUBackend;
struct VideoThreadCommand;

namespace VideoThread {
using AsyncCallType = std::function<void()>;
using AsyncBufferCallType = void (*)(void*);

enum class RunIdleReason : u8
{
  NoGPUBackend = (1 << 0),
  SystemPaused = (1 << 1),
  OSDMessagesActive = (1 << 2),
  FullscreenUIActive = (1 << 3),
  NotificationsActive = (1 << 4),
  LoadingScreenActive = (1 << 5),
  AchievementOverlaysActive = (1 << 6),
};

/// Starts Big Picture UI.
bool StartFullscreenUI(bool fullscreen, Error* error);
bool IsFullscreenUIRequested();
void StopFullscreenUI();

/// Backend control.
std::optional<GPURenderer> GetRequestedRenderer();
GPUBackend* GetGPUBackend();
bool CreateGPUBackend(GPURenderer renderer, bool upload_vram, std::optional<bool> fullscreen, Error* error);
void DestroyGPUBackend();
bool HasGPUBackend();
bool IsGPUBackendRequested();
bool IsFullscreen();

/// Re-presents the current frame. Call when things like window resizes happen to re-display
/// the current frame with the correct proportions. Should only be called from the CPU thread.
void PresentCurrentFrame();

/// Handles fullscreen transitions and such.
void RecreateRenderWindow();
void SetFullscreen(bool fullscreen);
void SetFullscreenWithCompletionHandler(bool fullscreen, AsyncCallType completion_handler);

/// Called when the window is resized.
void ResizeRenderWindow(s32 width, s32 height, float scale, float refresh_rate);

/// Access to main window size from CPU thread.
const WindowInfo& GetRenderWindowInfo();

void UpdateSettings(bool gpu_settings_changed, bool device_settings_changed, bool thread_changed);
void UpdateGameInfo(const std::string& title, const std::string& serial, const std::string& path, GameHash hash,
                    bool wake_thread = true);
void ClearGameInfo();

/// Triggers an abnormal system shutdown and waits for it to destroy the backend.
void ReportFatalErrorAndShutdown(std::string_view reason);

bool IsOnThread();
bool IsUsingThread();
void RunOnThread(AsyncCallType func);
void RunOnThreadAndSync(AsyncCallType func);
std::pair<VideoThreadCommand*, void*> BeginASyncBufferCall(AsyncBufferCallType func, u32 buffer_size);
void EndASyncBufferCall(VideoThreadCommand* cmd);
void SetVSync(GPUVSyncMode mode, PresentSkipMode present_throttle_mode);

// Should only be called on the GPU thread.
bool GetRunIdleReason(RunIdleReason reason);
void SetRunIdleReason(RunIdleReason reason, bool enabled);
bool IsRunningIdle();
bool IsSystemPaused();
const std::string& GetGameTitle();
const std::string& GetGameSerial();
const std::string& GetGamePath();
GameHash GetGameHash();

VideoThreadCommand* AllocateCommand(VideoThreadCommandType command, u32 size);
void PushCommand(VideoThreadCommand* cmd);
void PushCommandAndWakeThread(VideoThreadCommand* cmd);
void PushCommandAndSync(VideoThreadCommand* cmd, bool spin);
void SyncThread(bool spin);

/// Returns a handle to the video thread.
const Threading::ThreadHandle& GetThreadHandle();

/// Presents the current frame and restores the GPU context, only carefully call on the video thread.
bool PresentFrameAndRestoreContext();

} // namespace VideoThread
