#include "qjs_sdl_input.h"
#include "jsffi.h"
#include "qjs_macros.h"
#include "cell.h"
#include "qjs_blob.h"
#include "stb_ds.h"
#include "qjs_actor.h"
#include "qjs_wota.h"
#include "qjs_imgui.h"

#include <SDL3/SDL.h>

// Internal keymod function for input module
static JSValue js_keymod(JSContext *js)
{
  SDL_Keymod modstate = SDL_GetModState();
  JSValue ret = JS_NewObject(js);
  if (SDL_KMOD_CTRL & modstate)
    JS_SetPropertyStr(js,ret,"ctrl", JS_NewBool(js,1));
  if (SDL_KMOD_SHIFT & modstate)
    JS_SetPropertyStr(js,ret,"shift", JS_NewBool(js,1));
  if (SDL_KMOD_ALT & modstate)
    JS_SetPropertyStr(js,ret,"alt", JS_NewBool(js,1));
  if (SDL_KMOD_GUI & modstate)
    JS_SetPropertyStr(js,ret,"super", JS_NewBool(js,1));
  if (SDL_KMOD_NUM & modstate)
    JS_SetPropertyStr(js,ret,"numlock", JS_NewBool(js,1));
  if (SDL_KMOD_CAPS & modstate)
    JS_SetPropertyStr(js,ret,"caps", JS_NewBool(js,1));
  if (SDL_KMOD_SCROLL & modstate)
    JS_SetPropertyStr(js,ret,"scrolllock", JS_NewBool(js,1));
  if (SDL_KMOD_MODE & modstate)
    JS_SetPropertyStr(js,ret,"mode", JS_NewBool(js,1));

  return ret;
}

// INPUT FUNCTIONS
JSC_CCALL(input_mouse_lock, SDL_CaptureMouse(JS_ToBool(js,argv[0])))

JSC_CCALL(input_mouse_show,
  if (JS_ToBool(js,argv[0]))
    SDL_ShowCursor();
  else
    SDL_HideCursor();
)

JSC_CCALL(input_keyname,
  return JS_NewString(js, SDL_GetKeyName(js2number(js,argv[0])));
)

JSC_CCALL(input_keymod,
  return js_keymod(js);
)

JSC_CCALL(input_mousestate,
  float x,y;
  SDL_MouseButtonFlags flags = SDL_GetMouseState(&x,&y);
  JSValue m = JS_NewObject(js);
  JS_SetPropertyStr(js,m,"x", number2js(js,x));
  JS_SetPropertyStr(js,m,"y", number2js(js,y));

  if (flags & SDL_BUTTON_LMASK)
    JS_SetPropertyStr(js, m, "left", JS_NewBool(js, 1));
  if (flags & SDL_BUTTON_MMASK)
    JS_SetPropertyStr(js, m, "middle", JS_NewBool(js, 1));
  if (flags & SDL_BUTTON_RMASK)
    JS_SetPropertyStr(js, m, "right", JS_NewBool(js, 1));
  if (flags & SDL_BUTTON_X1MASK)
    JS_SetPropertyStr(js, m, "x1", JS_NewBool(js, 1));
  if (flags & SDL_BUTTON_X2MASK)
    JS_SetPropertyStr(js, m, "x2", JS_NewBool(js, 1));
    
  return m;
)

// Event processing functions (moved from cell.c)

