#include <frostbite2D/script/squirrel_vm.h>

#include <SDL2/SDL.h>
#include <squirrel.h>

namespace frostbite2D {

SquirrelVM &SquirrelVM::get() {
  static SquirrelVM instance;
  return instance;
}

void SquirrelVM::setScriptDirectory(const std::string &path) {
  scriptDirectory_ = path;
}

void SquirrelVM::setDebugMode(bool enable) { debugMode_ = enable; }

bool SquirrelVM::init() {
  if (initialized_) {
    return true;
  }

  vm_ = sq_open(1024);
  if (!vm_) {
    SDL_LogError(SDL_LOG_CATEGORY_ERROR, "[Squirrel] Failed to create VM");
    return false;
  }

  sqstd_seterrorhandlers(vm_);
  sq_pushroottable(vm_);
  sqstd_register_bloblib(vm_);
  sqstd_register_iolib(vm_);
  sqstd_register_systemlib(vm_);
  sqstd_register_mathlib(vm_);
  sqstd_register_stringlib(vm_);

  sq_setprintfunc(vm_, printCallback, errorCallback);

  loadScript();

  initialized_ = true;
  SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
              "[Squirrel] VM initialized (Squirrel %s)", SQUIRREL_VERSION);

  return true;
}

void SquirrelVM::loadScript() {

  std::string scriptDir =
      scriptDirectory_.empty() ? "scripts" : scriptDirectory_;
  std::string configPath = scriptDir + "/scripts.json";

  Asset &asset = Asset::get();
  std::optional<std::string> jsonContent = asset.readFileToString(configPath);

  if (!jsonContent) {
    SDL_LogError(SDL_LOG_CATEGORY_ERROR,
                 "[Squirrel] Failed to load scripts config: %s",
                 configPath.c_str());
    return;
  }

  try {
    auto json = nlohmann::json::parse(*jsonContent);
    auto files = json["files"];

    for (const auto &filePath : files) {
      std::string fullPath = scriptDir + "/" + std::string(filePath);
      loadScriptFile(fullPath);
    }
  } catch (const std::exception &e) {
    SDL_LogError(SDL_LOG_CATEGORY_ERROR,
                 "[Squirrel] Failed to parse scripts.json: %s", e.what());
  }
}

void SquirrelVM::run() {
  if (!initialized_ || !vm_) {
    return;
  }

  sq_pushroottable(vm_);
  sq_pushstring(vm_, "main", -1);

  if (SQ_SUCCEEDED(sq_get(vm_, -2))) {
    sq_pushroottable(vm_);

    if (SQ_FAILED(sq_call(vm_, 1, SQFalse, SQTrue))) {
      SDL_LogError(SDL_LOG_CATEGORY_ERROR,
                   "[Squirrel] Failed to call main function");
    }

    sq_pop(vm_, 2);
  } else {
    sq_pop(vm_, 1);
  }
}

bool SquirrelVM::loadScriptFile(const std::string &path) {
  Asset &asset = Asset::get();
  std::optional<std::string> content = asset.readFileToString(path);

  if (!content) {
    SDL_LogError(SDL_LOG_CATEGORY_ERROR, "[Squirrel] Failed to read script: %s",
                 path.c_str());
    return false;
  }

  const SQChar *script = content->c_str();

  SQInteger size = static_cast<SQInteger>(content->size());

  if (SQ_FAILED(sq_compilebuffer(vm_, script, size, path.c_str(), SQTrue))) {
    SDL_LogError(SDL_LOG_CATEGORY_ERROR,
                 "[Squirrel] Failed to compile script: %s", path.c_str());
    return false;
  }

  sq_pushroottable(vm_);

  if (SQ_FAILED(sq_call(vm_, 1, SQTrue, SQTrue))) {
    SDL_LogError(SDL_LOG_CATEGORY_ERROR,
                 "[Squirrel] Failed to execute script: %s", path.c_str());
    sq_pop(vm_, 2);
    return false;
  }

  sq_pop(vm_, 2);
  SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[Squirrel] Loaded script: %s",
              path.c_str());
  return true;
}

void SquirrelVM::shutdown() {
  if (vm_) {
    sq_close(vm_);
    vm_ = nullptr;
  }
  initialized_ = false;
  SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[Squirrel] VM shutdown");
}

void SquirrelVM::printCallback(HSQUIRRELVM vm, const char *s, ...) {
  va_list args;
  va_start(args, s);

  int size = vsnprintf(nullptr, 0, s, args);
  va_end(args);

  if (size > 0) {
    std::vector<char> buffer(size + 1);
    va_start(args, s);
    vsnprintf(buffer.data(), buffer.size(), s, args);
    va_end(args);
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[Squirrel] %s", buffer.data());
  }
}

void SquirrelVM::errorCallback(HSQUIRRELVM vm, const char *s, ...) {
  va_list args;
  va_start(args, s);

  int size = vsnprintf(nullptr, 0, s, args);
  va_end(args);

  if (size > 0) {
    std::vector<char> buffer(size + 1);
    va_start(args, s);
    vsnprintf(buffer.data(), buffer.size(), s, args);
    va_end(args);
    SDL_LogError(SDL_LOG_CATEGORY_ERROR, "[Squirrel Error] %s", buffer.data());
  }
}

} // namespace frostbite2D
