#include "lua/wherigo.h"
#include "app.h"
#include "lua/game_engine.h"
#include "lua/zobject.h"
#include "lua/ztimer.h"
#include "ui/wherigo_dialog.h"

extern "C" {
#include <lauxlib.h>
#include <lua.h>
}

#include <wx/dir.h>
#include <wx/filename.h>
#include <wx/log.h>
#include <wx/stdpaths.h>

#include <algorithm>
#include <cmath>
#include <string>
#include <vector>

namespace wherigo {

std::string getCompletionCode(lua_State *L) {
  lua_getglobal(L, "Player");
  if (!lua_istable(L, -1)) {
    lua_pop(L, 1);
    return "";
  }

  lua_getfield(L, -1, "CompletionCode");
  std::string completionCode;
  if (lua_isstring(L, -1)) {
    completionCode = lua_tostring(L, -1);
    if (completionCode.length() > 15) {
      completionCode = completionCode.substr(0, 15);
    }
  }
  lua_pop(L, 2);

  return completionCode;
}

// Wherigo.RequestSync()
static int wherigo_RequestSync(lua_State *L) {
  // RequestSync shows the completion code from the cartridge in a MessageBox
  wxLogDebug(
      "Wherigo.RequestSync() called - showing completion code from cartridge");

  auto completionCode = getCompletionCode(L);
  if (completionCode.empty()) {
    wxApp *app = wxTheApp;
    if (!app) {
      wxLogWarning("Cannot sync: no app instance");
      return 0;
    }

    cApp *wherigApp = dynamic_cast<cApp *>(app);
    if (!wherigApp || !wherigApp->isCartridgeLoaded()) {
      wxLogWarning("Cannot sync: no cartridge loaded");
      return 0;
    }

    auto cartridge = wherigApp->getCartridge();

    completionCode = cartridge->completionCode().substr(0, 15);
  }

  wxString message =
      wxString::Format("Completion Code:\n\n%s\n\nEnter this code on "
                       "wherigo.com to verify your completion.",
                       completionCode.c_str());

  std::vector<wxString> buttons = {"OK"};
  wherigo::WherigoMessageDialog dlg(nullptr, message, "Wherigo Completion",
                                    buttons);
  dlg.ShowModal();

  return 0;
}

// Wherigo.ZonePoint(lat, lng, alt)
static int wherigo_ZonePoint(lua_State *L) {
  lua_Number lat = luaL_checknumber(L, 1);
  lua_Number lng = luaL_checknumber(L, 2);
  lua_Number alt = luaL_optnumber(L, 3, 0.0);

  lua_newtable(L);
  lua_pushnumber(L, lat);
  lua_setfield(L, -2, "latitude");
  lua_pushnumber(L, lng);
  lua_setfield(L, -2, "longitude");
  lua_pushnumber(L, alt);
  lua_setfield(L, -2, "altitude");

  return 1;
}

// Wherigo.Distance(value, unit)
static int wherigo_Distance(lua_State *L) {
  lua_Number value = luaL_checknumber(L, 1);
  const char *unit = luaL_optstring(L, 2, "meters");

  lua_newtable(L);
  lua_pushnumber(L, value);
  lua_setfield(L, -2, "value");
  lua_pushstring(L, unit);
  lua_setfield(L, -2, "unit");

  return 1;
}

// Wherigo.Zone(cartridge)
static int wherigo_Zone(lua_State *L) {
  lua_newtable(L);

  lua_pushstring(L, "Zone");
  lua_setfield(L, -2, "ClassName");

  lua_pushboolean(L, 0);
  lua_setfield(L, -2, "Active");

  lua_pushboolean(L, 1);
  lua_setfield(L, -2, "Visible");

  lua_newtable(L);
  lua_setfield(L, -2, "Points");

  lua_pushcfunction(L, zobject_Contains);
  lua_setfield(L, -2, "Contains");

  return 1;
}

// Wherigo.ZCharacter(cartridge)
static int wherigo_ZCharacter(lua_State *L) {
  lua_newtable(L);

  lua_pushstring(L, "ZCharacter");
  lua_setfield(L, -2, "ClassName");

  lua_pushboolean(L, 0);
  lua_setfield(L, -2, "Active");

  lua_pushboolean(L, 1);
  lua_setfield(L, -2, "Visible");

  lua_pushcfunction(L, zobject_MoveTo);
  lua_setfield(L, -2, "MoveTo");

  lua_pushcfunction(L, zobject_Contains);
  lua_setfield(L, -2, "Contains");

  return 1;
}

// Wherigo.ZItem(cartridge)
static int wherigo_ZItem(lua_State *L) {
  lua_newtable(L);

  lua_pushstring(L, "ZItem");
  lua_setfield(L, -2, "ClassName");

  lua_pushboolean(L, 0);
  lua_setfield(L, -2, "Active");

  lua_pushboolean(L, 1);
  lua_setfield(L, -2, "Visible");

  lua_pushcfunction(L, zobject_MoveTo);
  lua_setfield(L, -2, "MoveTo");

  lua_pushcfunction(L, zobject_Contains);
  lua_setfield(L, -2, "Contains");

  return 1;
}

// Wherigo.ZTask(cartridge)
static int wherigo_ZTask(lua_State *L) {
  lua_newtable(L);

  lua_pushstring(L, "ZTask");
  lua_setfield(L, -2, "ClassName");

  lua_pushboolean(L, 0);
  lua_setfield(L, -2, "Active");

  lua_pushboolean(L, 0);
  lua_setfield(L, -2, "Complete");

  return 1;
}

// Wherigo.ZTimer(cartridge)
static int wherigo_ZTimer(lua_State *L) {
  lua_newtable(L);

  lua_pushstring(L, "ZTimer");
  lua_setfield(L, -2, "ClassName");

  lua_pushstring(L, "Countdown");
  lua_setfield(L, -2, "Type");

  lua_pushnumber(L, 0);
  lua_setfield(L, -2, "Duration");

  lua_pushboolean(L, 0);
  lua_setfield(L, -2, "Running");

  lua_pushcfunction(L, ztimer_Start);
  lua_setfield(L, -2, "Start");

  lua_pushcfunction(L, ztimer_Stop);
  lua_setfield(L, -2, "Stop");

  lua_pushcfunction(L, ztimer_Reset);
  lua_setfield(L, -2, "Reset");

  return 1;
}

// Wherigo.ZInput(cartridge)
static int wherigo_ZInput(lua_State *L) {
  lua_newtable(L);

  lua_pushstring(L, "ZInput");
  lua_setfield(L, -2, "ClassName");

  lua_pushstring(L, "Text");
  lua_setfield(L, -2, "InputType");

  return 1;
}

// Wherigo.ZMedia(cartridge)
static int s_mediaCounter = 0;

static int wherigo_ZMedia(lua_State *L) {
  lua_newtable(L);

  lua_pushstring(L, "ZMedia");
  lua_setfield(L, -2, "ClassName");

  // Store the media index (1-based, 0 is luac)
  s_mediaCounter++;
  lua_pushinteger(L, s_mediaCounter);
  lua_setfield(L, -2, "MediaIndex");

  return 1;
}

void resetMediaCounter() { s_mediaCounter = 0; }

// Wherigo.ZCartridge()
static int wherigo_ZCartridge(lua_State *L) {
  lua_newtable(L);

  lua_pushstring(L, "ZCartridge");
  lua_setfield(L, -2, "ClassName");

  lua_newtable(L);
  lua_setfield(L, -2, "AllZObjects");

  lua_pushcfunction(L, wherigo_RequestSync);
  lua_setfield(L, -2, "RequestSync");

  return 1;
}

// Wherigo.ZCommand(cartridge)
static int wherigo_ZCommand(lua_State *L) {
  lua_newtable(L);

  lua_pushstring(L, "ZCommand");
  lua_setfield(L, -2, "ClassName");

  lua_pushboolean(L, 1);
  lua_setfield(L, -2, "Enabled");

  return 1;
}

// Wherigo.Player
static int wherigo_Player(lua_State *L) {
  lua_newtable(L);

  lua_pushstring(L, "Player");
  lua_setfield(L, -2, "ClassName");

  lua_pushstring(L, "Player");
  lua_setfield(L, -2, "Name");

  lua_newtable(L);
  lua_setfield(L, -2, "Inventory");

  return 1;
}

// Wherigo.MessageBox(table)
static int wherigo_MessageBox(lua_State *L) {
  if (!lua_istable(L, 1))
    return 0;

  // Get Text
  lua_getfield(L, 1, "Text");
  const char *text = lua_isstring(L, -1) ? lua_tostring(L, -1) : "";
  lua_pop(L, 1);

  // Get Buttons
  std::vector<wxString> buttons;
  lua_getfield(L, 1, "Buttons");
  if (lua_istable(L, -1)) {
    int n = lua_objlen(L, -1);
    for (int i = 1; i <= n; i++) {
      lua_rawgeti(L, -1, i);
      if (lua_isstring(L, -1)) {
        buttons.push_back(wxString::FromUTF8(lua_tostring(L, -1)));
      }
      lua_pop(L, 1);
    }
  }
  lua_pop(L, 1);

  // Get Callback
  lua_getfield(L, 1, "Callback");
  bool hasCallback = lua_isfunction(L, -1);
  int callbackRef = LUA_NOREF;
  if (hasCallback) {
    callbackRef = luaL_ref(L, LUA_REGISTRYINDEX);
  } else {
    lua_pop(L, 1);
  }

  wxLogDebug("MessageBox: %s", text);

  // Show dialog
  WherigoMessageDialog dlg(nullptr, wxString::FromUTF8(text), "Wherigo",
                           buttons);
  dlg.ShowModal();
  int selected = dlg.getSelectedButton();

  // Call callback if exists
  if (callbackRef != LUA_NOREF) {
    lua_rawgeti(L, LUA_REGISTRYINDEX, callbackRef);
    if (buttons.empty()) {
      lua_pushnil(L);
    } else if (selected >= 0 && selected < (int)buttons.size()) {
      lua_pushstring(L, buttons[selected].ToUTF8().data());
    } else {
      lua_pushnil(L);
    }
    if (lua_pcall(L, 1, 0, 0) != 0) {
      wxLogError("MessageBox callback error: %s", lua_tostring(L, -1));
      lua_pop(L, 1);
    }
    luaL_unref(L, LUA_REGISTRYINDEX, callbackRef);
  }

  // Notify game state change after MessageBox
  // This ensures UI updates if state was modified during the MessageBox
  GameEngine::getInstance().notifyStateChanged();

  return 0;
}

// Wherigo.Dialog(table)
static int wherigo_Dialog(lua_State *L) {
  if (!lua_istable(L, 1))
    return 0;

  std::vector<DialogEntry> entries;
  int n = lua_objlen(L, 1);

  for (int i = 1; i <= n; i++) {
    lua_rawgeti(L, 1, i);
    if (lua_istable(L, -1)) {
      DialogEntry entry;

      lua_getfield(L, -1, "Text");
      entry.text = lua_isstring(L, -1) ? lua_tostring(L, -1) : "";
      lua_pop(L, 1);

      // Get Media object name
      lua_getfield(L, -1, "Media");
      if (lua_istable(L, -1)) {
        lua_getfield(L, -1, "Name");
        entry.mediaName = lua_isstring(L, -1) ? lua_tostring(L, -1) : "";
        lua_pop(L, 1);
      }
      lua_pop(L, 1);

      lua_getfield(L, -1, "Buttons");
      if (lua_istable(L, -1)) {
        int bn = lua_objlen(L, -1);
        for (int j = 1; j <= bn; j++) {
          lua_rawgeti(L, -1, j);
          if (lua_isstring(L, -1)) {
            entry.buttons.push_back(lua_tostring(L, -1));
          }
          lua_pop(L, 1);
        }
      }
      lua_pop(L, 1);

      entries.push_back(entry);
    }
    lua_pop(L, 1);
  }

  wxLogDebug("Dialog with %zu entries", entries.size());
  for (const auto &entry : entries) {
    wxLogDebug("  Text: %s, Media: %s", entry.text.c_str(),
               entry.mediaName.empty() ? "(none)" : entry.mediaName.c_str());
  }

  // Show dialogs sequentially
  WherigoDialogRunner::getInstance().showDialog(entries);

  return 0;
}

// Wherigo.GetInput(zinput)
static int wherigo_GetInput(lua_State *L) {
  if (lua_istable(L, 1)) {
    lua_getfield(L, 1, "Name");
    const char *name = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(unnamed)";
    wxLogDebug("GetInput: %s", name);
    lua_pop(L, 1);
  }
  return 0;
}

// Wherigo.PlayAudio(zmedia)
static int wherigo_PlayAudio(lua_State *L) {
  if (lua_istable(L, 1)) {
    lua_getfield(L, 1, "Name");
    const char *name = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(unnamed)";
    wxLogDebug("PlayAudio: %s", name);
    lua_pop(L, 1);
  }
  return 0;
}

// Wherigo.ShowScreen(screen, obj)
static int wherigo_ShowScreen(lua_State *L) {
  int screen = luaL_optinteger(L, 1, 0);
  wxLogDebug("ShowScreen: %d", screen);

  // Show details dialog for DETAILSCREEN
  if (screen == 5 && lua_gettop(L) >= 2 && lua_istable(L, 2)) {
    // Get Name
    lua_getfield(L, 2, "Name");
    std::string name = lua_isstring(L, -1) ? lua_tostring(L, -1) : "Details";
    lua_pop(L, 1);
    // Get Description
    lua_getfield(L, 2, "Description");
    std::string desc = lua_isstring(L, -1) ? lua_tostring(L, -1) : "";
    lua_pop(L, 1);
    // Get Media name (prefer Media, fallback to Icon)
    std::string mediaName;
    lua_getfield(L, 2, "Media");
    if (lua_istable(L, -1)) {
      lua_getfield(L, -1, "Name");
      if (lua_isstring(L, -1)) {
        mediaName = lua_tostring(L, -1);
      }
      lua_pop(L, 1);
    }
    lua_pop(L, 1);
    if (mediaName.empty()) {
      lua_getfield(L, 2, "Icon");
      if (lua_istable(L, -1)) {
        lua_getfield(L, -1, "Name");
        if (lua_isstring(L, -1)) {
          mediaName = lua_tostring(L, -1);
        }
        lua_pop(L, 1);
      }
      lua_pop(L, 1);
    }
    // Show dialog with media if available
    std::vector<wxString> buttons = {"OK"};
    wherigo::WherigoMessageDialog dlg(nullptr, wxString::FromUTF8(desc.c_str()), wxString::FromUTF8(name.c_str()), buttons, wxString::FromUTF8(mediaName.c_str()));
    dlg.ShowModal();
    return 0;
  }
  return 0;
}

// Wherigo.LogMessage(message, level)
static int wherigo_LogMessage(lua_State *L) {
  const char *message = luaL_checkstring(L, 1);
  wxLogDebug("Wherigo.LogMessage: %s", message);
  return 0;
}

// Wherigo.NoCaseEquals(str1, str2)
static int wherigo_NoCaseEquals(lua_State *L) {
  const char *str1 = luaL_checkstring(L, 1);
  const char *str2 = luaL_checkstring(L, 2);

  std::string s1(str1);
  std::string s2(str2);

  std::transform(s1.begin(), s1.end(), s1.begin(), ::tolower);
  std::transform(s2.begin(), s2.end(), s2.begin(), ::tolower);

  lua_pushboolean(L, s1 == s2);
  return 1;
}

// Wherigo.TranslatePoint(point, distance, bearing)
static int wherigo_TranslatePoint(lua_State *L) {
  // TODO: implement point translation
  lua_newtable(L);
  return 1;
}

// Wherigo.VectorToZone(point, zone)
static int wherigo_VectorToZone(lua_State *L) {
  lua_newtable(L);
  lua_pushnumber(L, 0);
  lua_setfield(L, -2, "distance");
  lua_pushnumber(L, 0);
  lua_setfield(L, -2, "bearing");
  lua_pushboolean(L, 0);
  lua_setfield(L, -2, "inside");
  return 1;
}

// Wherigo.VectorToPoint(point1, point2)
static int wherigo_VectorToPoint(lua_State *L) {
  lua_newtable(L);
  lua_pushnumber(L, 0);
  lua_setfield(L, -2, "distance");
  lua_pushnumber(L, 0);
  lua_setfield(L, -2, "bearing");
  return 1;
}

// Wherigo.VectorToSegment(point, point1, point2)
static int wherigo_VectorToSegment(lua_State *L) {
  lua_newtable(L);
  lua_pushnumber(L, 0);
  lua_setfield(L, -2, "distance");
  lua_pushnumber(L, 0);
  lua_setfield(L, -2, "bearing");
  return 1;
}

static const luaL_Reg wherigo_funcs[] = {
    {"ZonePoint", wherigo_ZonePoint},
    {"Distance", wherigo_Distance},
    {"Zone", wherigo_Zone},
    {"ZCharacter", wherigo_ZCharacter},
    {"ZItem", wherigo_ZItem},
    {"ZTask", wherigo_ZTask},
    {"ZTimer", wherigo_ZTimer},
    {"ZInput", wherigo_ZInput},
    {"ZMedia", wherigo_ZMedia},
    {"ZCartridge", wherigo_ZCartridge},
    {"ZCommand", wherigo_ZCommand},
    {"MessageBox", wherigo_MessageBox},
    {"Dialog", wherigo_Dialog},
    {"GetInput", wherigo_GetInput},
    {"PlayAudio", wherigo_PlayAudio},
    {"ShowScreen", wherigo_ShowScreen},
    {"LogMessage", wherigo_LogMessage},
    {"NoCaseEquals", wherigo_NoCaseEquals},
    {"TranslatePoint", wherigo_TranslatePoint},
    {"VectorToZone", wherigo_VectorToZone},
    {"VectorToPoint", wherigo_VectorToPoint},
    {"VectorToSegment", wherigo_VectorToSegment},
    {"RequestSync", wherigo_RequestSync},
    {nullptr, nullptr}};

int luaopen_Wherigo(lua_State *L) {
  luaL_register(L, "Wherigo", wherigo_funcs);

  // Create Player object
  wherigo_Player(L);
  lua_setfield(L, -2, "Player");

  // Screen constants
  lua_pushnumber(L, 0);
  lua_setfield(L, -2, "MAINSCREEN");

  lua_pushnumber(L, 1);
  lua_setfield(L, -2, "LOCATIONSCREEN");

  lua_pushnumber(L, 2);
  lua_setfield(L, -2, "ITEMSCREEN");

  lua_pushnumber(L, 3);
  lua_setfield(L, -2, "INVENTORYSCREEN");

  lua_pushnumber(L, 4);
  lua_setfield(L, -2, "TASKSCREEN");

  lua_pushnumber(L, 5);
  lua_setfield(L, -2, "DETAILSCREEN");

  // Register common functions as globals (as expected by Wherigo Builder)
  lua_pushcfunction(L, wherigo_ZonePoint);
  lua_setglobal(L, "ZonePoint");

  lua_pushcfunction(L, wherigo_Distance);
  lua_setglobal(L, "Distance");

  // Register Player as global
  wherigo_Player(L);
  lua_setglobal(L, "Player");

  return 1;
}

} // namespace wherigo