const char* event_type_to_string(Uint32 event_type) {
  switch (event_type) {
    // Application events
    case SDL_EVENT_QUIT: return "quit";
    case SDL_EVENT_TERMINATING: return "terminating";
    case SDL_EVENT_LOW_MEMORY: return "low_memory";
    case SDL_EVENT_WILL_ENTER_BACKGROUND: return "will_enter_background";
    case SDL_EVENT_DID_ENTER_BACKGROUND: return "did_enter_background";
    case SDL_EVENT_WILL_ENTER_FOREGROUND: return "will_enter_foreground";
    case SDL_EVENT_DID_ENTER_FOREGROUND: return "did_enter_foreground";
    case SDL_EVENT_LOCALE_CHANGED: return "locale_changed";
    case SDL_EVENT_SYSTEM_THEME_CHANGED: return "system_theme_changed";

    // Display events
    case SDL_EVENT_DISPLAY_ORIENTATION: return "display_orientation";
    case SDL_EVENT_DISPLAY_ADDED: return "display_added";
    case SDL_EVENT_DISPLAY_REMOVED: return "display_removed";
    case SDL_EVENT_DISPLAY_MOVED: return "display_moved";
    case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED: return "display_desktop_mode_changed";
    case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED: return "display_current_mode_changed";
    case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: return "display_content_scale_changed";

    // Window events
    case SDL_EVENT_WINDOW_SHOWN: return "window_shown";
    case SDL_EVENT_WINDOW_HIDDEN: return "window_hidden";
    case SDL_EVENT_WINDOW_EXPOSED: return "window_exposed";
    case SDL_EVENT_WINDOW_MOVED: return "window_moved";
    case SDL_EVENT_WINDOW_RESIZED: return "window_resized";
    case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: return "window_pixel_size_changed";
    case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED: return "window_metal_view_resized";
    case SDL_EVENT_WINDOW_MINIMIZED: return "window_minimized";
    case SDL_EVENT_WINDOW_MAXIMIZED: return "window_maximized";
    case SDL_EVENT_WINDOW_RESTORED: return "window_restored";
    case SDL_EVENT_WINDOW_MOUSE_ENTER: return "window_mouse_enter";
    case SDL_EVENT_WINDOW_MOUSE_LEAVE: return "window_mouse_leave";
    case SDL_EVENT_WINDOW_FOCUS_GAINED: return "window_focus_gained";
    case SDL_EVENT_WINDOW_FOCUS_LOST: return "window_focus_lost";
    case SDL_EVENT_WINDOW_CLOSE_REQUESTED: return "window_close_requested";
    case SDL_EVENT_WINDOW_HIT_TEST: return "window_hit_test";
    case SDL_EVENT_WINDOW_ICCPROF_CHANGED: return "window_iccprof_changed";
    case SDL_EVENT_WINDOW_DISPLAY_CHANGED: return "window_display_changed";
    case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: return "window_display_scale_changed";
    case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: return "window_safe_area_changed";
    case SDL_EVENT_WINDOW_OCCLUDED: return "window_occluded";
    case SDL_EVENT_WINDOW_ENTER_FULLSCREEN: return "window_enter_fullscreen";
    case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: return "window_leave_fullscreen";
    case SDL_EVENT_WINDOW_DESTROYED: return "window_destroyed";
    case SDL_EVENT_WINDOW_HDR_STATE_CHANGED: return "window_hdr_state_changed";

    // Keyboard events
    case SDL_EVENT_KEY_DOWN: return "key_down";
    case SDL_EVENT_KEY_UP: return "key_up";
    case SDL_EVENT_TEXT_EDITING: return "text_editing";
    case SDL_EVENT_TEXT_INPUT: return "text_input";
    case SDL_EVENT_KEYMAP_CHANGED: return "keymap_changed";
    case SDL_EVENT_KEYBOARD_ADDED: return "keyboard_added";
    case SDL_EVENT_KEYBOARD_REMOVED: return "keyboard_removed";
    case SDL_EVENT_TEXT_EDITING_CANDIDATES: return "text_editing_candidates";

    // Mouse events
    case SDL_EVENT_MOUSE_MOTION: return "mouse_motion";
    case SDL_EVENT_MOUSE_BUTTON_DOWN: return "mouse_button_down";
    case SDL_EVENT_MOUSE_BUTTON_UP: return "mouse_button_up";
    case SDL_EVENT_MOUSE_WHEEL: return "mouse_wheel";
    case SDL_EVENT_MOUSE_ADDED: return "mouse_added";
    case SDL_EVENT_MOUSE_REMOVED: return "mouse_removed";

    // Joystick events
    case SDL_EVENT_JOYSTICK_AXIS_MOTION: return "joystick_axis_motion";
    case SDL_EVENT_JOYSTICK_BALL_MOTION: return "joystick_ball_motion";
    case SDL_EVENT_JOYSTICK_HAT_MOTION: return "joystick_hat_motion";
    case SDL_EVENT_JOYSTICK_BUTTON_DOWN: return "joystick_button_down";
    case SDL_EVENT_JOYSTICK_BUTTON_UP: return "joystick_button_up";
    case SDL_EVENT_JOYSTICK_ADDED: return "joystick_added";
    case SDL_EVENT_JOYSTICK_REMOVED: return "joystick_removed";
    case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: return "joystick_battery_updated";
    case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE: return "joystick_update_complete";

    // Gamepad events
    case SDL_EVENT_GAMEPAD_AXIS_MOTION: return "gamepad_axis_motion";
    case SDL_EVENT_GAMEPAD_BUTTON_DOWN: return "gamepad_button_down";
    case SDL_EVENT_GAMEPAD_BUTTON_UP: return "gamepad_button_up";
    case SDL_EVENT_GAMEPAD_ADDED: return "gamepad_added";
    case SDL_EVENT_GAMEPAD_REMOVED: return "gamepad_removed";
    case SDL_EVENT_GAMEPAD_REMAPPED: return "gamepad_remapped";
    case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: return "gamepad_touchpad_down";
    case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: return "gamepad_touchpad_motion";
    case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: return "gamepad_touchpad_up";
    case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: return "gamepad_sensor_update";
    case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE: return "gamepad_update_complete";
    case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: return "gamepad_steam_handle_updated";

    // Touch events
    case SDL_EVENT_FINGER_DOWN: return "finger_down";
    case SDL_EVENT_FINGER_UP: return "finger_up";
    case SDL_EVENT_FINGER_MOTION: return "finger_motion";

    // Clipboard events
    case SDL_EVENT_CLIPBOARD_UPDATE: return "clipboard_update";

    // Drag and drop events
    case SDL_EVENT_DROP_FILE: return "drop_file";
    case SDL_EVENT_DROP_TEXT: return "drop_text";
    case SDL_EVENT_DROP_BEGIN: return "drop_begin";
    case SDL_EVENT_DROP_COMPLETE: return "drop_complete";
    case SDL_EVENT_DROP_POSITION: return "drop_position";

    // Audio device events
    case SDL_EVENT_AUDIO_DEVICE_ADDED: return "audio_device_added";
    case SDL_EVENT_AUDIO_DEVICE_REMOVED: return "audio_device_removed";
    case SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED: return "audio_device_format_changed";

    // Sensor events
    case SDL_EVENT_SENSOR_UPDATE: return "sensor_update";

    // Pen events
    case SDL_EVENT_PEN_PROXIMITY_IN: return "pen_proximity_in";
    case SDL_EVENT_PEN_PROXIMITY_OUT: return "pen_proximity_out";
    case SDL_EVENT_PEN_DOWN: return "pen_down";
    case SDL_EVENT_PEN_UP: return "pen_up";
    case SDL_EVENT_PEN_BUTTON_DOWN: return "pen_button_down";
    case SDL_EVENT_PEN_BUTTON_UP: return "pen_button_up";
    case SDL_EVENT_PEN_MOTION: return "pen_motion";
    case SDL_EVENT_PEN_AXIS: return "pen_axis";

    // Camera events
    case SDL_EVENT_CAMERA_DEVICE_ADDED: return "camera_device_added";
    case SDL_EVENT_CAMERA_DEVICE_REMOVED: return "camera_device_removed";
    case SDL_EVENT_CAMERA_DEVICE_APPROVED: return "camera_device_approved";
    case SDL_EVENT_CAMERA_DEVICE_DENIED: return "camera_device_denied";

    // Render events
    case SDL_EVENT_RENDER_TARGETS_RESET: return "render_targets_reset";
    case SDL_EVENT_RENDER_DEVICE_RESET: return "render_device_reset";
    case SDL_EVENT_RENDER_DEVICE_LOST: return "render_device_lost";

    // User event (assuming it should be included)
    case SDL_EVENT_USER: return "user";

    default: return "unknown";
  }
}

