//
// Created by vastrakai on 9/23/2024.
//

#include "ScriptLib.hpp"
#include "lua.h"

// Helper function to get the Lua type name as a string
std::string luaTypeToString(int luaType)
{
    switch (luaType)
    {
        case LUA_TNIL: return "nil";
        case LUA_TNUMBER: return "number";
        case LUA_TBOOLEAN: return "boolean";
        case LUA_TSTRING: return "string";
        case LUA_TTABLE: return "table";
        case LUA_TFUNCTION: return "function";
        case LUA_TUSERDATA: return "userdata";
        case LUA_TTHREAD: return "thread";
        case LUA_TLIGHTUSERDATA: return "lightuserdata";
        default: return "unknown";
    }
}

// Function to generate documentation for LuaBridge-bound class member functions
void generateDocsForMetatable(lua_State* L, std::ofstream& docFile, const std::string& className, int depth)
{
    // Check if there is a metatable
    if (lua_getmetatable(L, -1)) {
        // Check if the metatable has an __index field, which contains methods
        lua_pushstring(L, "__index");
        lua_gettable(L, -2);

        if (lua_istable(L, -1)) {
            lua_pushnil(L);  // First key for the __index table (methods)

            while (lua_next(L, -2) != 0) {
                const char* key = lua_tostring(L, -2);
                int valueType = lua_type(L, -1);

                std::string indent(depth * 2, ' ');  // Create indentation for nested tables

                if (valueType == LUA_TFUNCTION) {
                    docFile << indent << "function " << className << ":" << key << "() end -- member function\n\n";
                }

                lua_pop(L, 1);  // Pop value, keep key for next iteration
            }
        }

        lua_pop(L, 1);  // Pop the __index table
        lua_pop(L, 1);  // Pop the metatable
    }
}

// Recursive function to traverse tables and generate docs
void generateDocsForTable(lua_State* L, std::ofstream& docFile, int depth = 0)
{
    lua_pushnil(L); // Start with the first key

    while (lua_next(L, -2) != 0)
    {
        const char* key = lua_tostring(L, -2);  // Get the key
        int valueType = lua_type(L, -1);        // Get the value type

        if (!key)
        {
            lua_pop(L, 1);  // Pop value, keep key for next iteration
            continue;
        }

        std::string indent(depth * 2, ' ');  // Create indentation for nested tables

        // Check if the value is a function
        if (valueType == LUA_TFUNCTION)
        {
            docFile << indent << "function " << key << "() end\n\n";
        }
        // Check if the value is a table, recursively generate docs for it
        else if (valueType == LUA_TTABLE)
        {
            // Ignore the global _G table to prevent recursive loops
            if (strcmp(key, "_G") == 0)
            {
                lua_pop(L, 1);  // Pop the value, but keep the key for the next iteration
                continue;
            }

            docFile << indent << "-- Class: " << key << "\n";
            docFile << indent << key << " = {}\n\n";

            // Generate documentation for the table itself
            generateDocsForTable(L, docFile, depth + 1);

            // Generate documentation for methods if the table has a metatable
            generateDocsForMetatable(L, docFile, key, depth + 1);
        }
        else
        {
            docFile << indent << "-- " << key << ": " << luaTypeToString(valueType) << "\n";
        }

        lua_pop(L, 1);  // Pop the value, but keep the key for the next iteration
    }
}

// Generate documentation for all registered functions and classes
void ScriptLib::generateDocs(lua_State* state)
{
    if (!state)
    {
        spdlog::error("lua_State is null, cannot generate docs.");
        return;
    }

    std::string docPath = FileUtils::getSolsticeDir() + "Scripts\\lua_docs.lua";
    std::ofstream docFile(docPath);

    if (!docFile.is_open())
    {
        spdlog::error("Failed to open file for writing: {}", docPath);
        return;
    }

    docFile << "-- This file was automatically generated by Solstice\n";
    docFile << "-- It contains documentation for all registered Lua functions and classes\n\n";

    // Push the global table (_G) onto the stack
    lua_getglobal(state, "_G");

    // Verify the global table is a valid Lua table
    if (lua_type(state, -1) != LUA_TTABLE)
    {
        spdlog::error("Global environment (_G) is not a table.");
        lua_pop(state, 1);  // Clean up the stack
        return;
    }

    // Recursively traverse the global table and generate documentation
    generateDocsForTable(state, docFile);

    lua_pop(state, 1);  // Remove the global table from the stack
    docFile.close();
    spdlog::info("Documentation generated at: {}", docPath);
}



void ScriptLib::registerFunction(lua_State* state, lua_CFunction func, const char* functionName, const char* tableName)
{
    lua_getglobal(state, tableName);  // Get the global table, if it exists

    if (lua_isnil(state, -1)) {
        lua_pop(state, 1);  // Pop the nil value from the stack
        lua_newtable(state);  // Create a new table if it doesn't exist
    }

    lua_pushcfunction(state, func, functionName);
    lua_setfield(state, -2, functionName);  // Set the function as a field in the table

    lua_setglobal(state, tableName);  // Set the table as a global variable in Lua

    spdlog::info("[lua] registered function: {}.{}", tableName, functionName);
}

// register global function
void ScriptLib::registerFunction(lua_State* state, lua_CFunction func, const char* functionName)
{
    lua_pushcfunction(state, func, functionName);
    lua_setglobal(state, functionName);

    spdlog::info("[lua] registered function: {}", functionName);
}