#include "ScriptEngine.h"
#include "Game.h"
#include <iostream>
#include <stdexcept>

#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>

namespace ZL {

    struct ScriptEngine::Impl {
        sol::state lua;
    };

    ScriptEngine::ScriptEngine() = default;
    ScriptEngine::~ScriptEngine() = default;

    void ScriptEngine::init(Game* game) {
        impl = std::make_unique<Impl>();
        sol::state& lua = impl->lua;

        lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string, sol::lib::table);

        auto api = lua.create_named_table("game_api");

        // npc_walk_to(index, x, y, z [, on_arrived])
        // on_arrived is an optional Lua function called when the NPC reaches the target.
        // It can call npc_walk_to again (or anything else) to chain behaviour.
        api.set_function("npc_walk_to",
            [game](int index, float x, float y, float z, sol::object on_arrived) {
                auto& npcs = game->npcs;
                if (index < 0 || index >= static_cast<int>(npcs.size())) {
                    std::cerr << "[script] npc_walk_to: index " << index
                        << " out of range (0.." << npcs.size() - 1 << ")\n";
                    return;
                }
                std::function<void()> cb;
                if (on_arrived.is<sol::protected_function>()) {
                    sol::protected_function fn = on_arrived.as<sol::protected_function>();
                    cb = [fn]() mutable {
                        auto result = fn();
                        if (!result.valid()) {
                            sol::error err = result;
                            std::cerr << "[script] on_arrived error: " << err.what() << "\n";
                        }
                        };
                }
                npcs[index]->setTarget(Eigen::Vector3f(x, y, z), std::move(cb));
            });

        // pickup_item(object_name)
        api.set_function("pickup_item", [game](const std::string& objectName) {
            std::cout << "[script] pickup_item: " << objectName << std::endl;

            for (auto& intObj : game->interactiveObjects) {
                if (intObj.id == objectName && intObj.isActive) {
                    // Add item to inventory
                    game->inventory.addItem(intObj.dropItem);

                    // Deactivate object
                    intObj.isActive = false;

                    std::cout << "[script] Item picked up successfully: " << intObj.dropItem.name << std::endl;
                    return;
                }
            }
            std::cerr << "[script] Warning: Interactive object not found or already picked up: " << objectName << std::endl;
            });


        // show_inventory()
        api.set_function("show_inventory", [game]() {
            std::cout << "[script] show_inventory called" << std::endl;

            game->menuManager.uiManager.setNodeVisible("inventory_items_panel", true);
            game->menuManager.uiManager.setNodeVisible("close_inventory_button", true);
            game->inventoryOpen = true;

            // Update UI with current items
            const auto& items = game->inventory.getItems();
            std::string itemText;

            if (items.empty()) {
                itemText = "Inventory (Empty)";
            }
            else {
                itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
                for (size_t i = 0; i < items.size(); ++i) {
                    itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
                }
            }

            game->menuManager.uiManager.setText("inventory_items_text", itemText);
            });

        // hide_inventory()
        api.set_function("hide_inventory", [game]() {
            std::cout << "[script] hide_inventory called" << std::endl;
            game->menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
            game->menuManager.uiManager.setNodeVisible("close_inventory_button", false);
            game->inventoryOpen = false;
            });

        // add_item(item_id, name, description, icon)
        api.set_function("add_item", [game](const std::string& id, const std::string& name,
            const std::string& description, const std::string& icon) {
                std::cout << "[script] add_item: " << name << std::endl;
                Item newItem(id, name, description, icon);
                game->inventory.addItem(newItem);
            });

        // remove_item(item_id)
        api.set_function("remove_item", [game](const std::string& id) {
            std::cout << "[script] remove_item: " << id << std::endl;
            game->inventory.removeItem(id);
            });

        // deactivate_interactive_object(object_name)
        api.set_function("deactivate_interactive_object", [game](const std::string& objectName) {
            std::cout << "[script] deactivate_interactive_object: " << objectName << std::endl;
            for (auto& intObj : game->interactiveObjects) {
                if (intObj.id == objectName) {
                    intObj.isActive = false;
                    std::cout << "[script] Interactive object deactivated: " << objectName << std::endl;
                    return;
                }
            }
            std::cerr << "[script] Warning: Interactive object not found: " << objectName << std::endl;
            });

        // get_inventory_count()
        api.set_function("get_inventory_count", [game]() {
            return game->inventory.getCount();
            });

        // has_item(item_id)
        api.set_function("has_item", [game](const std::string& id) {
            return game->inventory.hasItem(id);
            });

        api.set_function("start_dialogue",
            [game](const std::string& dialogueId) {
                if (!game->requestDialogueStart(dialogueId)) {
                    std::cerr << "[script] start_dialogue failed for id: " << dialogueId << "\n";
                }
            });

        api.set_function("set_dialogue_flag",
            [game](const std::string& flag, int value) {
                game->setDialogueFlag(flag, value);
            });

        api.set_function("get_dialogue_flag",
            [game](const std::string& flag) {
                return game->getDialogueFlag(flag);
            });

