#include "Profiler.h"
#include <cmath>

#include "Logging.h"

#include <fstream>
#include <json.hpp>
using json = nlohmann::json;

extern float g_fps;
extern EngineConfig g_engineConfig;

inline double Lerp(double a, double b, double t)
{
    return a + (b - a) * t;
}

static void SmoothVisualDurations(ProfileNode &node, double blendFactor)
{
    node.visualStartMs = Lerp(node.visualStartMs, node.startMs, blendFactor);
    node.visualDurationMs = Lerp(node.visualDurationMs, node.durationMs, blendFactor);
    for (auto &child : node.children)
        SmoothVisualDurations(child, blendFactor);
}

void HierarchicalProfiler::BeginFrame()
{
    if (!g_engineConfig.settings.profile_enabled)
        return;

    root.startMs = 0.0;
    root.durationMs = 0.0;
    root.visualStartMs = 0.0;
    root.visualDurationMs = 0.0;
    root.children.clear();

    currentStack.clear();
    sectionStartTimes.clear();

    currentStack.reserve(ProfilesLastFrame);
    sectionStartTimes.reserve(ProfilesLastFrame);

    startTime = Clock::now();
    currentStack.push_back(&root);

    ProfilesLastFrame = 0;
}

void HierarchicalProfiler::BeginSection(const std::string& name)
{
    if (__builtin_expect(!g_engineConfig.settings.profile_enabled, 0))
        return;

    if (__builtin_expect(currentStack.size() == 0, 0))
    {
        static bool warned = false;
        if (!warned) {
            Logger::LogWarning("[Profiler] BeginSection called with no active frame (name: %s)", name.c_str());
            warned = true;
        }
        return;
    }

    const auto now = Clock::now();
    const double startMs = std::chrono::duration<double, std::milli>(now - startTime).count();

    ProfileNode* parent = currentStack.back();
    parent->children.emplace_back(); // Use default ctor, fill manually
    ProfileNode& child = parent->children.back();

    child.name = name;
    child.startMs = startMs;

    currentStack.push_back(&child);
    sectionStartTimes.push_back(now);
    ++ProfilesLastFrame;
}



void HierarchicalProfiler::EndSection()
{
    if (!g_engineConfig.settings.profile_enabled)
        return;

    if (currentStack.size() <= 1 || sectionStartTimes.empty())
        return;

    auto now = Clock::now();
    ProfileNode &node = *currentStack.back();
    double duration = std::chrono::duration<double, std::milli>(now - sectionStartTimes.back()).count();

    node.durationMs = duration;

    currentStack.pop_back();
    sectionStartTimes.pop_back();
}

void HierarchicalProfiler::BeginEngineSection(const std::string &name)
{
    if (g_engineConfig.settings.profile_enabled && g_engineConfig.settings.profile_editor)
        BeginSection(name);
}

void HierarchicalProfiler::EndEngineSection()
{
    if (g_engineConfig.settings.profile_enabled && g_engineConfig.settings.profile_editor)
        EndSection();
}

void HierarchicalProfiler::BeginDeepSection(const std::string &name)
{
    if (g_engineConfig.settings.profile_deep)
        BeginSection(name);
}

void HierarchicalProfiler::EndDeepSection()
{
    if (g_engineConfig.settings.profile_deep)
        EndSection();
}

void HierarchicalProfiler::EndFrame()
{
    if (!g_engineConfig.settings.profile_enabled)
        return;

    root.durationMs = std::chrono::duration<double, std::milli>(Clock::now() - startTime).count();

    if (frameHistory.empty())
        root.visualDurationMs = root.durationMs;

    double blendFactor = 1.0 - std::pow(0.01, 1.0 / g_fps);
    SmoothVisualDurations(root, blendFactor);

    frameHistory.push_back(root);
    if (frameHistory.size() > maxFrames)
        frameHistory.erase(frameHistory.begin());
}

const std::vector<ProfileNode> &HierarchicalProfiler::GetFrames() const
{
    return frameHistory;
}

const ProfileNode *HierarchicalProfiler::GetLatestFrame() const
{
    return frameHistory.empty() ? nullptr : &frameHistory.back();
}

json SerializeProfileNode(const ProfileNode &node)
{
    json j;
    j["name"] = node.name;
    j["startMs"] = node.startMs;
    j["durationMs"] = node.durationMs;
    j["visualStartMs"] = node.visualStartMs;
    j["visualDurationMs"] = node.visualDurationMs;

    for (const auto &child : node.children)
    {
        j["children"].push_back(SerializeProfileNode(child));
    }
    return j;
}

HierarchicalProfiler profiler;
