#include "ui/map_sim_frame.h"
#include "lua/game_engine.h"

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

#include <wx/button.h>
#include <wx/log.h>
#include <wx/msgdlg.h>
#include <wx/sizer.h>
#include <wx/webview.h>

enum { ID_PlayBtn = wxID_HIGHEST + 100, ID_ClearBtn, ID_SimTimer };

wxBEGIN_EVENT_TABLE(MapSimFrame, wxFrame)
    EVT_BUTTON(ID_PlayBtn, MapSimFrame::OnPlay)
        EVT_BUTTON(ID_ClearBtn, MapSimFrame::OnClear)
            EVT_TIMER(ID_SimTimer, MapSimFrame::OnSimTimer) wxEND_EVENT_TABLE()

    // Escape a string for safe embedding inside a JS single-quoted string
    static wxString jsEscape(const std::string &s) {
  wxString out;
  for (const unsigned char c : s) {
    if (c == '\'')
      out += "\\'";
    else if (c == '\\')
      out += "\\\\";
    else if (c == '\n')
      out += "\\n";
    else if (c == '\r')
      out += "\\r";
    else
      out += static_cast<char>(c);
  }
  return out;
}

MapSimFrame::MapSimFrame(wxWindow *parent, double centerLat, double centerLon,
                         const std::vector<ZoneInfo> &zoneInfos)
    : wxFrame(parent, wxID_ANY, "GPS-Simulation", wxDefaultPosition,
              wxSize(900, 700)),
      m_simTimer(this, ID_SimTimer), m_zoneInfos(zoneInfos) {
  auto *sizer = new wxBoxSizer(wxVERTICAL);

  m_webView = wxWebView::New(this, wxID_ANY);
  sizer->Add(m_webView, 1, wxEXPAND);

  auto *btnSizer = new wxBoxSizer(wxHORIZONTAL);
  btnSizer->Add(new wxButton(this, ID_PlayBtn, "▶  Route abspielen"), 0, wxALL,
                5);
  btnSizer->Add(new wxButton(this, ID_ClearBtn, "✕  Wegpunkte löschen"), 0,
                wxALL, 5);
  sizer->Add(btnSizer, 0, wxALIGN_CENTER);
  SetSizer(sizer);

  // Register JS → C++ message handler (wxWidgets 3.1.5+)
  m_webView->AddScriptMessageHandler("wx");
  m_webView->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED,
                  &MapSimFrame::OnScriptMessage, this);
  m_webView->Bind(wxEVT_WEBVIEW_LOADED, &MapSimFrame::OnWebViewLoaded, this);

  wherigo::GameEngine::getInstance().Bind(
      wherigo::EVT_GAME_STATE_CHANGED, &MapSimFrame::OnGameStateChanged, this);

  Bind(wxEVT_CLOSE_WINDOW, &MapSimFrame::OnClose, this);

  // ── Build Leaflet page ───────────────────────────────────────────────────
  wxString html;
  html << "<!DOCTYPE html><html><head>"
          "<meta charset='utf-8'><title>GPS-Sim</title>"
          "<link rel='stylesheet' "
          "href='https://unpkg.com/leaflet/dist/leaflet.css'/>"
          "<link rel='stylesheet' "
          "href='https://unpkg.com/leaflet.markercluster/dist/"
          "MarkerCluster.css'/>"
          "<link rel='stylesheet' "
          "href='https://unpkg.com/leaflet.markercluster/dist/"
          "MarkerCluster.Default.css'/>"
          "<script src='https://unpkg.com/leaflet/dist/leaflet.js'></script>"
          "<script "
          "src='https://unpkg.com/leaflet.markercluster/dist/"
          "leaflet.markercluster.js'></script>"
          "<style>"
          "  html,body,#map{height:100%;margin:0;padding:0;}"
          "  .zone-dot{"
          "    width:14px;height:14px;border-radius:50%;"
          "    background:#e44;border:2px solid #900;"
          "    box-shadow:0 1px 3px rgba(0,0,0,.4);"
          "  }"
          "  .zone-cluster{"
          "    background:#e44;border:3px solid #900;border-radius:50%;"
          "    color:#fff;font-weight:bold;font-size:13px;"
          "    display:flex;align-items:center;justify-content:center;"
          "    box-shadow:0 2px 5px rgba(0,0,0,.4);"
          "  }"
          "  .zone-label{"
          "    background:transparent;border:none;font-weight:bold;"
          "    font-size:12px;color:#900;"
          "    text-shadow:0 0 3px #fff,0 0 3px #fff,0 0 3px #fff;"
          "    white-space:nowrap;pointer-events:none;"
          "  }"
          "</style>"
          "</head><body>"
          "<div id='map' style='height:calc(100vh - 50px);'></div>"
          "<script>"
       << wxString::Format("var map=L.map('map').setView([%.8f,%.8f],16);",
                           centerLat, centerLon)
       << "L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',"
          "  {maxZoom:19,attribution:'&copy; OpenStreetMap'}).addTo(map);"
          "var routeMarkers=[];"
          "var routeLine=null;"

          // Zone cluster group with custom cluster icon
          "var zoneCluster=L.markerClusterGroup({"
          "  maxClusterRadius:60,"
          "  iconCreateFunction:function(c){"
          "    var n=c.getChildCount();"
          "    var s=n<10?32:n<100?38:44;"
          "    return L.divIcon({"
          "      html:'<div class=\"zone-cluster\" "
          "style=\"width:'+s+'px;height:'+s+'px;\">'+n+'</div>',"
          "      className:'',"
          "      iconSize:[s,s],iconAnchor:[s/2,s/2]"
          "    });"
          "  }"
          "});";

  html << "map.addLayer(zoneCluster);"

          // Dynamic zone sync: adds new active zones, removes deactivated ones
          "var zoneMarkerMap={};"
          "function syncZones(zones){"
          "  var active={};"
          "  zones.forEach(function(z){"
          "    active[z.name]=true;"
          "    if(!zoneMarkerMap[z.name]){"
          "      var zm=L.marker([z.lat,z.lon],{"
          "        icon:L.divIcon({"
          "          html:'<div class=\"zone-dot\"></div>',"
          "          className:'',"
          "          iconSize:[14,14],iconAnchor:[7,7]"
          "        })"
          "      });"
          "      zm.bindTooltip(z.name,{"
          "        permanent:true,direction:'top',"
          "        offset:[0,-10],className:'zone-label'"
          "      });"
          "      zoneCluster.addLayer(zm);"
          "      zoneMarkerMap[z.name]=zm;"
          "    }"
          "  });"
          "  Object.keys(zoneMarkerMap).forEach(function(name){"
          "    if(!active[name]){"
          "      zoneCluster.removeLayer(zoneMarkerMap[name]);"
          "      delete zoneMarkerMap[name];"
          "    }"
          "  });"
          "}"

          // Player position marker (hidden until first position update)
          "var playerMarker=null;"
          "function updatePlayerPos(lat,lon){"
          "  if(!playerMarker){"
          "    playerMarker=L.marker([lat,lon],{"
          "      icon:L.divIcon({"
          "        html:'<div style=\"width:18px;height:18px;border-radius:50%;"
          "              background:#0077cc;border:3px solid #fff;"
          "              box-shadow:0 0 0 2px #0077cc,0 2px 6px "
          "rgba(0,0,0,.5);\"></div>',"
          "        className:'',"
          "        iconSize:[18,18],iconAnchor:[9,9]"
          "      })"
          "    }).addTo(map);"
          "    "
          "playerMarker.bindTooltip('Spieler',{permanent:false,direction:'top'}"
          ");"
          "  } else {"
          "    playerMarker.setLatLng([lat,lon]);"
          "  }"
          "  map.panTo([lat,lon]);"
          "}"

          // Click handler: add numbered route waypoint (NOT in cluster)
          "function updateLine(){"
          "  if(routeLine){map.removeLayer(routeLine);routeLine=null;}"
          "  if(routeMarkers.length>1){"
          "    var lls=routeMarkers.map(function(m){return m.getLatLng();});"
          "    "
          "routeLine=L.polyline(lls,{color:'#0077cc',weight:2,dashArray:'6,4'})"
          ".addTo(map);"
          "  }"
          "}"
          "map.on('click',function(e){"
          "  var n=routeMarkers.length+1;"
          "  var m=L.marker(e.latlng,{"
          "    icon:L.divIcon({"
          "      html:'<div "
          "style=\"background:#0077cc;color:#fff;border-radius:50%;width:20px;"
          "            "
          "height:20px;line-height:20px;text-align:center;font-size:11px;"
          "            font-weight:bold;border:2px solid "
          "#005299;\">'+n+'</div>',"
          "      className:'',"
          "      iconSize:[20,20],iconAnchor:[10,10]"
          "    })"
          "  }).addTo(map);"
          "  routeMarkers.push(m);"
          "  updateLine();"
          "  "
          "window.wx.postMessage(JSON.stringify({lat:e.latlng.lat,lon:e.latlng."
          "lng}));"
          "});"
          "</script></body></html>";

  m_webView->SetPage(html, "");
}