const char* mouse_button_to_string(int mouse) {
  switch (mouse) {
    case SDL_BUTTON_LEFT: return "left";
    case SDL_BUTTON_MIDDLE: return "middle";
    case SDL_BUTTON_RIGHT: return "right";
    case SDL_BUTTON_X1: return "x1";
    case SDL_BUTTON_X2: return "x2";
    default: return "left";
  }
}

static void wota_write_vec2(WotaBuffer *wb, double x, double y) {
    // We'll store as WOTA_ARR of length 2, then two numbers
    wota_write_array(wb, 2);
    wota_write_number(wb, x);
    wota_write_number(wb, y);
}

static int event2wota_count_props(const SDL_Event *event)
{
    // We always store at least "type" and "timestamp".
    int count = 2;

    switch (event->type) {

    case SDL_EVENT_AUDIO_DEVICE_ADDED:
    case SDL_EVENT_AUDIO_DEVICE_REMOVED:
        count += 2; // which, recording
        break;

    case SDL_EVENT_DISPLAY_ORIENTATION:
    case SDL_EVENT_DISPLAY_ADDED:
    case SDL_EVENT_DISPLAY_REMOVED:
    case SDL_EVENT_DISPLAY_MOVED:
    case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
    case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
    case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
        count += 3; // which, orientation/data1, data2
        break;

    case SDL_EVENT_MOUSE_MOTION:
        count += 5;
        break;

    case SDL_EVENT_MOUSE_WHEEL:
        // window, which, scroll, mouse => 4 extra
        count += 4;
        break;

    case SDL_EVENT_MOUSE_BUTTON_UP:
    case SDL_EVENT_MOUSE_BUTTON_DOWN:
        // window, which, down, button, clicks, mouse => 6 extra
        count += 6;
        break;

    case SDL_EVENT_SENSOR_UPDATE:
        // which, sensor_timestamp => 2 extra
        count += 2;
        break;

    case SDL_EVENT_KEY_DOWN:
    case SDL_EVENT_KEY_UP:
        // window, which, down, repeat, key, scancode, mod => 7 extra
        count += 7;
        break;

    case SDL_EVENT_FINGER_MOTION:
    case SDL_EVENT_FINGER_DOWN:
    case SDL_EVENT_FINGER_UP:
        // touch, finger, pos, d_pos, pressure, window => 6 extra
        count += 6;
        break;

    case SDL_EVENT_DROP_BEGIN:
    case SDL_EVENT_DROP_FILE:
    case SDL_EVENT_DROP_TEXT:
    case SDL_EVENT_DROP_COMPLETE:
    case SDL_EVENT_DROP_POSITION:
        // window, pos, data, source => 4 extra
        count += 4;
        break;

    case SDL_EVENT_TEXT_INPUT:
        // window, text, mod => 3 extra
        count += 3;
        break;

    case SDL_EVENT_CAMERA_DEVICE_APPROVED:
    case SDL_EVENT_CAMERA_DEVICE_REMOVED:
    case SDL_EVENT_CAMERA_DEVICE_ADDED:
    case SDL_EVENT_CAMERA_DEVICE_DENIED:
        // which => 1 extra
        count += 1;
        break;

    case SDL_EVENT_CLIPBOARD_UPDATE:
        // owner => 1 extra
        count += 1;
        break;

    /* Window events that only need 'which' */
    case SDL_EVENT_WINDOW_EXPOSED:
    case SDL_EVENT_WINDOW_FOCUS_GAINED:
    case SDL_EVENT_WINDOW_FOCUS_LOST:
    case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
        // which => 1 extra
        count += 1;
        break;

    /* Window events that need data1 and data2 */
    case SDL_EVENT_WINDOW_SHOWN:
    case SDL_EVENT_WINDOW_HIDDEN:
    case SDL_EVENT_WINDOW_MOVED:
    case SDL_EVENT_WINDOW_RESIZED:
    case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
    case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED:
    case SDL_EVENT_WINDOW_MINIMIZED:
    case SDL_EVENT_WINDOW_MAXIMIZED:
    case SDL_EVENT_WINDOW_RESTORED:
    case SDL_EVENT_WINDOW_MOUSE_ENTER:
    case SDL_EVENT_WINDOW_MOUSE_LEAVE:
    case SDL_EVENT_WINDOW_HIT_TEST:
    case SDL_EVENT_WINDOW_ICCPROF_CHANGED:
    case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
    case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
    case SDL_EVENT_WINDOW_OCCLUDED:
    case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
    case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
    case SDL_EVENT_WINDOW_DESTROYED:
    case SDL_EVENT_WINDOW_HDR_STATE_CHANGED:
        // which, x/width/display_index, y/height => 3 extra
        count += 3;
        break;

    case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED:
        // which, x, y, width, height => 5 extra
        count += 5;
        break;

    case SDL_EVENT_JOYSTICK_ADDED:
    case SDL_EVENT_JOYSTICK_REMOVED:
    case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE:
        // which => 1 extra
        count += 1;
        break;

    case SDL_EVENT_JOYSTICK_AXIS_MOTION:
        // which, axis, value => 3 extra
        count += 3;
        break;

    case SDL_EVENT_JOYSTICK_BALL_MOTION:
        // which, ball, rel => 3 extra
        count += 3;
        break;

    case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
    case SDL_EVENT_JOYSTICK_BUTTON_UP:
        // which, button, down => 3 extra
        count += 3;
        break;

    case SDL_EVENT_GAMEPAD_ADDED:
    case SDL_EVENT_GAMEPAD_REMOVED:
    case SDL_EVENT_GAMEPAD_REMAPPED:
    case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE:
    case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED:
        // which => 1 extra
        count += 1;
        break;

    case SDL_EVENT_GAMEPAD_AXIS_MOTION:
        // which, axis, value => 3 extra
        count += 3;
        break;

    case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
    case SDL_EVENT_GAMEPAD_BUTTON_UP:
        // which, button, down => 3 extra
        count += 3;
        break;

    case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
    case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
    case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
        // which, touchpad, finger, pos, pressure => 5 extra
        count += 5;
        break;

    case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
        // which, sensor, sensor_timestamp => 3 extra
        count += 3;
        break;

    case SDL_EVENT_USER:
        // cb => 1 extra
        count += 1;
        break;
    }

    return count;
}

