#include "utils.h"
#include <map>
#include "stb_image.h"
#include <windows.h>
#include <filesystem>
#include <string>
#include <sstream>
#include <unordered_map>
#include <mutex>
#include <xxhash.h>
#include <filesystem>
#include <chrono>
#include <ctime>
#include "imgui.h"
#include "imgui_internal.h"  // for ImGuiWindow, ImGuiDockNode
#include <fstream>
#include <sstream>


std::unordered_map<std::string, ImageCacheEntry> textureCache;
std::unordered_map<std::string, ImageCacheEntry> engineTextureCache;




ImageCacheEntry LoadImageCache(const std::string& path)
{
    Logger::LogWarning(
        "[LoadImageCache] This function is **deprecated**.\n"
        "Please use one of the following AssetManager functions instead:\n"
        "  - AssetManager::LoadAssetAsync(path, AssetType::Image)\n"
        "  - AssetManager::GetAssetByID(uaid)\n"
        "  - AssetManager::GetAssetByPath(path)\n"
    );

    return ImageCacheEntry{};
}




GLuint LoadTextureIfNeeded(const std::string &path)
{
    auto [it, inserted] = textureCache.try_emplace(path, ImageCacheEntry{});

    if (!inserted)
    {
        return it->second.textureID;
    }

    int w, h, channels;
    stbi_set_flip_vertically_on_load(false);
    unsigned char *data = stbi_load(path.c_str(), &w, &h, &channels, 4);
    if (!data)
        return 0;

    GLuint id;
    glGenTextures(1, &id);
    glBindTexture(GL_TEXTURE_2D, id);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    stbi_image_free(data);

    glm::vec2 imageSize(w, h);
    
    it->second = { id, imageSize };

    Logger::LogVerbose("Loaded Image: (%d, %d) '%s'",w, h, path.c_str());

    return id;
}

GLuint EngineLoadTextureIfNeeded(const std::string &path)
{
    auto [it, inserted] = engineTextureCache.try_emplace(path, ImageCacheEntry{});

    if (!inserted)
    {
        return it->second.textureID;
    }

    int w, h, channels;
    stbi_set_flip_vertically_on_load(false);
    unsigned char *data = stbi_load(path.c_str(), &w, &h, &channels, 4);
    if (!data)
        return 0;

    GLuint id;
    glGenTextures(1, &id);
    glBindTexture(GL_TEXTURE_2D, id);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    stbi_image_free(data);

    glm::vec2 imageSize(w, h);
    
    it->second = { id, imageSize };

    Logger::LogVerbose("Loaded Image: '%s'", path.c_str());
    return id;
}


std::string GetFilenameFromPath(const std::string &path)
{
    static std::unordered_map<std::string, std::string> cache;
    static std::mutex cacheMutex;

    std::scoped_lock lock(cacheMutex);

    auto it = cache.find(path);
    if (it != cache.end())
        return it->second;

    // Extract filename
    size_t pos1 = path.find_last_of("/\\");
    std::string filename = (pos1 == std::string::npos) ? path : path.substr(pos1 + 1);

    cache[path] = filename;
    return filename;
}

bool DeleteLatestLogFile()
{
    // Get %USERPROFILE%
    char userPath[MAX_PATH];
    DWORD len = GetEnvironmentVariableA("USERPROFILE", userPath, MAX_PATH);
    if (len == 0 || len >= MAX_PATH)
        return false;

    std::filesystem::path logPath = std::filesystem::path(userPath) / ".CreateEngine" / ".latest.log";

    if (std::filesystem::exists(logPath))
    {
        std::error_code ec;
        std::filesystem::remove(logPath, ec);
        return !ec; // true = success
    }

    return false; // File didn't exist
}

bool IsWindowVisibleTab(const char* name)
{
    ImGuiWindow* win = ImGui::FindWindowByName(name);
    if (!win || !win->DockNode)
        return false;

    return win->DockNode->SelectedTabId == win->ID;
}



std::string HashString(const std::string& data)
{
    uint64_t hash = XXH3_64bits(data.data(), data.size());

    std::ostringstream oss;
    oss << std::hex << std::setfill('0') << std::setw(16) << hash;
    return oss.str(); // returns lowercase 16-char hex string
}

std::string GetFileHash(const std::string &path)
{
    std::ifstream stream(path, std::ios::binary);
    if (!stream)
        return "";

    std::string data((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
    return HashString(data);
}



std::time_t GetFileLastWrite(const std::string &path)
{
    try
    {
        auto ftime = std::filesystem::last_write_time(path);
        auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
            ftime - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now());
        return std::chrono::system_clock::to_time_t(sctp);
    }
    catch (...)
    {
        return 0; // return 0 if file doesn't exist or error occurs
    }
}