// ── JS → C++ message
// ──────────────────────────────────────────────────────────

static bool parseLatLon(const wxString &json, double &lat, double &lon) {
  auto extractNum = [&](const wxString &key, double &val) -> bool {
    int pos = json.Find(key);
    if (pos == wxNOT_FOUND)
      return false;
    pos += static_cast<int>(key.length());
    while (pos < static_cast<int>(json.length()) &&
           (json[pos] == ' ' || json[pos] == ':'))
      ++pos;
    const int start = pos;
    while (pos < static_cast<int>(json.length()) &&
           (wxIsdigit(json[pos]) || json[pos] == '.' || json[pos] == '-'))
      ++pos;
    return json.Mid(start, pos - start).ToDouble(&val);
  };
  return extractNum("\"lat\"", lat) && extractNum("\"lon\"", lon);
}

void MapSimFrame::OnScriptMessage(wxWebViewEvent &event) {
  double lat = 0, lon = 0;
  if (parseLatLon(event.GetString(), lat, lon)) {
    AddSimPoint(lat, lon);
    wxLogDebug("MapSim: waypoint %zu added (%.6f, %.6f)", m_route.size(), lat,
               lon);
  }
}

// ── Route management
// ──────────────────────────────────────────────────────────

void MapSimFrame::AddSimPoint(double lat, double lon) {
  m_route.push_back({lat, lon});
}