static void event2wota_write(WotaBuffer *wb, const SDL_Event *e, int c) {
    wota_write_record(wb, (unsigned long long)c);
    wota_write_text(wb, "type");
    wota_write_text(wb, event_type_to_string(e->type));
    wota_write_text(wb, "timestamp");
    wota_write_number(wb, (double)e->common.timestamp);
    switch(e->type) {
    case SDL_EVENT_AUDIO_DEVICE_ADDED:
    case SDL_EVENT_AUDIO_DEVICE_REMOVED:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->adevice.which);
        wota_write_text(wb, "recording");
        wota_write_sym(wb, e->adevice.recording ? WOTA_TRUE : WOTA_FALSE);
        break;
    case SDL_EVENT_DISPLAY_ORIENTATION:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->display.displayID);
        wota_write_text(wb, "orientation");
        wota_write_number(wb, (double)e->display.data1);
        wota_write_text(wb, "data2");
        wota_write_number(wb, (double)e->display.data2);
        break;
    case SDL_EVENT_DISPLAY_ADDED:
    case SDL_EVENT_DISPLAY_REMOVED:
    case SDL_EVENT_DISPLAY_MOVED:
    case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
    case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
    case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->display.displayID);
        wota_write_text(wb, "data1");
        wota_write_number(wb, (double)e->display.data1);
        wota_write_text(wb, "data2");
        wota_write_number(wb, (double)e->display.data2);
        break;
    case SDL_EVENT_MOUSE_MOTION:
        wota_write_text(wb, "window");
        wota_write_number(wb, (double)e->motion.windowID);
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->motion.which);
        wota_write_text(wb, "state");
        wota_write_number(wb, (double)e->motion.state);
        wota_write_text(wb, "pos");
        wota_write_vec2(wb, (double)e->motion.x, (double)e->motion.y);
        wota_write_text(wb, "d_pos");
        wota_write_vec2(wb, (double)e->motion.xrel, (double)e->motion.yrel);
        break;
    case SDL_EVENT_MOUSE_WHEEL:
        wota_write_text(wb, "window");
        wota_write_number(wb, (double)e->wheel.windowID);
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->wheel.which);
        wota_write_text(wb, "scroll");
        wota_write_vec2(wb, (double)e->wheel.x, (double)e->wheel.y);
        wota_write_text(wb, "pos");
        wota_write_vec2(wb, (double)e->wheel.mouse_x, (double)e->wheel.mouse_y);
        break;
    case SDL_EVENT_MOUSE_BUTTON_UP:
    case SDL_EVENT_MOUSE_BUTTON_DOWN:
        wota_write_text(wb, "window");
        wota_write_number(wb, (double)e->button.windowID);
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->button.which);
        wota_write_text(wb, "down");
        wota_write_sym(wb, e->button.down ? WOTA_TRUE : WOTA_FALSE);
        wota_write_text(wb, "button");
        wota_write_text(wb, mouse_button_to_string(e->button.button));
        wota_write_text(wb, "clicks");
        wota_write_number(wb, (double)e->button.clicks);
        wota_write_text(wb, "pos");
        wota_write_vec2(wb, (double)e->button.x, (double)e->button.y);
        break;
    case SDL_EVENT_SENSOR_UPDATE:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->sensor.which);
        wota_write_text(wb, "sensor_timestamp");
        wota_write_number(wb, (double)e->sensor.sensor_timestamp);
        break;
    case SDL_EVENT_KEY_DOWN:
    case SDL_EVENT_KEY_UP:
        wota_write_text(wb, "window");
        wota_write_number(wb, (double)e->key.windowID);
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->key.which);
        wota_write_text(wb, "down");
        wota_write_sym(wb, e->key.down ? WOTA_TRUE : WOTA_FALSE);
        wota_write_text(wb, "repeat");
        wota_write_sym(wb, e->key.repeat ? WOTA_TRUE : WOTA_FALSE);
        wota_write_text(wb, "key");
        wota_write_number(wb, (double)e->key.key);
        wota_write_text(wb, "scancode");
        wota_write_number(wb, (double)e->key.scancode);
        wota_write_text(wb, "mod");
        wota_write_number(wb, (double)e->key.mod);
        break;
    case SDL_EVENT_FINGER_MOTION:
    case SDL_EVENT_FINGER_DOWN:
    case SDL_EVENT_FINGER_UP:
        wota_write_text(wb, "touch");
        wota_write_number(wb, (double)e->tfinger.touchID);
        wota_write_text(wb, "finger");
        wota_write_number(wb, (double)e->tfinger.fingerID);
        wota_write_text(wb, "pos");
        wota_write_vec2(wb, (double)e->tfinger.x, (double)e->tfinger.y);
        wota_write_text(wb, "d_pos");
        wota_write_vec2(wb, (double)e->tfinger.dx, (double)e->tfinger.dy);
        wota_write_text(wb, "pressure");
        wota_write_number(wb, (double)e->tfinger.pressure);
        wota_write_text(wb, "window");
        wota_write_number(wb, (double)e->key.windowID);
        break;
    case SDL_EVENT_DROP_BEGIN:
    case SDL_EVENT_DROP_FILE:
    case SDL_EVENT_DROP_TEXT:
    case SDL_EVENT_DROP_COMPLETE:
    case SDL_EVENT_DROP_POSITION:
        wota_write_text(wb, "window");
        wota_write_number(wb, (double)e->drop.windowID);
        wota_write_text(wb, "pos");
        wota_write_vec2(wb, (double)e->drop.x, (double)e->drop.y);
        wota_write_text(wb, "data");
        wota_write_text(wb, e->drop.data ? e->drop.data : "");
        wota_write_text(wb, "source");
        wota_write_text(wb, e->drop.source ? e->drop.source : "");
        break;
    case SDL_EVENT_TEXT_INPUT:
        wota_write_text(wb, "window");
        wota_write_number(wb, (double)e->text.windowID);
        wota_write_text(wb, "text");
        wota_write_text(wb, e->text.text);
        wota_write_text(wb, "mod");
        wota_write_number(wb, 0);
        break;
    case SDL_EVENT_CAMERA_DEVICE_APPROVED:
    case SDL_EVENT_CAMERA_DEVICE_REMOVED:
    case SDL_EVENT_CAMERA_DEVICE_ADDED:
    case SDL_EVENT_CAMERA_DEVICE_DENIED:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->cdevice.which);
        break;
    case SDL_EVENT_CLIPBOARD_UPDATE:
        wota_write_text(wb, "owner");
        wota_write_sym(wb, e->clipboard.owner ? WOTA_TRUE : WOTA_FALSE);
        break;
    case SDL_EVENT_WINDOW_EXPOSED:
    case SDL_EVENT_WINDOW_FOCUS_GAINED:
    case SDL_EVENT_WINDOW_FOCUS_LOST:
    case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->window.windowID);
        break;
    case SDL_EVENT_WINDOW_SHOWN:
    case SDL_EVENT_WINDOW_HIDDEN:
    case SDL_EVENT_WINDOW_MINIMIZED:
    case SDL_EVENT_WINDOW_MAXIMIZED:
    case SDL_EVENT_WINDOW_RESTORED:
    case SDL_EVENT_WINDOW_MOUSE_ENTER:
    case SDL_EVENT_WINDOW_MOUSE_LEAVE:
    case SDL_EVENT_WINDOW_HIT_TEST:
    case SDL_EVENT_WINDOW_ICCPROF_CHANGED:
    case SDL_EVENT_WINDOW_OCCLUDED:
    case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
    case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
    case SDL_EVENT_WINDOW_DESTROYED:
    case SDL_EVENT_WINDOW_HDR_STATE_CHANGED:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->window.windowID);
        wota_write_text(wb, "data1");
        wota_write_number(wb, (double)e->window.data1);
        wota_write_text(wb, "data2");
        wota_write_number(wb, (double)e->window.data2);
        break;
    case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED:
        {
            SDL_Window *window = SDL_GetWindowFromID(e->window.windowID);
            SDL_Rect safe_area = {0, 0, 0, 0};
            if (window && SDL_GetWindowSafeArea(window, &safe_area)) {
                wota_write_text(wb, "which");
                wota_write_number(wb, (double)e->window.windowID);
                wota_write_text(wb, "x");
                wota_write_number(wb, (double)safe_area.x);
                wota_write_text(wb, "y");
                wota_write_number(wb, (double)safe_area.y);
                wota_write_text(wb, "width");
                wota_write_number(wb, (double)safe_area.w);
                wota_write_text(wb, "height");
                wota_write_number(wb, (double)safe_area.h);
            } else {
                // Fallback to original behavior if SDL_GetWindowSafeArea fails
                wota_write_text(wb, "which");
                wota_write_number(wb, (double)e->window.windowID);
                wota_write_text(wb, "data1");
                wota_write_number(wb, (double)e->window.data1);
                wota_write_text(wb, "data2");
                wota_write_number(wb, (double)e->window.data2);
            }
        }
        break;
    case SDL_EVENT_WINDOW_MOVED:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->window.windowID);
        wota_write_text(wb, "x");
        wota_write_number(wb, (double)e->window.data1);
        wota_write_text(wb, "y");
        wota_write_number(wb, (double)e->window.data2);
        break;
    case SDL_EVENT_WINDOW_RESIZED:
    case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
    case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->window.windowID);
        wota_write_text(wb, "width");
        wota_write_number(wb, (double)e->window.data1);
        wota_write_text(wb, "height");
        wota_write_number(wb, (double)e->window.data2);
        break;
    case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
    case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->window.windowID);
        wota_write_text(wb, "display_index");
        wota_write_number(wb, (double)e->window.data1);
        wota_write_text(wb, "data2");
        wota_write_number(wb, (double)e->window.data2);
        break;
    case SDL_EVENT_JOYSTICK_ADDED:
    case SDL_EVENT_JOYSTICK_REMOVED:
    case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->jdevice.which);
        break;
    case SDL_EVENT_JOYSTICK_AXIS_MOTION:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->jaxis.which);
        wota_write_text(wb, "axis");
        wota_write_number(wb, (double)e->jaxis.axis);
        wota_write_text(wb, "value");
        wota_write_number(wb, (double)e->jaxis.value);
        break;
    case SDL_EVENT_JOYSTICK_BALL_MOTION:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->jball.which);
        wota_write_text(wb, "ball");
        wota_write_number(wb, (double)e->jball.ball);
        wota_write_text(wb, "rel");
        wota_write_vec2(wb, (double)e->jball.xrel, (double)e->jball.yrel);
        break;
    case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
    case SDL_EVENT_JOYSTICK_BUTTON_UP:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->jbutton.which);
        wota_write_text(wb, "button");
        wota_write_number(wb, (double)e->jbutton.button);
        wota_write_text(wb, "down");
        wota_write_sym(wb, e->jbutton.down ? WOTA_TRUE : WOTA_FALSE);
        break;
    case SDL_EVENT_GAMEPAD_ADDED:
    case SDL_EVENT_GAMEPAD_REMOVED:
    case SDL_EVENT_GAMEPAD_REMAPPED:
    case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE:
    case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->gdevice.which);
        break;
    case SDL_EVENT_GAMEPAD_AXIS_MOTION:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->gaxis.which);
        wota_write_text(wb, "axis");
        wota_write_text(wb, SDL_GetGamepadStringForAxis(e->gaxis.axis));
        wota_write_text(wb, "value");
        // Normalize axis values
        double normalized_value;
        if (e->gaxis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || 
            e->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
            // Triggers: 0 to 32767 -> 0 to 1
            normalized_value = (double)e->gaxis.value / 32767.0;
        } else {
            // Thumbsticks: -32768 to 32767 -> -1 to 1
            normalized_value = (double)e->gaxis.value / 32767.0;
        }
        wota_write_number(wb, normalized_value);
        break;
    case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
    case SDL_EVENT_GAMEPAD_BUTTON_UP:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->gbutton.which);
        wota_write_text(wb, "button");
        wota_write_text(wb, SDL_GetGamepadStringForButton(e->gbutton.button));
        wota_write_text(wb, "down");
        wota_write_sym(wb, e->gbutton.down ? WOTA_TRUE : WOTA_FALSE);
        break;
    case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
    case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
    case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->gtouchpad.which);
        wota_write_text(wb, "touchpad");
        wota_write_number(wb, (double)e->gtouchpad.touchpad);
        wota_write_text(wb, "finger");
        wota_write_number(wb, (double)e->gtouchpad.finger);
        wota_write_text(wb, "pos");
        wota_write_vec2(wb, (double)e->gtouchpad.x, (double)e->gtouchpad.y);
        wota_write_text(wb, "pressure");
        wota_write_number(wb, (double)e->gtouchpad.pressure);
        break;
    case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
        wota_write_text(wb, "which");
        wota_write_number(wb, (double)e->gsensor.which);
        wota_write_text(wb, "sensor");
        wota_write_number(wb, (double)e->gsensor.sensor);
        wota_write_text(wb, "sensor_timestamp");
        wota_write_number(wb, (double)e->gsensor.sensor_timestamp);
        break;
    case SDL_EVENT_USER:
        wota_write_text(wb, "cb");
        wota_write_number(wb, (double)(uintptr_t)e->user.data1);
        break;
    }
}