        api.set_function("set_navigation_area_available",
            [game](const std::string& areaName, bool available) {
                if (!game->setNavigationAreaAvailable(areaName, available)) {
                    std::cerr << "[script] set_navigation_area_available: area not found: "
                              << areaName << "\n";
                }
            });

        // receive_npc_gift(npc_index)
        api.set_function("receive_npc_gift", [game](int npcIndex) {
            std::cout << "[script] receive_npc_gift: npc index " << npcIndex << std::endl;

            auto& npcs = game->npcs;
            if (npcIndex < 0 || npcIndex >= static_cast<int>(npcs.size())) {
                std::cerr << "[script] receive_npc_gift: index out of range\n";
                return;
            }

            auto& npc = npcs[npcIndex];
            if (!npc) {
                std::cerr << "[script] receive_npc_gift: npc is null\n";
                return;
            }

            if (npc->giftReceived) {
                std::cout << "[script] NPC " << npc->npcName << " already gave gift\n";
                return;
            }
            if (npc->giftItem.id.empty()) {
                std::cerr << "[script] receive_npc_gift: NPC has no gift\n";
                return;
            }

            game->inventory.addItem(npc->giftItem);
            npc->giftReceived = true;

            std::cout << "[script] Received gift from " << npc->npcName << ": "
                << npc->giftItem.name << std::endl;
            });

        lua.script_file("resources/start.lua");
    }

    void ScriptEngine::callNpcInteractCallback(int npcIndex) {
        if (!impl) {
            std::cerr << "[SCRIPT] Engine not initialized!" << std::endl;
            return;
        }
        sol::state& lua = impl->lua;
        sol::function fn = lua["on_npc_interact"];
        if (fn.valid()) {
            auto result = fn(npcIndex);
            if (!result.valid()) {
                sol::error err = result;
                std::cerr << "[SCRIPT] on_npc_interact error: " << err.what() << "\n";
            }
            else {
                std::cout << "[SCRIPT] on_npc_interact called with index " << npcIndex << std::endl;
            }
        }
        else {
            std::cerr << "[SCRIPT] Lua function 'on_npc_interact' not found!" << std::endl;
        }
    }

    void ScriptEngine::runScript(const std::string& path) {
        auto result = impl->lua.safe_script_file(path, sol::script_pass_on_error);
        if (!result.valid()) {
            sol::error err = result;
            std::cerr << "[script] Error in " << path << ": " << err.what() << "\n";
        }
    }

    void ScriptEngine::callActivateFunction(const std::string& functionName) {
        if (!impl) {
            throw std::runtime_error("[SCRIPT] Engine not initialized!");
        }

        if (functionName.empty()) {
            throw std::runtime_error("[SCRIPT] Activate function name is empty!");
        }

        sol::state& lua = impl->lua;
        std::cout << "[SCRIPT] Looking for activate function: " << functionName << std::endl;

        sol::function activateFunc = lua[functionName];

        if (!activateFunc.valid()) {
            throw std::runtime_error("[SCRIPT] Lua function not found: " + functionName);
        }

        std::cout << "[SCRIPT] Found function! Calling: " << functionName << std::endl;
        auto result = activateFunc();
        
        if (!result.valid()) {
            sol::error err = result;
            throw std::runtime_error("[SCRIPT] Error executing " + functionName + ": " + std::string(err.what()));
        }

        std::cout << "[SCRIPT] Function executed successfully!" << std::endl;
    }

    void ScriptEngine::callItemPickupCallback(const std::string& objectName) {
        if (!impl) {
            std::cerr << "[SCRIPT] impl is null!" << std::endl;
            return;
        }

        sol::state& lua = impl->lua;

        // Try to find custom activate function first
        sol::function activateFunc = lua["on_item_pickup"];

        if (activateFunc.valid()) {
            std::cout << "[SCRIPT] Callback found! Calling with argument: " << objectName << std::endl;
            auto result = activateFunc(objectName);
            if (!result.valid()) {
                sol::error err = result;
                std::cerr << "[SCRIPT] on_item_pickup callback error: " << err.what() << "\n";
            }
            else {
                std::cout << "[SCRIPT] Callback executed successfully!" << std::endl;
            }
        }
        else {
            std::cout << "[SCRIPT] Fallback: on_item_pickup not found" << std::endl;
        }
    }

    void ScriptEngine::showInventory(Game* game) {
        std::cout << "[script] toggle_inventory called" << std::endl;

        bool isVisible = game->menuManager.uiManager.getNodeVisible("inventory_items_panel");
        game->menuManager.uiManager.setNodeVisible("inventory_items_panel", !isVisible);
        game->menuManager.uiManager.setNodeVisible("close_inventory_button", !isVisible);

        game->inventoryOpen = !isVisible;

        if (!isVisible) {
            const auto& items = game->inventory.getItems();
            std::string itemText;

            if (items.empty()) {
                itemText = "Inventory (Empty)";
            }
            else {
                itemText = "Inventory (" + std::to_string(items.size()) + " items)\n\n";
                for (size_t i = 0; i < items.size(); ++i) {
                    itemText += std::to_string(i + 1) + ". " + items[i].name + "\n";
                }
            }

            game->menuManager.uiManager.setText("inventory_items_text", itemText);
        }
    }

} // namespace ZL
