#include "frontend/app/components/trace_viewer_v2/input_handler.h"

#include <emscripten/emscripten.h>
#include <emscripten/html5.h>

#include "absl/strings/string_view.h"
#include "imgui.h"
#include "frontend/app/components/trace_viewer_v2/scheduler.h"

namespace traceviewer {

namespace {

ImGuiKey TranslateKey(absl::string_view code) {
  if (code == "KeyA") return ImGuiKey_A;
  if (code == "KeyD") return ImGuiKey_D;
  if (code == "KeyE") return ImGuiKey_E;
  if (code == "KeyS") return ImGuiKey_S;
  if (code == "KeyW") return ImGuiKey_W;
  if (code == "Comma") return ImGuiKey_Comma;
  if (code == "KeyO") return ImGuiKey_O;
  if (code == "KeyG") return ImGuiKey_G;
  if (code == "KeyF") return ImGuiKey_F;
  if (code == "KeyM") return ImGuiKey_M;
  if (code == "ArrowDown") return ImGuiKey_DownArrow;
  if (code == "ArrowLeft") return ImGuiKey_LeftArrow;
  if (code == "ArrowRight") return ImGuiKey_RightArrow;
  if (code == "ArrowUp") return ImGuiKey_UpArrow;
  if (code == "Digit0") return ImGuiKey_0;
  if (code == "Digit1") return ImGuiKey_1;
  if (code == "Digit2") return ImGuiKey_2;
  if (code == "Digit3") return ImGuiKey_3;
  if (code == "Digit4") return ImGuiKey_4;
  if (code == "Escape") return ImGuiKey_Escape;
  if (code == "Tab") return ImGuiKey_Tab;
  return ImGuiKey_None;
}

void UpdateModifierKeys(const EmscriptenKeyboardEvent* event) {
  ImGuiIO& io = ImGui::GetIO();
    io.AddKeyEvent(ImGuiKey_ModCtrl, event->ctrlKey);
    io.AddKeyEvent(ImGuiKey_ModShift, event->shiftKey);
    io.AddKeyEvent(ImGuiKey_ModAlt, event->altKey);
    io.AddKeyEvent(ImGuiKey_ModSuper, event->metaKey);
}

}  // namespace

// Returns true if the currently active element is an input element.
int IsActiveElementInput() {
  return EM_ASM_INT({
    const activeElement = document.activeElement;
    if (!activeElement) return 0;
    const tagName = activeElement.tagName.toLowerCase();
    return tagName == 'input' || tagName == 'textarea' ||
           activeElement.isContentEditable;
  });
}

// Input Event Handlers
//
// Common behavior for all the following input handlers:
//
// 1. Redraw Requests:
//    All handlers unconditionally request a redraw for the next animation frame
//    via Scheduler::Instance().RequestRedraw(). Since drawing is asynchronous
//    (scheduled for the next frame), the input updates processed here will be
//    correctly applied before that frame is rendered.
//
// 2. Return Values (Event Propagation):
//    The return value (EM_BOOL) indicates whether the event was handled:
//    - EM_TRUE (true): The event was handled and should NOT be propagated.
//    - EM_FALSE (false): The event was not handled and SHOULD be propagated to
//      other listeners (e.g., browser default behavior).

EM_BOOL HandleKeyDown(int, const EmscriptenKeyboardEvent* event, void*) {
  Scheduler::Instance().RequestRedraw();
  UpdateModifierKeys(event);

  // If a native input element has focus, do not let ImGui capture the keyboard.
  if (IsActiveElementInput()) {
    return false;
  }

  // Always let the browser/Angular handle Slash for search box focus toggle.
  if (absl::string_view(event->code) == "Slash") {
    return false;
  }

  ImGuiKey key = TranslateKey(event->code);
  if (key != ImGuiKey_None) {
    ImGui::GetIO().AddKeyEvent(key, true);
  }
  return ImGui::GetIO().WantCaptureKeyboard;
}

EM_BOOL HandleKeyUp(int, const EmscriptenKeyboardEvent* event, void*) {
  Scheduler::Instance().RequestRedraw();
  UpdateModifierKeys(event);

  // If a native input element has focus, do not let ImGui capture the keyboard.
  if (IsActiveElementInput()) {
    return false;
  }

  // Always let the browser/Angular handle Slash for search box focus toggle.
  if (absl::string_view(event->code) == "Slash") {
    return false;
  }

  ImGuiKey key = TranslateKey(event->code);
  if (key != ImGuiKey_None) {
    ImGui::GetIO().AddKeyEvent(key, false);
  }
  return ImGui::GetIO().WantCaptureKeyboard;
}

EM_BOOL HandleMouseMove(int, const EmscriptenMouseEvent* event, void*) {
  Scheduler::Instance().RequestRedraw();
  ImGuiIO& io = ImGui::GetIO();
  io.AddMousePosEvent(event->targetX, event->targetY);
  return io.WantCaptureMouse;
}

EM_BOOL HandleMouseDown(int, const EmscriptenMouseEvent* event, void*) {
  Scheduler::Instance().RequestRedraw();
  ImGuiIO& io = ImGui::GetIO();
  io.AddMouseButtonEvent(event->button, true);
  return io.WantCaptureMouse;
}

EM_BOOL HandleMouseUp(int, const EmscriptenMouseEvent* event, void*) {
  Scheduler::Instance().RequestRedraw();
  ImGuiIO& io = ImGui::GetIO();
  io.AddMouseButtonEvent(event->button, false);
  return io.WantCaptureMouse;
}

EM_BOOL HandleWheel(int, const EmscriptenWheelEvent* event, void*) {
  Scheduler::Instance().RequestRedraw();
  ImGuiIO& io = ImGui::GetIO();

    io.AddKeyEvent(ImGuiKey_ModCtrl, event->mouse.ctrlKey);
    io.AddKeyEvent(ImGuiKey_ModShift, event->mouse.shiftKey);
    io.AddKeyEvent(ImGuiKey_ModSuper, event->mouse.metaKey);

  float wheel_x = static_cast<float>(event->deltaX);
  float wheel_y = static_cast<float>(event->deltaY);

  io.AddMouseWheelEvent(wheel_x, wheel_y);

  return io.WantCaptureMouse;
}

}  // namespace traceviewer