static WotaBuffer event2wota(const SDL_Event *event) {
  WotaBuffer wb;
  wota_buffer_init(&wb, 8);
  int n = event2wota_count_props(event);
  event2wota_write(&wb, event, n);
  return wb;
}

// Get all events directly from SDL event queue
JSC_CCALL(input_get_events,
    JSValue events_array = JS_NewArray(js);
    SDL_Event event;
    int event_count = 0;
    
    while (SDL_PollEvent(&event)) {
        // Process event with ImGui first
//        gui_input(&event);
        
        WotaBuffer wb = event2wota(&event);
        JSValue event_obj = wota2value(js, wb.data);
        JS_SetPropertyUint32(js, events_array, event_count, event_obj);
        wota_buffer_free(&wb);
        event_count++;
    }
    
    return events_array;
)

JSC_CCALL(input_gamepad_id_to_type,
  int id = js2number(js, argv[0]);
  return JS_NewString(js, SDL_GetGamepadStringForType(SDL_GetGamepadTypeForID(id)));
)

static const JSCFunctionListEntry js_input_funcs[] = {
  MIST_FUNC_DEF(input, mouse_show, 1),
  MIST_FUNC_DEF(input, mouse_lock, 1),
  MIST_FUNC_DEF(input, keyname, 1),
  MIST_FUNC_DEF(input, keymod, 0),
  MIST_FUNC_DEF(input, mousestate, 0),
  MIST_FUNC_DEF(input, get_events, 0),
  MIST_FUNC_DEF(input, gamepad_id_to_type, 1),
};

JSValue js_input_use(JSContext *js) {
  JSValue mod = JS_NewObject(js);
  JS_SetPropertyFunctionList(js,mod,js_input_funcs,countof(js_input_funcs));
  return mod;
}