#pragma once

#include <reshade.hpp>
#include <functional>
#include "GameFixes.h"

constexpr auto OSD_SHADER_NAME = "OSD.fx";
using OSDUpdateFn = std::function<void(reshade::api::effect_runtime*)>;
using OSDEndFn = std::function<void(reshade::api::effect_runtime*)>;

// Reshade shader uniform variables
extern reshade::api::effect_uniform_variable u_td_show;
extern reshade::api::effect_uniform_variable u_td_enabled;
extern reshade::api::effect_uniform_variable u_td_world;
extern reshade::api::effect_uniform_variable u_td_AI;
extern reshade::api::effect_uniform_variable u_GodMode_show;
extern reshade::api::effect_uniform_variable u_GodMode_enabled;
extern reshade::api::effect_uniform_variable u_IgnoreHits_show;
extern reshade::api::effect_uniform_variable u_IgnoreHits_enabled;
extern reshade::api::effect_uniform_variable u_Stealth_show;
extern reshade::api::effect_uniform_variable u_Stealth_enabled;
extern reshade::api::effect_uniform_variable u_Stamina_show;
extern reshade::api::effect_uniform_variable u_Stamina_enabled;
extern reshade::api::effect_uniform_variable u_Mana_show;
extern reshade::api::effect_uniform_variable u_Mana_enabled;

extern void SetFixesEnabled(GameFixes fix, bool value);
extern void SaveSettings();

/**
 * @brief Displays an On-Screen Display (OSD) for a specified duration.
 *
 * This function triggers an OSD that lasts for `duration` seconds.
 * During the OSD display, the `onUpdate` callback will be called every frame
 * with the current `reshade::api::effect_runtime` pointer, allowing shader updates
 * or other visual effects. When the OSD duration ends, the `onEnd` callback
 * is executed to perform any cleanup or final state updates.
 * @param duration Duration in seconds for which the OSD should be visible.
 * @param onUpdate Callback executed each frame while the OSD is visible.
 * @param onEnd Callback executed once when the OSD ends or expires.
 * @note This function only schedules the OSD. The actual frame updates occur
 *       during `UpdateOSD`, which must be called once per frame.
 * @note Callbacks should be fast and non-blocking.
 */
void ShowOSD(float duration, OSDUpdateFn onUpdate, OSDEndFn onEnd);

/**
 * @brief Updates the OSD system each frame and executes callbacks if needed.
 *
 * This function must be called once per frame, typically in `on_reshade_present`,
 * to update any active OSDs, decrement their timers, and call the
 * `onUpdate` and `onEnd` callbacks as necessary.
 * @param runtime Pointer to the current `reshade::api::effect_runtime` object.
 * @param deltaTime Time elapsed (in seconds) since the last frame.
 * @note If no OSD is active, this function does nothing.
 * @note Always call this function after processing hotkeys to ensure proper
 *       timing of OSDs.
 */
void UpdateOSD(reshade::api::effect_runtime* runtime, float deltaTime);

/**
 * @brief Find all uniform variables enumerated.
 *
 * This function must be called typically in `on_reshade_begin_effects`,
 * @param runtime Pointer to the current `reshade::api::effect_runtime` object.
 * @param effect_name File name of the effect file to enumerate uniform variables from (eg. myfile.fx)
 */
void FindAllUniformVariables(reshade::api::effect_runtime* runtime, const char* effect_name);

/**
 * @brief Reset all uniform variables enumerated.
 *
 * This function must be called once, typically in `on_reshade_begin_effects`,
 * using an atomic compare-and-exchange operation
 * @param runtime Pointer to the current `reshade::api::effect_runtime` object.
 * @param effect_name File name of the effect file to enumerate uniform variables from (eg. myfile.fx)
 */
void ResetAllUniformVariables(reshade::api::effect_runtime* runtime, const char* effect_name);

/**
 * @brief Toggle cheats & OSD display.
 *
 * This function must be called once, typically in `init_effect_runtime`,
 * using an atomic compare-and-exchange operation
 * @param cheatEnabled cheat variable.
 * @param fix cheat type to toggle (TimeDilation, God Mode ...)
 * @param showUniform the Reshade uniform variable to show/hide the cheat on OSD
 * @param enabledUniform the cheat state (enabled or not)
 * @param duration the default OSD display duration (default 3 s)
 * @param effect_name File name of the effect file to enumerate uniform variables from (eg. myfile.fx)
 * @param extraFloats extra Reshade uniform variable (float) to initialize
 */
template<typename T>
void ToggleOSD(T& cheatEnabled, GameFixes fix,
    reshade::api::effect_uniform_variable showUniform,
    reshade::api::effect_uniform_variable enabledUniform,
    float duration = 3.f,
    std::initializer_list<std::pair<reshade::api::effect_uniform_variable, float>> extraFloats = {}) {

    cheatEnabled = !cheatEnabled;
    SetFixesEnabled(fix, cheatEnabled);
    SaveSettings();

    std::vector<std::pair<reshade::api::effect_uniform_variable, float>> stableFloats(extraFloats);

    ShowOSD(duration,
        [=, stableFloats = std::move(stableFloats)](reshade::api::effect_runtime* rt) {
            ResetAllUniformVariables(rt, OSD_SHADER_NAME);
            rt->set_uniform_value_bool(showUniform, true);
            rt->set_uniform_value_bool(enabledUniform, cheatEnabled);

            for (auto& uniform : stableFloats)
                if (uniform.first.handle)
                    rt->set_uniform_value_float(uniform.first, uniform.second);
        },
        [=](reshade::api::effect_runtime* rt) {
            rt->set_uniform_value_bool(showUniform, false);
        });
}