void MapSimFrame::StartSimulation() {
  if (m_route.empty())
    return;
  m_simIndex = 0;
  m_simTimer.Start(500);
}

// ── Button handlers
// ───────────────────────────────────────────────────────────

void MapSimFrame::OnPlay(wxCommandEvent &) {
  if (m_route.empty()) {
    wxMessageBox("Bitte zuerst Wegpunkte auf der Karte setzen.", "Hinweis",
                 wxOK | wxICON_INFORMATION);
    return;
  }
  m_simIndex = 0;
  m_simTimer.Start(500);
  wxLogDebug("MapSim: starting playback of %zu waypoints", m_route.size());
}

void MapSimFrame::OnClear(wxCommandEvent &) {
  m_simTimer.Stop();
  m_route.clear();
  m_simIndex = 0;
  // Remove all route markers and the connecting line from the map
  m_webView->RunScript(
      "routeMarkers.forEach(function(m){map.removeLayer(m);});"
      "routeMarkers=[];"
      "if(routeLine){map.removeLayer(routeLine);routeLine=null;}",
      nullptr);
  wxLogDebug("MapSim: route cleared");
}

void MapSimFrame::OnSimTimer(wxTimerEvent &) {
  if (m_simIndex >= m_route.size()) {
    wxCommandEvent dummy;
    OnClear(dummy);
    return;
  }
  const auto &pt = m_route[m_simIndex++];
  SendPositionToEngine(pt.lat, pt.lon);
}

// ── Zone sync
// ─────────────────────────────────────────────────────────────────

void MapSimFrame::syncZonesOnMap() {
  if (!m_pageLoaded)
    return;
  lua_State *L = wherigo::GameEngine::getInstance().getLuaState();
  if (!L)
    return;

  wxString js = "syncZones([";
  bool first = true;

  lua_getglobal(L, "_G");
  lua_pushnil(L);
  while (lua_next(L, -2) != 0) {
    if (lua_istable(L, -1)) {
      lua_getfield(L, -1, "ClassName");
      const bool isZone =
          lua_isstring(L, -1) && !strcmp(lua_tostring(L, -1), "Zone");
      lua_pop(L, 1);
      if (isZone) {
        lua_getfield(L, -1, "Active");
        const bool active = lua_toboolean(L, -1);
        lua_pop(L, 1);
        if (active) {
          lua_getfield(L, -1, "Name");
          const std::string name =
              lua_isstring(L, -1) ? lua_tostring(L, -1) : "";
          lua_pop(L, 1);

          lua_getfield(L, -1, "OriginalPoint");
          if (lua_istable(L, -1)) {
            lua_getfield(L, -1, "latitude");
            lua_getfield(L, -2, "longitude");
            const double lat = lua_tonumber(L, -2);
            const double lon = lua_tonumber(L, -1);
            lua_pop(L, 2);

            if (!first)
              js += ",";
            js += wxString::Format("{name:'%s',lat:%.8f,lon:%.8f}",
                                   jsEscape(name), lat, lon);
            first = false;
          }
          lua_pop(L, 1); // pop OriginalPoint
        }
      }
    }
    lua_pop(L, 1);
  }
  lua_pop(L, 1); // pop _G

  js += "]);";
  m_webView->RunScript(js, nullptr);
}

void MapSimFrame::OnWebViewLoaded(wxWebViewEvent &) {
  m_pageLoaded = true;
  syncZonesOnMap();
}

void MapSimFrame::OnGameStateChanged(wxEvent &) { syncZonesOnMap(); }

void MapSimFrame::OnClose(wxCloseEvent &event) {
  wherigo::GameEngine::getInstance().Unbind(
      wherigo::EVT_GAME_STATE_CHANGED, &MapSimFrame::OnGameStateChanged, this);
  event.Skip();
}

// ── Engine integration
// ────────────────────────────────────────────────────────

void MapSimFrame::SendPositionToEngine(double lat, double lon) {
  wxLogDebug("MapSim: position → engine (%.6f, %.6f)", lat, lon);
  wherigo::GameEngine::getInstance().updatePlayerPosition(lat, lon, 0.0);

  m_webView->RunScript(
      wxString::Format("updatePlayerPos(%.8f,%.8f);", lat, lon), nullptr);
}
